WinDbg Q&A: Returværdier i WinDbg (anden del)

I forrige indlæg viste jeg, hvordan vi får returværdien fra en metode ved hjælp af WinDbg. Denne gang skal vi se på en lidt mere kompliceret situation. Hvordan finder vi returværdien fra en anonym metode?

Givet nedenstående kode:

Func<int, int> f = x => x + 1;
Console.WriteLine(f.Invoke(1));

Hvordan finder vi returværdien for den metode, f repræsenterer?

Faktisk er problemstillingen ikke så kompliceret, så man måske skulle tro. Anonyme metoder er nemlig ikke spor anonyme, når det kommer til stykket. De er blot pakket ind i lidt compilerhekseri, så den første opgave er, at finde ud af hvilket navn den anonyme metode gemmer sig under.

Det kan vi læse ud af IL-koden, så lad os se nærmere på den for den omsluttende metode. I det her tilfælde er det Main(), så lad os se på den. For at se på IL-koden via WinDbg, har vi brug for Main()s MethodDesc. Den kan vi finde med !name2ee:

0:000> !name2ee * TestBench.Program.Main
Module: 6db11000 (mscorlib.dll)
--------------------------------------
Module: 00162c5c (TestBench.exe)
Token: 0x06000001
MethodDesc: 00163010
Name: TestBench.Program.Main()
JITTED Code Address: 00270070

MethodDesc for Main() er altså 00163010. Lad os se på IL for metoden.

0:000> !dumpil 00163010
ilAddr = 013c2064
IL_0000: nop
IL_0001: ldstr "press enter"
IL_0006: call System.Console::WriteLine
IL_000b: nop
IL_000c: call System.Console::ReadLine
IL_0011: pop
IL_0012: ldsfld TestBench.Program::CS$<>9__CachedAnonymousMethodDelegate1
IL_0017: brtrue.s IL_002c
IL_0019: ldnull
IL_001a: ldftn TestBench.Program::<Main>b__0
IL_0020: newobj class [System.Core]System.Func`2<int32,int32>::.ctor
IL_0025: stsfld TestBench.Program::CS$<>9__CachedAnonymousMethodDelegate1
IL_002a: br.s IL_002c
IL_002c: ldsfld TestBench.Program::CS$<>9__CachedAnonymousMethodDelegate1
IL_0031: stloc.0
IL_0032: ldloc.0
IL_0033: ldc.i4.1
IL_0034: callvirt class [System.Core]System.Func`2<int32,int32>::Invoke
IL_0039: call System.Console::WriteLine
IL_003e: nop
IL_003f: ret

Læg mærke til de spøjse navne. Det er navnene på henholdsvis den generede delegate type og den faktiske metode, der implementerer vores anonyme metode. Vores metode hedder altså TestBench.Program::

b__0. Lad os se nærmere på den:

0:000> !name2ee * TestBench.Program.<Main>b__0
Module: 6db11000 (mscorlib.dll)
--------------------------------------
Module: 00152c5c (TestBench.exe)
Token: 0x06000003
MethodDesc: 00153024
Name: TestBench.Program.<Main>b__0(Int32)
Not JITTED yet. Use !bpmd -md 00153024 to break on run.

Som det fremgår, er den endnu ikke oversat, så vi er nødt til at gå via et breakpoint på MD som foreslået i udskriften.

 0:000> !bpmd -md 00153024
MethodDesc = 00153024
Adding pending breakpoints...

Så er det blot at køre videre, til vi rammer vores breakpoint.

0:000> g
(1ad0.ecc): CLR notification exception - code e0444143 (first chance)
JITTED TestBench!TestBench.Program.<Main>b__0(Int32)
Setting breakpoint: bp 00260118 [TestBench.Program.<Main>b__0(Int32)]
Breakpoint 0 hit
eax=00153024 ebx=0038efdc ecx=00000001 edx=00000001 esi=006255f0 edi=00000000
eip=00260118 esp=0038efa0 ebp=0038efb0 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
00260118 55              push    ebp

Metoden er JIT-oversat, og vi er stoppet i begyndelsen af den. Som i første eksempel skal vi altså blot køre til slutningen og inspicere det korrekte register.

0:000> g $ra
eax=00000002 ebx=0038efdc ecx=00000001 edx=00000000 esi=006255f0 edi=00000000
eip=002600f0 esp=0038efa4 ebp=0038efb0 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
002600f0 8945fc          mov     dword ptr [ebp-4],eax ss:002b:0038efac=0038f1d8

0:000> ? $retreg
Evaluate expression: 2 = 00000002

Og der har vi vores returværdi. Havde metoden returneret long i stedet for int, skulle vi blot bruge $retreg64 i stedet.

Leave a Reply