In this entry, we will examine the last two interesting new commands bundled in the Silverlight SOS.DLL – namely !FindRoots and !ListNearObj. (Previously in this series: Basic introduction to Silverlight SOS; The !AnalyzeOOM command; The !HeapStat and !GCWhere commands.)
The !FindRoots command is specifically oriented at understanding memory leaks and object promotion scenarios. It is a wrapper on top of existing functionality, making analysis more convenient.
The key scenario to using !FindRoots is as follows:
- You’re noticing that some objects are not being collected even though you think they are supposed to be.
- You determine that these objects are in some generation N (by using the !GCWhere command).
- You use !FindRoots –gen N to set a breakpoint when the GC completes a collection of generation N. (This is a one-shot breakpoint, by the way.)
- You use the !FindRoots command again with the object’s address to find all roots still keeping it alive (or, if the object is dead, you are satisfied that it has been collected).
The following is example output for the above scenario – I’m looking for the reason a specific object in gen1 is not being collected:
0:016> !GCWhere 0394aabc
Address Gen Heap segment begin allocated size
0394aabc 1 0 038e0000 038e1000 03986fb8 …(40300)
0:016> !FindRoots -gen 1
<snipped for clarity>
CLR notification: GC – Performing a gen 1 collection. Determined surviving objects…
First chance exceptions are reported before any exception handling.
<snipped for clarity>
0:005> !FindRoots 0394aabc
older generations::Root: 048e4260(Object)->
We have just determined that the specific byte array (at 0x0394aabc) is kept alive by a reference from an older generation. This reference is coming through a list of byte arrays which is turn referenced by the Silverlight page and so on.
The second !FindRoots command can be used to look at object roots only after the breakpoint induced by the first !FindRoots command has occurred. An alternative that is (almost) always available is the !GCRoot command (which lacks the ability to report “older generations” as a special kind of roots – it doesn’t treat older generations differently from any other reference root path).
For those of us (like me) still working on the desktop version of the CLR, an alternative to setting a breakpoint with !FindRoots is using the standard breakpoints mechanism in WinDbg. The key method to look for is mscorwks!WKS::GCHeap::RestartEE (for server GC, replace WKS with SVR); to determine which generation is currently being collected, inspect the static variable mscorwks!WKS::GCHeap::GcCondemnedGeneration.
To automate the process, you can set a conditional breakpoint to stop whenever gen1 has been collected:
0:005> bp mscorwks!WKS::GCHeap::RestartEE "j 0<poi(mscorwks!WKS::GCHeap::GcCondemnedGeneration) ”; g"
This is a somewhat dense format to learn, but the Debugging Tools for Windows documentation is quite helpful.
Finally, we’re up to the last command – !ListNearObj. From everything we’ve seen in the previous installments, you might have safely deduced that heaps don’t lie (even confirmed by Shakira). However, sometimes heaps do lie – or become corrupted – and when they do, the ability to take an arbitrary address and look at its surroundings to see what’s going on is priceless.
This is the purpose of the !ListNearObj command – when given an address, it will attempt to show you other objects in the area, even if the address points to the middle of an object. It does so using a heuristic looking for things that appear like a valid method table address (so if you accidentally have an integer field that looks like a valid method table address, the command reports wrong information). It also attempts to detect local heap corruption, as a bonus.
Here’s what !ListNearObj looks like when passed a rogue pointer into the middle of a byte array:
0:016> !ListNearObj 0393d500
Before: 0x393d430 24456 (0x5f88) System.Byte
After: 0x39433b8 42740 (0xa6f4) System.Byte
Heap local consistency confirmed.
It has found the heap locally consistent, and the address passed to it is in the middle of a large (24,000+ elements) array of bytes.