Determining the origin of a static root
A number of times when debugging managed code I've realised an object is ultimitely rooted in a static member but I've not been sure how to determine where in the application that static is declared. Today I finally got round to figuring out a way to do it.
Take this program as an example:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static ArrayList _fruit = new ArrayList();
static void Main(string[] args)
{
_fruit.Add(new Fruit("Apple"));
_fruit.Add(new Fruit("Bannana"));
_fruit.Add(new Fruit("Cherry"));
Console.ReadLine();
}
}
class Fruit
{
public Fruit(string Name)
{
_name = Name;
}
private string _name = "";
}
}
Suppose I am debugging this and want to determine where these Fruit instances are rooted. Obviously in this case I know 'cause I just wrote the program but let's pretend I don't.
First we need to load the SOS extension for managed debugging:
0:003> .loadby sos mscorwks
Note that the above is for Whidbey SOS debugging. For 1.1 framework debugging you need to load the SOS that installs with WinDBG:
0:003> .load clr10\sos
Next we need to find where one of the Fruit instances is rooted.
0:003> !dumpheap -type Fruit
Address MT Size
02651c28 01cc30c4 12
02651c68 01cc30c4 12
02651c74 01cc30c4 12
total 3 objects
Statistics:
MT Count TotalSize Class Name
01cc30c4 3 36 ConsoleApplication1.Fruit
Total 3 objects
0:003> !gcroot 02651c28
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread c8c
ESP:2cf438:Root:02651ba4(System.Collections.ArrayList)->
02651c48(System.Object[])->
02651c28(ConsoleApplication1.Fruit)
ESP:2cf450:Root:02651ba4(System.Collections.ArrayList)->
02651c48(System.Object[])
ESP:2cf464:Root:02651ba4(System.Collections.ArrayList)->
02651c48(System.Object[])
Scan Thread 2 OSTHread d98
DOMAIN(002D9968):HANDLE(Pinned):1ca13fc:Root:03651010(System.Object[])->
02651ba4(System.Collections.ArrayList)
So we can see that in addition to some roots in registers on the current thread, it is rooted in a pinned Object[]. Let's look closer at the Object array:
0:003> !do 03651010
Name: System.Object[]
MethodTable: 79124228
EEClass: 7912479c
Size: 4096(0x1000) bytes
Array: Rank 1, Number of elements 1020, Type CLASS
Element Type: System.Object
Fields:
None
The size of this array is 1020 elements and 0x1000 bytes. You'll see such arrays quite often when debugging managed code because the static members for an AppDomain are held in an Object array. Each static is represented by a particular element in this array. So we can find it by searching for it in the memory occupied by the Object[]:
0:003> s-d 03651010 L?0x1000 02651ba4
03651ec4 02651ba4 00000000 00000000 00000000 ..e.............
So the address of the element of the array that holds the statics is 0x03651ec4. Let's search the whole of memory for references to that address:
0:003> s-d 0 L?0xbfffffff 03651ec4
01cc2fc8 03651ec4 00000500 01cc3008 11000001 ..e......0......
02060094 03651ec4 e13989e8 c35e9077 01cc1880 ..e...9.w.^.....
Luckily we only found two hits. Check if any of these are in JITted code:
0:003> !u 01cc2fc8
Unmanaged code
01cc2fc8 c41e les ebx,[esi]
01cc2fca 650300 add eax,gs:[eax]
01cc2fcd 0500000830 add eax,0x30080000
01cc2fd2 cc int 3
01cc2fd3 0101 add [ecx],eax
01cc2fd5 0000 add [eax],al
01cc2fd7 1100 adc [eax],eax
01cc2fd9 0000 add [eax],al
01cc2fdb 90 nop
01cc2fdc 0000 add [eax],al
NO, that's not it. Check the next one:
0:003> !u 02060094
Normal JIT generated code
ConsoleApplication1.Program..cctor()
Begin 02060070, size 30
02060070 56 push esi
02060071 833dc82dcc0100 cmp dword ptr [01cc2dc8],0x0
02060078 7405 jz 0206007f
0206007a e87f220378 call mscorwks!JIT_DbgIsJustMyCode (7a0922fe)
0206007f b9b0361079 mov ecx,0x791036b0 (MT: System.Collections.ArrayList)
02060084 e8931fc5ff call 01cb201c (JitHelp: CORINFO_HELP_NEWSFAST)
02060089 8bf0 mov esi,eax
0206008b 8bce mov ecx,esi
*** WARNING: Unable to verify checksum for mscorlib.ni.dll
0206008d e8ce1d3077 call mscorlib_ni+0x2a1e60 (79361e60) (System.Collections.ArrayList..ctor(), mdToken: 0600153c)
02060092 8d15c41e6503 lea edx,[03651ec4]
02060098 e88939e177 call mscorwks!JIT_Writeable_Thunks_Buf+0xf6 (79e73a26) (mscorwks!JIT_Writeable_Thunks_Buf)
0206009d 90 nop
0206009e 5e pop esi
0206009f c3 ret
This is it! In the static constructor of our Program class (ConsoleApplication1.Program..cctor) we create the new ArrayList. This is then stored in the Object[].