WinDbg Q&A: Returværdier i WinDbg (første del)

Et tilbagevendende spørgsmål fra min sidemand er ”hvordan ser man returværdien fra en funktion under debugging?” og kort tid efter kommer kommentaren, ”det kunne man da i C++”. Derefter følger lidt mumlen og banden.

Desværre er der ingen let måde at gøre det på, når det kommer til managed code. I hvert fald ikke fra Visual Studio. I WinDbg er det derimod ikke så svært, så lad os se på hvad, der skal til.

Returværdier afleveres i et register. Desværre er dette platformafhængig, men til alt held har WinDbg et såkaldt pseudoregister, der indkapsler denne forskellighed (faktisk er der to, et til 32 bit værdier og et til 64 bit værdier). For at se returværdien skal vi altså blot inspicere dette register på det rette tidspunkt.

Det relevante register sættes lige inden funktionen returnerer, og derfor er vi nødt til at stoppe afviklingen på dette tidspunkt. Heldigvis er der endnu et pseudoregister, der kan hjælpe os i den situation. Lad os se på et eksempel.

Betragt nedenstående klasse. Vi har en instansmetode, SomeMethod(), der returnerer en string eller mere specifikt en reference til en instans af string.

public sealed class SomeType {
   public string SomeMethod() {
      var date = DateTime.Now.ToShortDateString();
      var buffer = new StringBuilder();
      var line = new string('-', date.Length);
      buffer.AppendLine(line);
      buffer.AppendLine(date);
      buffer.AppendLine(line);
      return buffer.ToString();
   }
}

Vi desuden har følgende kode:

var st = new SomeType();
Console.WriteLine(st.SomeMethod());

og vi er interesseret i, at finde ud af hvad SomeMethod() returnerer. Lad os sætte WinDbg på sagen, og lad os desuden sige, at vi endnu ikke har kørt SomeMethod() andetsteds i koden, så den er endnu ikke blevet JIT-oversat.

Det første, vi skal gøre, er at finde method tabel (MT) for SomeType. Det kan vi gøre !name2ee eller via !dumpheap -type. Sidstnævnte kræver lidt mindre tastearbejde, så den snupper jeg.

0:003> !dumpheap -type SomeType
 Address       MT     Size
02652a78 002a3080       12
total 1 objects
Statistics:
      MT    Count    TotalSize Class Name
002a3080        1           12 TestBench.SomeType
Total 1 objects

Som forventet finder vi en instans. Af ovenstående kan vi se, at den relevante MT er 002a3080. Via den kan vi se metoder på vores type.

0:003> !dumpmt -md 002a3080
EEClass: 002a138c
Module: 002a2c5c
Name: TestBench.SomeType
mdToken: 02000004  (C:\dev\TestBench\TestBench\bin\x86\Debug\TestBench.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
6dcd6a90   6db51248   PreJIT System.Object.ToString()
6dcd6ab0   6db51250   PreJIT System.Object.Equals(System.Object)
6dcd6b20   6db51280   PreJIT System.Object.GetHashCode()
6dd474c0   6db512a4   PreJIT System.Object.Finalize()
003e00d8   002a3078      JIT TestBench.SomeType..ctor()
002ac03d   002a306c     NONE TestBench.SomeType.SomeMethod()

Der har vi SomeMethod(). Bemærk, at JIT-kolonnen siger NONE, hvilket vil sige, at metoden ikke er blevet JIT-oversat. Det kan skyldes, at den enten ikke har været kaldt endnu eller at den er blevet inlined. I vores tilfælde er det fordi den endnu ikke er blevet kaldt, og derfor er den ikke blevet oversat.

Læg mærke til Entry og MethodDesc-kolonnerne. Hvis metoden er oversat, står adressen på den oversatte version i Entry-kolonnen. Er den ikke oversat, skal vi i stedet sætte et breakpoint via MethodDesc. Da vores metode ikke er oversat, er det fremgangsmåden i dette tilfælde. Det gøres som følger:

0:003> !bpmd -md 002a306c
MethodDesc = 002a306c
Adding pending breakpoints...

Herefter kører vi videre, indtil vi rammer vores breakpoint.

0:003> g
(1638.fc4): CLR notification exception - code e0444143 (first chance)
JITTED TestBench!TestBench.SomeType.SomeMethod()
Setting breakpoint: bp 003E0110 [TestBench.SomeType.SomeMethod()]
Breakpoint 0 hit
eax=002a306c ebx=001af3ac ecx=02652a78 edx=00000000 esi=005855f0 edi=00000000
eip=003e0110 esp=001af370 ebp=001af380 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
003e0110 55              push    ebp

Applikationen er nu stoppet ved indgangen til SomeMethod(), og hvis vi kører !dumpmt igen som ovenfor, kan vi se, at metoden ganske rigtig er blevet JIT-oversat.

Hvis vi blot er interesseret i returværdien, er det eneste, vi behøver at gøre, at fortsætte afviklingen til vi rammer returadressen. Herefter vil vores returværdi være i det relevante register.

Vi kan fortsætte afviklingen og stoppe ved returadressen via g-kommandoen og pseudoregisteret $ra, som indeholder returadressen. Altså:

0:000> g $ra
C:\Windows\assembly\NativeImages_v2.0.50727_32\mscorlib\8c1770d45c63cf5c462eeb945ef9aa5d\mscorlib.ni.dll
eax=02655d04 ebx=001af3ac ecx=00000001 edx=00000000 esi=005855f0 edi=00000000
eip=003e00b7 esp=001af374 ebp=001af380 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
003e00b7 8945f4          mov     dword ptr [ebp-0Ch],eax ss:002b:001af374=001af390

Så er vi ved udgangen af metoden, og skal altså blot udskrive returværdien. Vi ved, at det er en string, så returværdien er en reference til et objekt. Lad os skrive det ud:

0:000> !do $retreg
Name: System.String
MethodTable: 6dd788c0
EEClass: 6db3a498
Size: 146(0x92) bytes
 (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: ----------
12-03-2010
----------

Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
6dd7ab0c  4000096        4         System.Int32  1 instance       65 m_arrayLength
6dd7ab0c  4000097        8         System.Int32  1 instance       36 m_stringLength
6dd795a0  4000098        c          System.Char  1 instance       2d m_firstChar
6dd788c0  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  00582618:02651198 <<
6dd794f0  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  00582618:02651768 <<

Voila! Et styk string fra vores metode.

2 Responses to “WinDbg Q&A: Returværdier i WinDbg (første del)”

  1. Christian HovedMistænkt Rysgaard says:

    Jeg melder mig som hovedmistænkt, meeeen, studser nok en smule over din bemærkning: “I WinDbg er det derimod ikke så svært”

    Alene baseret på antallet af linier i denne artikel som bevismateriale, burde den sætning nok omformuleres til: “i Visual Studio kan man ikke, men det er muligt i WinDbg”… :)

  2. Ja ja, det kan du mene, men sammenlignet med alt andet i WinDbg, er dette jo ligetil, og jeg finder det altså ikke så svært :)

Leave a Reply