[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
}
}
Comments
Anonymous
March 04, 2012
Thanks for this script. One question: The comments say token 11 is needed but the code actually uses token 15. Shouldn't the code test for 0n11, not 0n15? MarcAnonymous
March 05, 2012
Marc, thanks for letting me know. The source code is actually right but the comment was wrong. :-) Just fixed it. RobertoAnonymous
March 05, 2012
There is a bug when running it on an x64 dump file. I'm fixing it and soon I'll release an update.Anonymous
March 05, 2012
This new version should work fine with 32 bits and 64 bits. :-)