[WinDbg Script] Displaying the COM object referenced by an RCW object

Here we go again after a long time without blogging and an even longer time without blogging about WinDBG scripts.

When debugging dump files from .NET applications sometimes we may encounter a situation where we want to get the COM object referenced by a System.__ComObject wrapper which references an RCW
object.

You may think that dumping the System.__ComObject may give you the answer, but it doesn’t.

 

Example:

Name: System.__ComObject

MethodTable: 79307098

EEClass: 790dfa34

Size: 16(0x10) bytes

GC Generation: 2

 (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

Fields:

      MT    Field  
Offset                 Type
VT     Attr    Value Name

79330740  400018a        4       
System.Object  0 instance 00000000
__identity

79333178  400027e        8 ...ections.Hashtable  0 instance 00000000 m_ObjectToDataMap

 

Where is the COM object???

Thus, to get the information there is a series of steps which I learned from Mario Hewardt (author of Advanced Windows Debugging and Advanced .NET Debugging) when using Private Symbols. The first thing that occurred to me is that I’d forget the steps when needing to use the technique again, so I created a script to do the job for me. After using the Microsoft internal script for a while I decided to create a simpler version to share with the public. This version only needs public symbols.

Interesting thing is that just before creating this blog post, while browsing to see if someone else could have blogged a similar script, I found out this article which uses the same technique that Mario uses! So in case you want to know the technique, just read the article or analyze my source code. ;-)

I must warn you that although this script should work for dump files from either 32 bits processes or 64 bits processes, we didn’t have the opportunity to test it in different scenarios because we don’t have enough dump files to cover most scenarios. So if you find bugs, please put a comment here and let me know.

 

For those new to WinDbg scripting, this script uses different techniques combined. Likewise it is good to be used as a template to create other scripts. You can learn more techniques from here and here.

Based on the public beta of SOS 4.5 there is a command that does that:

 

!DumpRCW <RCW address>

This command lists information about a Runtime Callable Wrapper. You can use !DumpObj to obtain the RCW address corresponding to a managed object.

The output contains all COM interface pointers that the RCW holds on to, which is useful for investigating lifetime issues of interop-heavy applications.

 

Screenshots:   
  
  
 

Now if the COM object was released and the RCW reference counter was not decremented (for instance, Marshal.FinalReleaseComObject not called), you’ll know because the script is going to show invalid addresses, like:

 

 

Another situation:

 

 

Source code for COM_FROM_RCW_PUBLIC.TXT:

 

$$
$$ =============================================================================
$$ COM_FROM_RCW_PUBLIC.TXT
$$
$$ Version: 1.2
$$
$$ Note: Create a folder called MyScripts where your WinDbg.exe is located and
$$       save the script there.
$$
$$ This script gives you the COM object used by System.__ComObject
$$
$$ Note: This is the Public version.
$$ Compatibility: Win32/Win64.
$$ PSSCORx or SOS required.
$$
$$ Usage: $$>a<myscripts\COM_FROM_RCW.txt  <address of System.__ComObject>
$$
$$ Mario Hewardt    
$$ Roberto Alexis Farah - https://blogs.msdn.com/debuggingtoolbox/
$$
$$ 3/5/2012 - Fixed problem with x64.
$$
$$ All my scripts are provided "AS IS" with no warranties, and confer no rights.
$$ =============================================================================
$$
$$ Checks if user is providing the argument.
.if(0 == ${/d:$arg1})
{

    .printf /D "\n<b> Please, provide the address of System.__ComObject as argument for the

script.</b>\n\n"
    .printf  "Usage: $$>a<myscripts\\COM_FROM_RCW_PUBLIC.txt <address of System.__ComObject>"
}
.else
{
    .catch
    {
        .printf /D "\n<b> Make sure you are using Symbols and PSSCOR or SOS is loaded...</b>\n

\n"

        $$ Let's get the address of the object - 0x4 and the low order WORD from that address.
        r @$t0 = wo(${$arg1}-0x4)

        $$ Gets Sync Block because we need the first syncblk field.
        $$ To do that we need to redirect the output to a file and parse the file.
        .logopen TEMP.LOG

        !syncblk @$t0

        .logclose

        $$ Now let's parse the output... We need token # 15
        $$ This is what we need:
        $$
        $$ Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner
        $$     3 100e502c <<<       0         0 00000000     none    12a32b3c System.__ComObject
     
        $$ Counter to count tokens. /pS didn't work as I expected...
        r @$t1 = 0
        r @$t2 = 0
          
        .foreach /f (obj "TEMP.LOG")
        {
             r @$t1 = @$t1 + 1

             $$ Is this field number 15? If yes we can ignore the other fields.    
             .if(0n15 == @$t1)
             {
                  .echo SyncBlock address = ${obj}

                  $$ Let's save our address. Keep in mind that this line can store
                  $$ garbage, like a field name from the output, if something goes wrong.
                  r @$t2 = ${obj}

                  .break
             }
        }

        $$ Protection against invalid pointers.
        .catch
        {
            .if(0n4 == @$ptrsize)
            {
                r @$t3 = poi(poi(poi(@$t2+0x1c)+@$ptrsize*0n3)+0x88)
            }
            .else
            {
                r @$t3 = poi(poi(poi(@$t2+0x28)+@$ptrsize*0n3)+0x100)
            }
        }

        .printf /D "\n<b>This is the COM object referenced by the RCW object from address

%p:</b>\n\n", @$t2

        dps @$t3 L10
    }
}