Traversing the gc heap (and introducing PSSCOR.DLL)

We have an improved SOS.DLL with many bug fixes and enhancements. Tom Christian in Product Support maintains it, and gave me permission to post it here under the name PSSCOR.DLL.

[update June 2005 - For some time now, PSSCOR.DLL has been included with the Windows Debugger package, although it is renamed to SOS.DLL. I've removed this old link because you get it just by installing the debugger].


It works on V1.0 and V1.1 of the CLR. Load it in the same way you'd load sos.dll in the Windows Debugger, with “.load psscor.dll“. The good thing about PSSCOR.DLL is that we can fix bugs and enhance functions without going through a lengthy QFE process. If you've found bugs in SOS, it's likely that many were fixed already in PSSCOR.DLL.

The code examples below use psscor.dll to explore the gc heap. You'll want to use it in lieu of SOS, because some commands like !DumpMT and !EEHeap have additional useful output.

It's useful to know how objects are laid out in the gc heap. During garbage collection, valid objects are marked by recursively visiting objects starting from roots on stacks and in handles. But it's also important that the location of the objects sit in an organized way from the beginning to end of each heap segment. The psscor !DumpHeap command counts on this logical organization to walk the heap properly, and if it reports an error you can bet something is wrong with your heap (and will bite you later with a perplexing application violation). So to understand what !dumpheap is talking about, here is your guide to walking these objects by hand, hopping from one stone to another across a vast lake.

First you need a program. I have taken this program from Joel Pobar's Reflection Emit example, and inserted a PInvoke to DebugBreak so you can easily stop in the Windows Debugger. (You could use Visual Studio for these illustrations too, but the Windows Debugger "dd" command is quicker for viewing memory).

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using System.Runtime.InteropServices;

public class EmitHelloWorld

      public static extern void DebugBreak();

      static void Main(string[] args)
            // create a dynamic assembly and module 
            AssemblyName assemblyName = new AssemblyName(); 
            assemblyName.Name = "HelloWorld"; 
            AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
            ModuleBuilder module; 
            module = assemblyBuilder.DefineDynamicModule("HelloWorld.exe"); 
            // create a new type to hold our Main method
            TypeBuilder typeBuilder = module.DefineType("HelloWorldType", TypeAttributes.Public | TypeAttributes.Class);
            // create the Main(string[] args) method
            MethodBuilder methodbuilder = typeBuilder.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(string[]) });
            // generate the IL for the Main method
            ILGenerator ilGenerator = methodbuilder.GetILGenerator();
            ilGenerator.EmitWriteLine("hello, world");
            // bake it
            Type helloWorldType = typeBuilder.CreateType();
            // run it
            helloWorldType.GetMethod("Main").Invoke(null, new string[] {null});

            // set the entry point for the application and save it
            assemblyBuilder.SetEntryPoint(methodbuilder, PEFileKinds.ConsoleApplication);

Save the program as example.cs, compile and run "cdb -g example.exe"

When you reach the breakpoint, load psscor and run "!eeheap -gc". It lists the heap segments that objects are stored in:

0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00aa1b78
generation 1 starts at 0x00a5100c
generation 2 starts at 0x00a51000
ephemeral segment allocation context: none
 segment    begin allocated     size
00a50000 00a51000  00ae4000 0x00093000(602112)
Large object heap starts at 0x01a51000
 segment    begin allocated     size
01a50000 01a51000  01a54060 0x00003060(12384)
Total Size   0x96060(614496)
GC Heap Size   0x96060(614496)

This is a small gc heap, with only one normal object segment, and one large object segment (for objects over 80K). It's fine for our purposes. Normal-sized objects start at address 00a51000, and end at 00ae4000. In general we have this simple pattern:

       |---------|   segment.begin = 00a51000
       |object 1 |
       |object 2 |
       |   ...   |
       |object N |
       |_________|  segment.allocated = 00ae4000

How large is each object? You can run !dumpobj to find out. The interesting thing is that each object has a 4 byte header, and the size of the header for object 2 is included in the size of object 1. Another point is that a special kind of object called a "Free" object lives in the heap. This is used to plug holes between valid objects. These Free objects are temporary, in that if a compacting gc occurs they'll disappear. Yun wrote a great article about how the heap could be unable to compact in the face of heavy pinning , and be filled with Free objects (

Let's start walking. (My heap may look different because it's a Whidbey debug build)

0:000> !dumpobj 00a51000
Free Object
Size 12(0xc) bytes
0:000> !dumpobj 00a51000+c
Free Object
Size 12(0xc) bytes
0:000> !dumpobj 00a51000+c+c
Free Object
Size 12(0xc) bytes
0:000> !dumpobj 00a51000+c+c+c
Name: System.OutOfMemoryException
MethodTable: 03077e9c
EEClass: 03064050
Size: 68(0x44) bytes
      MT    Field   Offset                 Type       Attr    Value Name
03076b7c  40000a5        4                CLASS   instance 00000000 _className
03076b7c  40000a6        8                CLASS   instance 00000000 _exceptionMe
03076b7c  40000a7        c                CLASS   instance 00000000 _exceptionMe
03076b7c  40000a8       10                CLASS   instance 00000000 _message
03076b7c  40000a9       14                CLASS   instance 00000000 _data
03076b7c  40000aa       18                CLASS   instance 00000000 _innerExcept
03076b7c  40000ab       1c                CLASS   instance 00000000 _helpURL
03076b7c  40000ac       20                CLASS   instance 00000000 _stackTrace
03076b7c  40000ad       24                CLASS   instance 00000000 _stackTraceS
03076b7c  40000ae       28                CLASS   instance 00000000 _remoteStack
03076b7c  40000af       30         System.Int32   instance        0 _remoteStack
03076b7c  40000b0       34         System.Int32   instance -2147024882 _HResult
03076b7c  40000b1       2c                CLASS   instance 00000000 _source
03076b7c  40000b2       38        System.IntPtr   instance        0 _xptrs
03076b7c  40000b3       3c         System.Int32   instance -532459699 _xcode

Wow, it took some time to get to something interesting. You could continue like this until you get a buffer overflow due to all the "+c+44+68+12+..." You can also let !DumpHeap do this for you. It gives a rather sparse printout of the object pointers. Let's limit the output to the segment we care about (and note that Size is in decimal):

0:000> !dumpheap 00a51000 00ae4000
 Address       MT     Size
00a51000 0015c260       12 Free
00a5100c 0015c260       12 Free
00a51018 0015c260       12 Free
00a51024 03077e9c       68    
00a51068 030782cc       68    
00a510ac 030786fc       68    
00a510f0 03078b5c       68    
00a51134 030f7b54       20    
00a51148 0308b06c      108    
00a511b4 030fa5bc       32    
00a511d4 0305bbf8       28    
00a511f0 030592e0       80    
00a51240 0015c260       72 Free

How do we know the size of each object? Just look at the MethodTable, the first DWORD of the object. You can run !dumpmt on it:

0:000> !dumpmt 03077e9c
EEClass: 03064050
Module: 0016b118
Name: System.OutOfMemoryException
mdToken: 02000038  (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86dbg\mscorlib.dll)
BaseSize: 44
Number of IFaces in IFaceMap: 2
Slots in VTable: 21

BaseSize is in hex here. (We have a hard time deciding how we like to see these things!) How about arrays, how do we know their size?  Let's list all the arrays in the segment to figure it out:

0:000> !dumpheap -type [] 00a51000 00ae4000
 Address       MT     Size
00a511f0 030592e0       80
00a5129c 03115b68       56
00a51348 03135ca0       76
00a513a8 030592e0       16
00a51434 0313b1c0      144
00a51634 0313c234      100
00a51698 0313c620       56
00a51cc4 030592e0       16
00a51e8c 0313b1c0      144
00a52008 0313b1c0      144
00a52244 0313b1c0      144
00a52308 0313b1c0      144
00a523cc 0313b1c0      144
00a52620 0313b1c0      144
00a526e4 0313b1c0      144
00a52a14 031e23f8       36
00a52b7c 0313b1c0      144
00a52c0c 0315778c     1084
00a53048 0315778c     1628
00a536a4 0315778c      824

Picking one at random:

0:000> !dumpobj 00a52c0c
Name: System.Int32[]
MethodTable: 0315778c
EEClass: 03157708
Size: 1084(0x43c) bytes
Array: Rank 1, Type Int32
Element Type: System.Int32

The formula for determining array size is:

MethodTable.BaseSize + (MethodTable.ComponentSize * Object.Components)

!dumpmt will tell you the first two:

0:000> !dumpmt 315778c
EEClass: 03157708
Module: 0016b118
Name: System.Int32[]
mdToken: 02000000  (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86dbg\mscorlib.dll)
BaseSize: 0xc
ComponentSize: 0x4
Number of IFaces in IFaceMap: 4
Slots in VTable: 25

and you can find the number of items in the array with:

0:000> dd 00a52c0c+4 l1
00a52c10  0000010C

[I'm sure Josh Williams will come along and chide me for forgetting that on 64-bit pointers are 8 bytes, so I'd have to add 8 instead of 4 above. :p]. 0xc + (0x10C*0x4) = 0x43c, so our size is correct.

So we understand object sizes, and how they are arranged. There is one thing missing though, and this is the presence of zero-filled regions throughout the heap called Allocation Contexts. For efficiency, each managed thread can be given such a region to direct new allocations to. This allows multithreaded apps to allocate without expensive locking operations. There is also an Allocation Context for the heap segment that contains generations 0 and 1 (also called the Ephemeral Segment). The !dumpheap command is aware of these regions, and steps lightly over them. You can get the thread Allocation Context addresses with the !threads command:

0:000> !threads
ThreadCount: 2
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 0
                                 PreEmptive   GC Alloc               Lock
      ID OSID ThreadOBJ    State     GC       Context       Domain   Count APT Exception
  0    1 16ac 00155da8      a020 Enabled  00ae2e1c:00ae3ff4 0014a890     0 MTA
  2    2 169c 001648f8      b220 Enabled  00000000:00000000 0014a890     0 MTA (Finalizer)

Thread 0 (the main thread) has an allocation context, from 00ae2e1c to 00ae3ff4. If we look at that memory, we'll see all zeros:

0:000> dd 00ae2e1c
00ae2e1c  00000000 00000000 00000000 00000000
00ae2e2c  00000000 00000000 00000000 00000000
00ae2e3c  00000000 00000000 00000000 00000000
00ae2e4c  ...

As for the Ephemeral Segment Allocation Context, we don't have one. Recalling !eeheap -gc output:

0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00aa1b78
generation 1 starts at 0x00a5100c
generation 2 starts at 0x00a51000
ephemeral segment allocation context: none
 segment    begin allocated     size
00a50000 00a51000  00ae4000 0x00093000(602112)

You might end up with a buffer overflow someday, and obliterate the MethodTable of an object right after your array of StrongBad fan club members. The next time a GC occurs, your program will crash. Let's simulate that dreadful occurrance and see how !dumpheap responds:

0:000> ed adf7f8 00650033 (I'm overwriting the MethodTable of the array we've been enjoying)
0:000> !dumpheap 00a51000 00ae4000
00adf7ac 03135ca0       76
object 00adf7f8: does not have valid MT
curr_object : 00adf7f8
Last good object: 00adf7ac

This allows you to become suspicious of the last good object, 00adf7ac. Of course we know he's alright, he's not responsible for what happened. But in the real world, an aggressive response is required! [imagine WWII air-raid siren here]

What is that last good object anyway?

0:000> !dumpobj adf7ac
Name: System.Byte[]
MethodTable: 03135ca0
EEClass: 03135c1c
Size: 76(0x4c) bytes
Array: Rank 1, Type Byte
Element Type: System.Byte

Who cares about him? If I can find a root to this object on a stack, I may be close to code that would overwrite the next object:

0:000> !gcroot adf7ac
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 16ac
Scan Thread 2 OSTHread 169c

Thread 0, eh? He's employed by an ILGenerator, eh? What kind of nefarious operations are going on in their shop! Okay I'll stop. But it's true, often the last good object is somehow responsible, and a PInvoke overrun is the reason why.

I've ignore the Large Object Heap Segment, but it is crawled in the same way. It has no pesky Allocation Contexts to muddy the water. Large Object segments are never compacted, it would take to long to move such objects around, as they are over 80K in size.

Have fun with PSSCOR.DLL.


Comments (36)
  1. Pavel Lebedinsky says:

    > Tom Christian in Product Support maintains

    > it, and gave me permission to post it here

    > under the name PSSCOR.DLL…

    Isn’t this essentially the same thing as sos.dll that’s included with the latest debuggers (

    > BaseSize: 44

    > BaseSize is in hex here. (We have a hard

    > time deciding how we like to see these

    > things!)

    How about using 0x44 or 00000044 to make it clearer?

  2. mvstanton says:

    Yes it is. I wasn’t aware that version of the Windows Debugger (with psscor.dll shipped as sos.dll) had been publicly released yet.

    As for the hex/decimal confusion, great idea, I will do that. :-).

  3. Rick Byers <> says:

    Thanks for the awesome information!

    There is one thing that has been bugging me for a while about using SOS to analyze memory dumps. What is the level of support for minidumps? I had thought there was no support, but then I discovered the mscordmp tool ("CLR minidump") that comes with the SDK.

    I have searched all over, but haven’t been able to determine how to make use of its output. Is there any way to load the data it saves into WinDBG and have SOS use it? How much information is included there? It doesn’t look like rotor has such a tool, so I can’t simply look at the source.



  4. Dmitriy Zaslavskiy says:

    I doesn’t seem that this is the same version as the one included with WinDbg.

    This works with 1.0 and 1.1 and the other one not really.

  5. Keith Hill says:

    I have the same question as Rick. How do you debug a dump file created by MSCORDMP.EXE? I am intrigued by the possibility of doing post-mortem debugging via dump files for managed applications. The only way to create dump files for managed apps is with mscordmp. Yet they don’t load into WinDBG and they won’t run in VS.NET. I get unrecognized or unsupported binary format.

  6. Mike Blake-Knox says:

    I’m trying to look at perpetually growing heap using PSSCOR.DLL (in windbg) and a .NET 1.0 Windows service. I believe I need to use the !findtable command first but I keep getting two messages about not being able to find a .bin file and a reference to "\netdebugsos" (NOT a machine I’m aware of).

    Here’s the text:

    0:000> !findtable C:Tempdump1SOS.SVR.4.3705.288.BIN

    No .BIN files found.

    Attempting to load SOS data from the directory: "\netdebugsos".

    No .BIN files found.

    Any ideas?


  7. Michael STanton says:

    Hi Mike, you shouldn’t need to use !findtable. Just load PSSCOR.DLL, and immediately use !eeheap, or !dumpheap, or whatever command you want. Let me know,


  8. In questo

    caso SOS sta per Son Of



    debugger (tra gli altri CDB e&amp;nbsp;WinDBG)…

  9. Stale DNA says:

    Probably beaten to death elsewhere but I always seem to end up having to search for these when I need…

  10. Stale DNA says:

    Probably beaten to death elsewhere but I always seem to end up having to search for these when I need…

  11. Stale DNA says:

    Probably beaten to death elsewhere but I always seem to end up having to search for these when I need…

  12. SoS is the WinDbg extension for analysing Managed Applications


    Brown’s Bugslayer

    SOS It’s…

  13. For those who have worked with WinDBG, Son-of-Strike (SOS) wouldn’t be an unfamiliar territory – the…

  14. Probably beaten to death elsewhere but I always seem to end up having to search for these when I need

  15. I was writing an internal wiki page on performance and thought this info is useful to many external readers

  16. In questo caso SOS sta per S on O f S trike. Alcuni debugger (tra gli altri CDB e WinDBG) espongono un

  17. roy ashbrook says:

    I&#39;m done with most of these, but not totally. Tons of great information! Here are some more great

  18. This should be must tools and skills that every .net developer needs to learn, to use WinDbg and SOS.dll

  19. crosspost from This should be must tools and skills that every .net developer

  20. This should be must tools and skills that every .net developer needs to learn, to use WinDbg and SOS

  21. myspace free layouts and backgrounds

Comments are closed.

Skip to main content