Den kommende version af .NET byder på flere nyheder i selve afviklingsmiljøet. Derfor er den medfølgende SOS også blevet opdateret til at arbejde med den nyeste version af CLRen, der meget passende er blevet omdømt fra mscorwks.dll til clr.dll. Den første synlige ændring er således måden, hvorpå vi indlæser SOS.
Normalt har jeg blot benyttet mig af, at jeg har .NET framework i min path, og derfor kan jeg blot skrive.
.load sos
Det finder den korrekte version af SOS til framework version 2.0. Det virker ikke umiddelbart længere, da vi har brug for at kunne indlæse den korrekte version, afhængig af om vi debugger en applikation til .NET 4.0 eller tidligere. For at indlæse SOS til .NET 4.0, skal vi derfor bruge .loadby således:
.loadby sos clr
Det indlæser den version af sos.dll, der ligger sammen med clr.dll. Vi kan verificere, at vi har fået indlæst den korrekte version ved hjælp af .chain.
0:004> .chain
...
Extension DLL chain:
C:\Windows\Microsoft.NET\Framework\v4.0.21006\sos: image 4.0.21006.1, API 1.0.0, built Tue Oct 06 23:13:16 2009
[path: C:\Windows\Microsoft.NET\Framework\v4.0.21006\sos.dll]
...
Som det fremgår af ovenstående udsnit, har vi fået indlæst den version af SOS, der hører til version 4 af .NET framework. Med det på plads, er vi klar til at se på de nye kommandoer.
Nye kommandoer
I Beta 2 er der, så vidt jeg kan se følgende nye kommandoer: AnalyzeOOM, DumpSigElem, FindRoots, GCWhere, HeapStat, HistClear, HistInit, HistObj, HistObjFind, HistRoot, ListNearObj, VerifyObj og ThreadState. I Beta 1 var der desuden en HistStat-kommando, der er forsvundet hjælpen. I dette indlæg skal vi se på nogle af de nye kommandoer. Jeg vender tilbage til de resterende kommandoer i efterfølgende indlæg.
FindRoots og GCWhere
Formålet med FindRoots er at finde ud af, hvorfor instanser, der ikke længere bliver refereret, ikke er blevet fjernet af GC. Det er ikke ualmindelig, at objekter i generation 1 eller 2 ligger på heapen uden aktive rødder. Hvis en oprydning af generation 0 tilgodeser applikationens behov, vil eventuelle instanser i generation 1 og 2 ikke blive ryddet op uanset, at de ikke længere er i brug af applikationen. De vil først blive opryddet, når deres respektive generationer bliver opryddet.
Vi kan bruge FindRoots til at finde objekter i den tilstand. Kommandoen fungerer lidt anderledes end de kendte SOS-kommandoer, idet den udføres af to omgange. Først bruger vi -gen til at sætte et breakpoint, når en given generation bliver ryddet op. Derefter kører vi programmet og venter på at vores breakpoint bliver aktiveret. Når dette er sket, kan vi undersøge om specifikke objekter vil blive ryddet op i denne ombæring.
Lad os se på et eksempel. Betragt nedenstående stump kode.
class Foo {
public byte[] buffer { get; set; }
}
public static class Program {
static void Main() {
Console.ReadLine();
var f = new Foo();
GC.Collect();
GC.Collect(); // f peger på instans i gen2
Debugger.Break();
var buffer = new byte[1000]; // gen0
f.buffer = buffer; // gen2 objekt peger på gen0
Console.WriteLine(f.buffer.Length);
Debugger.Break();
buffer = null;
Console.WriteLine(f);
f = null;
GC.Collect(0);
}
}
Kører vi programmet, attacher mens vi venter på ReadLine() og fortsætter med g, indtil vi rammer Debugger.Break(), kan vi se på tilstanden.
Vi kan bruge DumpStackObjects (dso) til at lokalisere vores instans af Foo. Vi ved, at der er en reference til den på stakken, og i og med vi er stoppet som følge af Debugger.Break(), vil vi være på den korrekt tråd.
0:000> !dso OS Thread Id: 0x654 (0) ESP/REG Object Name 003FEED8 0251d0b8 System.Security.Permissions.SecurityPermission 003FEEEC 0251d0b8 System.Security.Permissions.SecurityPermission 003FEF80 0251d094 TestApp.Foo 003FEF8C 0251d094 TestApp.Foo 003FF3C8 03513250 System.Object[] (System.Object[])
Det ser ud til at vores instans er på adressen 0251d094. Lad os se efter.
0:000> !do 0251d094
Name: TestApp.Foo
MethodTable: 00193880
EEClass: 001914a0
Size: 12(0xc) bytes
File: c:\dev2010\TestApp\TestApp\bin\Release\TestApp.exe
Fields:
MT Field Offset Type VT Attr Value Name
589ff9f8 4000001 4 System.Byte[] 0 instance 00000000 <buffer>k__BackingField
Yep, det er vores instans af Foo, og som vi kan se, er der ikke noget i vores buffer-property. Eftersom at vi har kørt GC.Collect() to gange, er vores instans nu i generation 2. Det kan vi verificere med GCWhere.
0:000> !gcwhere 0251d094 Address Gen Heap segment begin allocated size 0251d094 2 0 02510000 02511000 0251d8cc 0xc(12)
Som forventet er vores objekt i generation 2. Lad os forsætte.
0:000> g (744.18f8): Break instruction exception - code 80000003 (first chance) …
Vi udskriver vores objekt igen og skulle gerne kunne se en reference til en instans af byte[].
0:000> !do 0251d094
Name: TestApp.Foo
MethodTable: 00193880
EEClass: 001914a0
Size: 12(0xc) bytes
File: c:\dev2010\TestApp\TestApp\bin\Release\TestApp.exe
Fields:
MT Field Offset Type VT Attr Value Name
589ff9f8 4000001 4 System.Byte[] 0 instance 0251d0c4 <buffer>k__BackingField
Vores instans af Foo refererer nu en instans af byte[] på adressen 0251d0c4. Denne instans er i generation 0, da vi lige har allokeret den, og nu kan vi bruge FindRoots til at indskyde et breakpoint, når denne generation bliver ryddet op.
0:000> !findroots -gen 0
Herefter fortsætter vi afviklingen og venter på den næste oprydning af generation 0 (som vi i dette tilfælde selv fremprovokerer ved kald af GC.Collect()).
0:000> g (744.18f8): CLR notification exception - code e0444143 (first chance) CLR notification: GC - Performing a gen 0 collection. Determined surviving objects... First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=0041e95c ebx=00000000 ecx=00000003 edx=00000000 esi=0041ea08 edi=00000003 eip=771fb727 esp=0041e95c ebp=0041e9ac iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 KERNELBASE!RaiseException+0x58: 771fb727 c9 leave
Vi kan nu bruge den anden version af FindRoots til at finde ud af, hvorfor vores objekt ikke er blevet ryddet op, skønt det ligger i generation 0 og ikke længere er refereret af applikationen.
0:000> !findroots 0251d0c4 older generations::Root: 0251d094(TestApp.Foo)-> 0251d0c4(System.Byte[])
FindRoots fortæller os, at et objekt i en ældre generation holder vores instans i live. Hvad siger FindRoots til vores objekt i generation 2.
0:000> !findroots 0251d094 Object 0251d094 will survive this collection: gen(0x251d094) = 2 > 0 = condemned generation.
Her kan vi se, at vores instans ikke bliver ryddet op fordi, der er et objekt i generation 2, der stadig refererer det, og eftersom generation 2 ikke bliver ryddet op i denne ombæring, er vores instans stadig i brug.
Eksemplet her er en smule konstrueret, så derfor vil den ivrige læser sikkert opdage, at GCRoot også melder vores buffer-instans som værende i brug, skønt vi kan se på koden, at dette ikke er tilfældet. Grunden til GCRoots udmelding skyldes, at vi stadig har en stakreference til vores instans af Foo, eftersom vores stak stadig er i live. Under mere realistiske forhold ville denne information ikke længere være i spil. Kører vi således GCRoot med -nostacks, er udmeldingen da også som forventet, at der ikke er nogen rødder til vores instans.
HeapStat
HeapStat giver et hurtig overblik over størrelsen og den tilgængelige plads for de forskellige heaps. Oversigten er inddelt per generation, så det er let at få indsigt i forbruget. Der er også en procentvis angivelse af hvor meget plads, der er tilbage i de forskellige heaps. SOH står for Small Object Heap og dækker generation 0 til 2. LOH står for Large Object Heap og dækker den del af heapen, der bruges til opbevaring af instanser på mere end 85000 bytes.
Output ser ud som følger:
0:002> !heapstat Heap Gen0 Gen1 Gen2 LOH Heap0 177904 12 306956 8784 Heap1 159652 12 12 16 Total 337556 24 306968 8800 Free space: Percentage Heap0 28 12 12 64 SOH: 0% LOH: 0% Heap1 104 12 12 16 SOH: 0% LOH:100% Total 132 24 24 80
ListNearObj
ListNearObj leder efter instanser umiddelbart før og efter en given instans på heapen. Samtidig foretager den en validering af heapens tilstand.
Kommandoen er brugbar i to situationer: Hvis heapen er i god stand, fortæller den os noget om hvordan objekterne ligger indbyrdes. Det kan f.eks. være brugbart i forbindelse med Free-områder, da de ofte opstår som følge af mellemliggende pinned objekter.
Hvis vi har en smadret heap og ser på en instans, hvis tilstand er forkert, kan det være en hjælp at kunne finde objekter i nærheden af dette, eftersom de meget vel kan være medvirkende til problemerne.
Kommandoen bruges som følger:
0:004> !listnearobj 02912010 Before: 02911010 4096 (0x1000) System.Object[] Current: 02912010 16 (0x10) Free object After: 02912020 528 (0x210) System.Object[] Heap local consistency confirmed.
ThreadState
ThreadState er analog til min lille kommando af same navn. Den tager en samling flag fra Threads-kommandoen og oversætter dem fra deres hex-repræsentation til noget mere sigende. Så hvis Threads fortæller os, at en given tråd har State b220, kan vi bruge ThreadState til at få dette oversat som vist nedenfor.
0:004> !threadstate b220
Legal to Join
Background
CLR Owns
CoInitialized
In Multi Threaded Apartment
Det er måske stadig ikke så uddybende, som man kunne ønske sig, men det er da en anelse mere forståelig end hex-værdien.
Det var ordene for denne gang. Jeg vender tilbage med flere detaljer om de nye kommandoer i senere indlæg.