Minimal example:
> cat app.c
extern char lafc(int argc, char *argv[]);
int main(int argc, char *argv[]) {
return lafc(argc, argv);
}
> cat lafc.c
char lafc(int argc, char *argv[]) {
char ec = argv[argc - 1][0];
return ec;
}
The idea is to build lafc.c
("Last Arg First Char") without debug info and instead synthesize them in the plugin:
> clang -O0 -g0 -c lafc.c -o lafc.obj
> clang -O0 -g app.c lafc.obj -o app.exe
> dumpbin /DISASM app.exe | grep -A15 -B14 "lafc:"
main:
0000000140007130: 48 83 EC 38 sub rsp,38h
0000000140007134: C7 44 24 34 00 00 mov dword ptr [rsp+34h],0
00 00
000000014000713C: 48 89 54 24 28 mov qword ptr [rsp+28h],rdx
0000000140007141: 89 4C 24 24 mov dword ptr [rsp+24h],ecx
0000000140007145: 48 8B 54 24 28 mov rdx,qword ptr [rsp+28h]
000000014000714A: 8B 4C 24 24 mov ecx,dword ptr [rsp+24h]
000000014000714E: E8 B4 BA FF FF call @ILT+7170(lafc)
0000000140007153: 0F BE C0 movsx eax,al
0000000140007156: 48 83 C4 38 add rsp,38h
000000014000715A: C3 ret
000000014000715B: CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC ................
000000014000716B: CC CC CC CC CC .....
lafc:
0000000140007170: 48 83 EC 10 sub rsp,10h
0000000140007174: 48 89 54 24 08 mov qword ptr [rsp+8],rdx
0000000140007179: 89 4C 24 04 mov dword ptr [rsp+4],ecx
000000014000717D: 48 8B 44 24 08 mov rax,qword ptr [rsp+8]
0000000140007182: 8B 4C 24 04 mov ecx,dword ptr [rsp+4]
0000000140007186: 83 E9 01 sub ecx,1
0000000140007189: 48 63 C9 movsxd rcx,ecx
000000014000718C: 48 8B 04 C8 mov rax,qword ptr [rax+rcx*8]
0000000140007190: 8A 00 mov al,byte ptr [rax]
0000000140007192: 88 44 24 03 mov byte ptr [rsp+3],al
0000000140007196: 8A 44 24 03 mov al,byte ptr [rsp+3]
000000014000719A: 48 83 C4 10 add rsp,10h
000000014000719E: C3 ret
000000014000719F: CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC ................
00000001400071AF: CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC ................
Register the lafc
function and set a regular breakpoint:
0:000> !load SymbolBuilderComposition.dll
0:000> dx @$s = Debugger.Utility.SymbolBuilder.CreateSymbols("app", new { AutoImportSymbols = true })
@$s = Debugger.Utility.SymbolBuilder.CreateSymbols("app", new { AutoImportSymbols = true })
0:000> .reload
0:000> lmm app
Browse full module list
start end module name
00007ff7`b8d80000 00007ff7`b8e22000 app C (service symbols: Symbol Builder Symbols (with demand import from PDB))
0:000> dx @$lafc = @$s.Functions.Create("lafc", "char", 0x7170, 0x2e)
@$lafc = @$s.Functions.Create("lafc", "char", 0x7170, 0x2e) : Function: lafc
Name : lafc
QualifiedName : lafc
AddressRanges : [7170, 719e)
LocalVariables
Parameters : ()
ReturnType : Basic Type: char ( size = 1, align = 1 )
0:000> bp lafc
Register function parameters argc
and argv
:
0:000> dx @$lafc.Parameters.Add("argc", "int")
@$lafc.Parameters.Add("argc", "int") : int argc
Name : argc
QualifiedName : argc
Parent : Function: lafc
Type : Basic Type: int ( size = 4, align = 4 )
LiveRanges
Delete [Delete() - Deletes the variable (parameter or local)]
0:000> dx @$lafc.Parameters[0].LiveRanges.Add(0, 0x2e, "[@rsp+4]")
@$lafc.Parameters[0].LiveRanges.Add(0, 0x2e, "[@rsp+4]") : [0, 2e): argc = [@rsp + 4]
Offset : 0x0
Size : 0x2e
Location : [@rsp + 4]
0:000> dx @$lafc.Parameters.Add("argv", "char *[]")
@$lafc.Parameters.Add("argv", "char *[]") : char *[0] argv
Name : argv
QualifiedName : argv
Parent : Function: lafc
Type : Array: char *[0] ( size = 0, align = 8 )
LiveRanges
Delete [Delete() - Deletes the variable (parameter or local)]
0:000> dx @$lafc.Parameters[1].LiveRanges.Add(0, 0x2e, "[@rsp+8]")
@$lafc.Parameters[1].LiveRanges.Add(0, 0x2e, "[@rsp+8]") : [0, 2e): argv = [@rsp + 8]
Offset : 0x0
Size : 0x2e
Location : [@rsp + 8]
Register local variable ec
:
0:000> dx @$ec = @$lafc.LocalVariables.Add("ec", "char")
@$ec = @$lafc.LocalVariables.Add("ec", "char") : char ec
Name : ec
QualifiedName : ec
Parent : Function: lafc
Type : Basic Type: char ( size = 1, align = 1 )
LiveRanges
Delete [Delete() - Deletes the variable (parameter or local)]
0:000> dx @$ec.LiveRanges.Add(0, 0x2e, "[@rsp+3]")
@$ec.LiveRanges.Add(0, 0x2e, "[@rsp+3]") : [0, 2e): ec = [@rsp + 3]
Offset : 0x0
Size : 0x2e
Location : [@rsp + 3]
Resume execution, step and dump values:
0:000> g
Breakpoint 0 hit
app!lafc:
00007ff7`b8d87170 4883ec10 sub rsp,10h
0:000> t
app!lafc+0x4:
00007ff7`b8d87174 4889542408 mov qword ptr [rsp+8],rdx ss:00000093`0d0ff890=00100800000a0671
0:000> dv
argc = 0n0
argv = char *[0]
ec = 0n0 ''
0:000> t
app!lafc+0x9:
00007ff7`b8d87179 894c2404 mov dword ptr [rsp+4],ecx ss:000000f8`1d79fbec=00000000
0:000> t
app!lafc+0xd:
00007ff7`b8d8717d 488b442408 mov rax,qword ptr [rsp+8] ss:000000f8`1d79fbf0=00000289e4563950
0:000> dv
argc = 0n1
argv = char *[0]
ec = 0n0 ''
If we move past 0x140007190
we also see ec
getting a non-zero value. It's the first character of the last command line argument. Voila!