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[].