debuggingwindbgsossosex

Dumping only rooted managed objects and/or statistics on them inside WinDbg


My problem is that dumpheap -stat returns an awful lot of objects and I have no idea which ones are rooted and which ones are not.

Well, I can if I run the !mroot or !refs command on an individual address, but this approach does not scale very well to thousands of objects reported by dumpheap.

For example, dumpheap -stat contains the following line:

000007fef3d14088    74247      2375904 Microsoft.Internal.ReadLock

Wow, 74,247 instances. However, running

.logopen c:\tmp\2\log.txt;.foreach (entry {!dumpheap -type Microsoft.Internal.ReadLock -short}){!refs ${entry} -target};.logclose

reveals that each and every instance reported by DumpHeap is actually unreclaimed garbage!

How I found that each and every instance is garbage is another problem. I had to extract all the NONE strings to one file and all the Objects referencing strings to another and then compared the number of lines in every file. Surely there is a better way :-(.

Anyway, I would like to know how to focus on the rooted objects only. Ideally, I would like to get the statistics as well as details on such objects.


Solution

  • Your loop over all objects is already great, just the !refs command needs to be replaced by something else which finds only rooted objects. My example uses Strings, because I don't have an application available using ReadLocks.

    There are two possible outputs of the !refs command. A referenced object outputs e.g.

    Objects referencing 02703f18 (System.String):
    follow 02703a88       128   System.Globalization.NumberFormatInfo
    

    A garbage objects looks like this:

    Objects referencing 02703f30 (System.String):
    NONE
    

    You're only interested in the address of the first line in case the second line contains the word "follows". Luckily, the address is equal to ${entry}, so we actually don't need it. Otherwise you would be in the same trouble like I am.

    This brings me to this question about .if.

    You can use .foreach again on the output of !refs. Let's have a look at this on a single object first:

    .foreach (reftoken {!refs 027045cc -target}) { .printf "${reftoken}\n" }
    

    This prints one word per line and we are interested only in the fifth, which means we can initially skip 4 items and then skip the rest, which gives us

    .foreach /pS 4 /ps 3 (reftoken {!refs 027045cc -target}) { .printf "${reftoken}\n" }
    

    For robustness purposes, let's use /ps 99.
    Next we need to check if this token equals follow, which is done by

    .if ($sicmp("${reftoken}","follow") == 0) { .echo found follow }
    

    And combine it all together:

    .foreach (entry {!dumpheap -type System.String -short})
    {
       .foreach /pS 4 /ps 99 (reftoken {!refs ${entry} -target}) 
       { 
          .if ($sicmp("${reftoken}","follow") == 0) 
          { 
                .printf "${entry}\n"
          } 
       }
    }
    

    In one line:

     .foreach (entry {!dumpheap -type System.String -short}){.foreach /pS 4 /ps 99 (reftoken {!refs ${entry} -target}) {.if ($sicmp("${reftoken}","follow") == 0) {.printf "${entry}\n"}}}
    

    Of course you can replace .print by any other command which is helpful in your situation.

    I hope you can adapt this sample to use ReadLock.