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.
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”…
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