InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Windbg: Finding a specific instance of a managed object in a windows process

Todays post is a small detour from the regular Exchange related posts. The other day I was troubleshooting an issue with a program. It did not behave as expected in production and so I fired up WinDbg to inspect the value of some objects. I attached WindDbg to the process in question and load psscor4, a replacement from Microsoft for the standard SOS extension (Note that if you want to use psscor, you need the right version: Psscor2 is for .NET 2.0-3.5 software. Psscor4 is only for .NET 4 programs). The WinDbg results I present in this article are not from the process I actually examined. I hacked together a small sample program instead.

The sample class creates instances of a class named Person. A Person class has two properties: A name (string) and an age (int). The object instance I’m looking for is named Mallory. The goal is to get a look at the object instance with a call to !DumpObj. So all I need is the memory address of the Person instance named Alice. This should be easy.

So here we go. First, load the SOS extension. Since I ‘m examining an x64 process, I ‘m using the X64 version of psscor.

.load C:\temp\Psscor4\amd64\amd64\psscor4.dll

The first thing to do is to get an overview about the target class. How many instances of the Person class are currently lying around? A call to Dumpheap reveals this:

0:007> !dumpheap -type Person -stat
Loading the heap objects into our cache.
total 21 objects
Statistics:
              MT    Count    TotalSize       Change Class Name
000007ff000242b0        1           40            1 System.Collections.Generic.List`1[[DebugTest.Person, DebugTest]]
000007ff00024220       20          640           20 DebugTest.Person

So, we have one list of Person objects: List<Person> and 20 instances of the Person class itself. Since there are only 20 of them, let’s just dump them to the console:

Total 21 objects, Total size: 680
0:007> !dumpheap -type Person
Loading the heap objects into our cache.
         Address               MT     Size
00000000027f2588 000007ff000242b0       40    0 System.Collections.Generic.List`1[[DebugTest.Person, DebugTest]] 
00000000027f2588 000007ff000242b0       40     
00000000027f25d0 000007ff00024220       32    0 DebugTest.Person 
00000000027f25d0 000007ff00024220       32     
00000000027f2630 000007ff00024220       32    0 DebugTest.Person 
00000000027f2630 000007ff00024220       32     
00000000027f2650 000007ff00024220       32    0 DebugTest.Person 
00000000027f2650 000007ff00024220       32     
00000000027f2670 000007ff00024220       32    0 DebugTest.Person 
00000000027f2670 000007ff00024220       32     
00000000027f2690 000007ff00024220       32    0 DebugTest.Person 
00000000027f2690 000007ff00024220       32     
00000000027f2710 000007ff00024220       32    0 DebugTest.Person 
00000000027f2710 000007ff00024220       32     
00000000027f2730 000007ff00024220       32    0 DebugTest.Person 
00000000027f2730 000007ff00024220       32     
00000000027f2750 000007ff00024220       32    0 DebugTest.Person 
00000000027f2750 000007ff00024220       32     
00000000027f2770 000007ff00024220       32    0 DebugTest.Person 
00000000027f2770 000007ff00024220       32     
00000000027f2830 000007ff00024220       32    0 DebugTest.Person 
00000000027f2830 000007ff00024220       32     
00000000027f2850 000007ff00024220       32    0 DebugTest.Person 
00000000027f2850 000007ff00024220       32     
00000000027f2870 000007ff00024220       32    0 DebugTest.Person 
00000000027f2870 000007ff00024220       32     
00000000027f2890 000007ff00024220       32    0 DebugTest.Person 
00000000027f2890 000007ff00024220       32     
00000000027f28b0 000007ff00024220       32    0 DebugTest.Person 
00000000027f28b0 000007ff00024220       32     
00000000027f28d0 000007ff00024220       32    0 DebugTest.Person 
00000000027f28d0 000007ff00024220       32     
00000000027f28f0 000007ff00024220       32    0 DebugTest.Person 
00000000027f28f0 000007ff00024220       32     
00000000027f2910 000007ff00024220       32    0 DebugTest.Person 
00000000027f2910 000007ff00024220       32     
00000000027f2a50 000007ff00024220       32    0 DebugTest.Person 
00000000027f2a50 000007ff00024220       32     
00000000027f2a70 000007ff00024220       32    0 DebugTest.Person 
00000000027f2a70 000007ff00024220       32     
00000000027f2a90 000007ff00024220       32    0 DebugTest.Person 
00000000027f2a90 000007ff00024220       32     
So, twenty objects. Let’s have a look at one of the instances. This can be achieved using the !DumpObj command, or !do, in short:
0:007> !do 00000000027f2830 
Name:        DebugTest.Person
MethodTable: 000007ff00024220
EEClass:     000007ff00132400
Size:        32(0x20) bytes
File:        c:\users\hkrause\documents\visual studio 2010\Projects\DebugTest\DebugTest\bin\Debug\DebugTest.exe
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef5786870  4000001        8        System.String  0 instance 00000000027f22e8 <Name>k__BackingField
000007fef578c758  4000002       10         System.Int32  1 instance               25 <Age>k__BackingField

The interesting lines here are the last two. They describe the details and the values of the properties. Since I used auto-properties instead of traditional ones they are named <Name>k__BackingField instead something like _Name. Since the name if of type System.String, the value is a reference. So, the next step is to issue a !DumpObj on the value of that field:

0:007> !do 00000000027f22e8 
Name:        System.String
MethodTable: 000007fef5786870
EEClass:     000007fef530ed58
Size:        38(0x26) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      Bob
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef578c758  4000103        8         System.Int32  1 instance                6 m_stringLength
000007fef578b298  4000104        c          System.Char  1 instance               57 m_firstChar
000007fef5786870  4000105       10        System.String  0   shared           static Empty
                                 >> Domain:Value  00000000003ab570:00000000027e1420 <<

The nice thing about !DumpObj is that it prints string values directly to the console. In this case, the value is Bob. Ok, so this is not the instance I’m searching for. Hmm, just to check one instance for its name, I have to issue two !DumpObj commands and do a lot of copy-and-paste to copy the addresses from the console to the command line. And in this example, there are only 20 instances. In the process I debugged, there were much more. There must be a better way to do this. A colleague of mine jumped to help and together we indeed came up with one.

The idea is the following:

  • Get a list of the addresses of all Person instances.
  • For each entry
    • Given the address of the Perons instance, get the value of the <Name>k__BackingField
    • Print out the address of the Person instance along with the string value of the <Name>k__BackingField.

The first step is easy: Just execute this line:

!DumpHeap -type Person -short

This command will just dump the addresses of each instance to the console. Iterating through this list is also trivial, thanks to the foreach command:

.foreach(entry {!dumpheap -type Person -short}){.printf "%p:\n", entry;}

For each entry in the list returned by the command between the first pair curly braces, the variable entry is populated with the value and the command sequence between the second pair of curly braces is executed. The example above just prints out the value itself.

Now comes the harder part. We need to figure out the value of the backing field for the Name property. A closer inspection of the !DumpObj result above reveals that the field is stored at memory offset 8 inside of the object. To read that memory directly, the dq command can be used (dq because I’m debugging a 64bit process. Use dd for a 32bit process). Dq prints the memory at the specified address in the form of 64bit hex values to the screen. Since we are only interested in the 64-bit pointer at offset 8 of that object, we issue this command:

0:007> dq 00000000027f2830+8 L1
00000000`027f2838  00000000`027f22e8

The 00000000027f2830+8 specifies the memory address we are interested in. And the L1 specifies that we only want one quad-word. You can see that the result from the dq operation equals the vlaue of the backing field: 00000000027f22e8. We now need a way to dereference that memory address and print the value of the string at that point. At this point it’s good to know how .NET strings are stored in memory: A length indicator (32bit) followed by an unicode-char-array. The result of the DumpObj command on the string instance “Bob” above reveled that the character array starts at offset 0x0c within the string instance. To print the value of the string, the dq command cannot be used because we are interested in the string value itself. For this, the du command can be used. du means: Dump unicode string. To dereference the pointer provided by the dq command, the poi operator can be used. All combined, this resolves to this command:

0:007> du poi(00000000027f2830+8)+c
00000000`027f22f4  "Walter"

Nice, exactly what we wanted. Now we can combine this with the foreach command above:

0:007> .foreach(entry {!dumpheap -type Person -short}){.printf "%p: ", entry; du poi(${entry}+8)+c;.printf "\n"}
00000000027f2588: 00000000`027f293c  ""

00000000027f2588: 00000000`027f293c  ""

00000000027f25d0: 00000000`027f219c  "Alice"

00000000027f25d0: 00000000`027f219c  "Alice"

00000000027f2630: 00000000`027f21c4  "Bob"

00000000027f2630: 00000000`027f21c4  "Bob"

00000000027f2650: 00000000`027f21e4  "Carol"

00000000027f2650: 00000000`027f21e4  "Carol"

00000000027f2670: 00000000`027f220c  "Chuck"

00000000027f2670: 00000000`027f220c  "Chuck"

00000000027f2690: 00000000`027f2234  "Dave"

00000000027f2690: 00000000`027f2234  "Dave"

00000000027f2710: 00000000`027f225c  "Eve"

00000000027f2710: 00000000`027f225c  "Eve"

00000000027f2730: 00000000`027f227c  "Mallory"

00000000027f2730: 00000000`027f227c  "Mallory"

00000000027f2750: 00000000`027f22a4  "Peggy"

00000000027f2750: 00000000`027f22a4  "Peggy"

00000000027f2770: 00000000`027f22cc  "Victor"

00000000027f2770: 00000000`027f22cc  "Victor"

00000000027f2830: 00000000`027f22f4  "Walter"

00000000027f2830: 00000000`027f22f4  "Walter"

00000000027f2850: 00000000`027f231c  "Trent"

00000000027f2850: 00000000`027f231c  "Trent"

00000000027f2870: 00000000`027f2344  "Charlie"

00000000027f2870: 00000000`027f2344  "Charlie"

00000000027f2890: 00000000`027f236c  "Carlos"

00000000027f2890: 00000000`027f236c  "Carlos"

00000000027f28b0: 00000000`027f2394  "Arthur"

00000000027f28b0: 00000000`027f2394  "Arthur"

00000000027f28d0: 00000000`027f23bc  "Merlin"

00000000027f28d0: 00000000`027f23bc  "Merlin"

00000000027f28f0: 00000000`027f23e4  "Paul "

00000000027f28f0: 00000000`027f23e4  "Paul "

00000000027f2910: 00000000`027f2234  "Dave"

00000000027f2910: 00000000`027f2234  "Dave"

00000000027f2a50: 00000000`027f240c  "Sue"

00000000027f2a50: 00000000`027f240c  "Sue"

00000000027f2a70: 00000000`027f242c  "John"

00000000027f2a70: 00000000`027f242c  "John"

00000000027f2a90: 00000000`027f2454  "George"

00000000027f2a90: 00000000`027f2454  "George"

Now we can easily spot the instance named Mallory: It’s located at 00000000027f2730 and we can issue a !DumpObj on that address:

0:007> !do 00000000027f2730
Name:        DebugTest.Person
MethodTable: 000007ff00024220
EEClass:     000007ff00132400
Size:        32(0x20) bytes
File:        c:\users\hkrause\documents\visual studio 2010\Projects\DebugTest\DebugTest\bin\Debug\DebugTest.exe
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef5786870  4000001        8        System.String  0 instance 00000000027f2270 <Name>k__BackingField
000007fef578c758  4000002       10         System.Int32  1 instance               34 <Age>k__BackingField

Case closed Smile.


Tags:

Technorati:

Posted by Henning Krause on Monday, August 15, 2011 12:00 PM, last modified on Saturday, August 13, 2011 3:42 PM
Permalink | Post RSSRSS comment feed

Comments (1) -

On 8/17/2011 1:50:58 AM Elvin Cheng wrote:

Elvin Cheng

Good stuff for beginner to learn how to use WinDbg. Thanks!

 +Pingbacks and trackbacks (2)