Arrays and SOS


Looking at arrays with SOS on WinDBG is not exactly the most intuitive process in the world.  In order to demonstrate how to do this, I’ve written a small sample program that I’m going to “debug”.  The code simply creates three arrays:



string[] strs = new string[] { “String 1”, “String 2”, “String 3” };
int[] ints = new int[] { 21, 22, 23, 24, 25, 26, 27 };
double[] fps = new double[] { 3.14 };

All of the information in this post was done debugging Whidbey, though it should still apply to v1.0 and v1.1 of the runtime. This was also all done on x86. If you’re using IA64 or AMD64 then obviously the array format will be slightly different (there will be more padding, and the addresses will be 64 bits instead of 32), but the general format will remain the same, and you should be able to figure out what’s going on from looking at memory dumps.


The first step is to get the addresses of these arrays (I’ll edit all of my WinDBG outputs to focus on the relevant portions):



0:000> !sos.dso
ESP/REG Object Name
0012ec94 00ac2ad8 System.Double[]
0012eca8 00ac2a88 System.Object[]
0012ecac 00ac2aa4 System.Int32[]


Arrays of Native Types


In order to look at arrays of native types (here, int and double), just doing a standard !do doesn’t provide much information. Working with my integer array:



0:000> !do 00ac2aa4
Name: System.Int32[]
MethodTable: 5b9ca974
EEClass: 5ba4d4ec
Size: 40(0x28) bytes
Array: Rank 1, Type Int32
Element Type: System.Int32
Fields:
None


Instead of using SOS to look at this memory, I’ll instead just dump the memory contents at that address:



0:000> dd 00ac2aa4
00ac2aa4  5b9ca974 00000007 00000015 00000016
00ac2ab4  00000017 00000018 00000019 0000001a
00ac2ac4  0000001b 00010000 5b9938b0 00000000
00ac2ad4  00010000 5b9c9b18 00000001 51eb851f
00ac2ae4  40091eb8 00010000 5b9b2be0 00000006
00ac2af4  00000000 00000080 00010000 5b9c6614
00ac2b04  00000001 5b9938b0 00ac2aec 00010000
00ac2b14  5b9b2c8c 00000080 00010000 5b9b413c


I’ve highlighted relevant portions. The first word, in green, is the method table for the type of array. If you do a !dumpmt on this address, you’ll see that this is an array of integers:



0:000> !dumpmt 5b9ca974
EEClass: 5ba4d4ec
Module: 5b918000
Name: System.Int32[]
mdToken: 02000000 (C:\WINNT\Microsoft.NET\Framework\v2.0.40428\assembly\GAC_32\mscorlib\2.0.3600.0__b77a5c561934e089\mscorlib.dll)
BaseSize: 0xc
ComponentSize: 0x4
Number of IFaces in IFaceMap: 4
Slots in VTable: 25


The second word, in blue, is the number of elements in the array. In this example, there are 7 elements. The next seven words, shown here in maroon, are the actual integers stored in the array. You can see they’re hex values 0x15 through 0x1b, which are 21-27 in decimal … the values in the array


The floating point array works much the same. From the first !dso, remember that the floating point array starts at address 00ac2ad8. Running a !do on this just says that this is an array of doubles:



0:000> !do 00ac2ad8
Name: System.Double[]
MethodTable: 5b9c9b18
EEClass: 5ba4cc30
Size: 20(0x14) bytes
Array: Rank 1, Type Double
Element Type: System.Double
Fields:
None


To get useful information, once again dump the memory at the address:



0:000> dd 00ac2ad8
00ac2ad8  5b9c9b18 00000001 40091eb8 51eb851f
00ac2ae8  00010000 5b9b2be0 00000006 00000000
00ac2af8  00000080 00010000 5b9c6614 00000001
00ac2b08  5b9938b0 00ac2aec 00010000 5b9b2c8c
00ac2b18  00000080 00010000 5b9b413c 00ac2b80
00ac2b28  00010100 00000000 00010000 5b9b4668
00ac2b38  00000000 00000009 0000000a 00010000
00ac2b48  5b9c6614 0000000a 5b9938b0 00000000


Again, I’ve highlighted the relevant portions. The first word, in green, is once again the method table for this array. Dumping that shows we have an array of doubles:



0:000> !dumpmt 5b9c9b18
EEClass: 5ba4cc30
Module: 5b918000
Name: System.Double[]
mdToken: 02000000 (C:\WINNT\Microsoft.NET\Framework\v2.0.40428\assembly\GAC_32\mscorlib\2.0.3600.0__b77a5c561934e089\mscorlib.dll)
BaseSize: 0xc
ComponentSize: 0x8
Number of IFaces in IFaceMap: 4
Slots in VTable: 25


The next word, in blue, is the number of elements in the array. This shows there’s only one element. Since a double is 64 bits, the next two words form the value of this element. However, they are currently arranged in little endian format, and they need to be in big endian. To get this value, simply put the second word before the first. The value of this double is therefore: 0x40091eb851eb851f. Since I haven’t memorized the IEEE floating point format, and certainly can’t just interpret it by sight, I’ll use the WinDBG .formats command to translate this value:



0:000> .formats 40091eb851eb851f Evaluate expression:
  Hex:     40091eb8`51eb851f
  Decimal: 4614253070214989087
  Octal:   0400110753412172702437
  Binary:  01000000 00001001 00011110 10111000 01010001 11101011 10000101 00011111
  Chars:   @…Q…
  Time:    Fri Dec 27 09:23:41.498 16222 (GMT-7)
  Float:   low 1.26444e+011 high 2.1425
  Double:  3.14


You can see that it translates to the expected value of 3.14


Arrays of objects


Arrays of objects work a little differently. Running a !do on one shows the usual information:



0:000> !do 00ac2a88
Name: System.Object[]
MethodTable: 5b9c6614
EEClass: 5ba4b89c
Size: 28(0x1c) bytes
Array: Rank 1, Type CLASS
Element Type: System.Object
Fields:
None


So lets move on to dumping the memory directly, where I’ll again highlight the relevant portions:



0:000> dd 00ac2a88
00ac2a88  5b9c6614 00000003 5b99452c 00ac2a1c
00ac2a98  00ac2a40 00ac2a64 00010000 5b9ca974
00ac2aa8  00000007 00000015 00000016 00000017
00ac2ab8  00000018 00000019 0000001a 0000001b
00ac2ac8  00010000 5b9938b0 00000000 00010000
00ac2ad8  5b9c9b18 00000001 51eb851f 40091eb8
00ac2ae8  00010000 5b9b2be0 00000006 00000000
00ac2af8  00000080 00010000 5b9c6614 00000001


The first two words should be familiar, the first one is the method table for the type of array, and the second one is the number of elements in the array. The third word, highlighted in purple, is interesting. It’s the address of the method table of the type of object contained in the array. Since running a !dumpmt on the first word only shows us that this is an array of objects, we have to do a !dumpmt on the third word to figure out what kind of objects they are:



0:000> !dumpmt 5b9c6614
EEClass: 5ba4b89c
Module: 5b918000
Name: System.Object[]
mdToken: 02000000 (C:\WINNT\Microsoft.NET\Framework\v2.0.40428\assembly\GAC_32\mscorlib\2.0.3600.0__b77a5c561934e089\mscorlib.dll)
BaseSize: 0x10
ComponentSize: 0x4
Number of IFaces in IFaceMap: 4
Slots in VTable: 25


0:000> !dumpmt 5b99452c
EEClass: 5b9d7f10
Module: 5b918000
Name: System.String
mdToken: 02000027 (C:\WINNT\Microsoft.NET\Framework\v2.0.40428\assembly\GAC_32\mscorlib\2.0.3600.0__b77a5c561934e089\mscorlib.dll)
BaseSize: 0x10
ComponentSize: 0x2
Number of IFaces in IFaceMap: 5
Slots in VTable: 190


From the previous method tables, and from looking at the second word, we see we have an array of 3 objects of type System.String. The three words following the type method table address are the addresses of the items stored in the array. You can simply run a !do on these addresses to examine the objects contained within the array (edited for brevity):



0:000> !do 00ac2a1c
Name: System.String
MethodTable: 5b99452c
EEClass: 5b9d7f10
Size: 34(0x22) bytes
(C:\WINNT\Microsoft.NET\Framework\v2.0.40428\assembly\GAC_32\mscorlib\2.0.3600.0__b77a5c561934e089\mscorlib.dll)
String: String 1


0:000> !do 00ac2a40
Name: System.String
MethodTable: 5b99452c
EEClass: 5b9d7f10
Size: 34(0x22) bytes
(C:\WINNT\Microsoft.NET\Framework\v2.0.40428\assembly\GAC_32\mscorlib\2.0.3600.0__b77a5c561934e089\mscorlib.dll)
String: String 2


0:000> !do 00ac2a64
Name: System.String
MethodTable: 5b99452c
EEClass: 5b9d7f10
Size: 34(0x22) bytes
(C:\WINNT\Microsoft.NET\Framework\v2.0.40428\assembly\GAC_32\mscorlib\2.0.3600.0__b77a5c561934e089\mscorlib.dll)
String: String 3


From looking at this data, we can see that the three strings are “String 1”, “String 2”, “String 3”.

Comments (4)

  1. Pavel Lebedinsky says:

    There should really be a !DumpArray (!da) command for this.

  2. I was talking to the dev that owns SOS on the CLR the other day about adding a DumpArray command and he mentioned it is on the todo list, it however is a long list… Actually what I think would be great would be a more general !DumpCollection which would be array inclusive.

    For primitive types you can always also use the "dt -aX <type> <address>" command (I think that is the right syntax, don’t have the help in front of me). This works for types that the debugger can recognize and will print out X elements of the array of type <type> starting at <address>. It is useful for non-managed objects also where the debugger can give you good info.

    But I think that "dt -a7 int 00ac2aac" would pretty print the integer array. This works because for value types the managed array layout matches that of a native array…

  3. Janine Zhang says:

    Great post! The debugging steps are very clear and illustrutive. I learned several things. thanks.

  4. Oleg Shmytov says:

    If you have a hashtable or Array you can use !do command with -v switch.

    Here is an example for ArrayList collection:

    !do 3378ac5c

    Name: System.Collections.ArrayList

    MethodTable 0x79ba0d74

    EEClass 0x79ba0eb0

    Size 24(0x18) bytes

    mdToken: 020000ff (c:winntmicrosoft.netframeworkv1.1.4322mscorlib.dll)

    FieldDesc*: 79ba0f14

    MT Field Offset Type Attr Value Name

    79ba0d74 400035b 4 CLASS instance 3378ac74 _items

    79ba0d74 400035c c System.Int32 instance 0 _size

    79ba0d74 400035d 10 System.Int32 instance 0 _version

    79ba0d74 400035e 8 CLASS instance 00000000 _syncRoot

    Then you would need to execute

    !do -v 3378ac74 to get a list of elements from this Object array.

    !DumpCollection (!dc) is also available now. It works fine with ArrayList, Hashtable, Queue and some other types…