Date: | April 24, 2007 / year-entry #142 |
Tags: | other |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20070424-00/?p=27143 |
Comments: | 9 |
Summary: | When you're debugging, you might have a pointer to a COM interface and want to know what the underlying object is. Now, sometimes this trick won't work because the interface pointer actually points to a stub or proxy, but in the case where no marshalling is involved, it works great. (This technique also works for... |
When you're debugging, you might have a pointer to a COM interface and want to know what the underlying object is. Now, sometimes this trick won't work because the interface pointer actually points to a stub or proxy, but in the case where no marshalling is involved, it works great. (This technique also works for many C++ compilers for any object that has virtual methods and therefore a vtable.) Recall that the layout of a COM object requires that the pointer to a COM interface point to the object's vtable, and it's the vtable that is the key. 0:000> dv pstm = 0x000c7568 0:000> dt psf Local var @ 0x7cc2c Type IStream* 0x000c7568 +0x000 __VFN_table : 0x1c9c8e84
Okay, so far all we know is that our 0:000> ln 0x1c9c8e84 (1c9c8e84) ABC!CAlphaStream::`vftable'
Aha, it's a 0:000> dt ABC!CAlphaStream 0x000c7568 +0x000 __VFN_table : 0x1c9c8e84 // our vtable +0x004 m_cRef : 480022128 +0x008 lpVtbl : 0x1c9d2d30 +0x00c lpVtbl : 0x00000014 +0x010 m_pszName : 0x000c7844 "??????????" +0x014 m_dwFlags : 0x3b8 +0x018 m_pBuffer : 0x00000005 +0x01c m_cbBuffer : 705235565 +0x020 m_cbPos : 2031674
"Hey, how did you get the debugger to dump
Okay, back to the structure dump.
It doesn't look right at all.
The reference count is some absurd value,
the vtable at offset
What happened?
Well, clearly we were given a " How do we do this adjustment? There's the methodical way and the quick-and-dirty way.
The methodical way is to use the
adjustor thunks
to tell you how much the pointer needs to be adjusted
in order to move from a secondary vtable to the primary one.
(This assumes that the primary 0:000> dps 1c9c8e84 l1 1c9c8e84 1c9eb08e ABC![thunk]:CAlphaStream::QueryInterface`adjustor{8}' Aha, this adjustors adjust by eight bytes, so we just need to subtract eight from our pointer to get the object's starting address. 0:000> dt ABC!CAlphaStream 0x000c7560-8 +0x000 __VFN_table : 0x1c9c8ee8 +0x004 m_cRef : 2 +0x008 lpVtbl : 0x1c9c8e84 +0x00c lpVtbl : 0x1c9c8e70 +0x010 m_pszName : 0x1c9d2d30 "Scramble" +0x014 m_dwFlags : 0x14 +0x018 m_pBuffer : 0x000c7844 +0x01c m_cbBuffer : 952 +0x020 m_cbPos : 5 Ah, that looks much nicer. Notice that the reference count is a more reasonable value of two, the name pointer looks good, the buffer size and position appear to be much more realistic. Now, I don't bother with the whole adjustor thunk thing. Instead I rely on the principle of "Assume it's mostly correct": Assume that the object is not corrupted and just adjust the pointer by eye until the fields line up. Let's take another look at the original (bad) dump: 0:000> dt ABC!CAlphaStream 0x000c7568 +0x000 __VFN_table : 0x1c9c8e84 +0x004 m_cRef : 480022128 +0x008 lpVtbl : 0x1c9d2d30 +0x00c lpVtbl : 0x00000014 +0x010 m_pszName : 0x000c7844 "??????????" +0x014 m_dwFlags : 0x3b8 +0x018 m_pBuffer : 0x00000005 +0x01c m_cbBuffer : 705235565 +0x020 m_cbPos : 2031674
This obviously doesn't smell right, but what do we have to do
to get things to line up?
Well, we know that the vtable we have must go into one of the
other two vtable slots, either the one at offset In real life, I tend to pay attention to the vtables, the reference count, and any string members because it's usually pretty easy to see whether you got them right. (Vtables reside in code. Reference counts tend to be small integers. Strings are, well, strings.) |
Comments (9)
Comments are closed. |
Wow, thanks for elucidating this stuff!
Great stuff – this has been Ctrl-C/Ctrl-V’ed right into my debugging notes file.
Metanitpicker’s corner: Only a bug hunter would care.
I do something similar at runtime in Delphi:
http://hallvards.blogspot.com/2004/07/hack-7-interface-to-object-in-delphi.html
Delphi interfaces are COM compatible, so basically it is the same technique. I guess it is easier to call this function from the debugger than to muck about by hand ;).
What a coincidence you should be talking about COM interface pointers and vtables. I recently wrote a shell extension that adds a custom clipboard format to the shell’s IDataObject by allocating some global memory, and (after doing something useful with the memory) calling IDataObject::SetData. The shell eventually frees the memory by calling IUnknown::Release on the pUnkForRelease member of the STGMEDIUM I passed into IDataObject::SetData.
Nitpicker’s corner: when the shell extension’s reference count decrements zero, it calls OleFlushClipboard if necessary, which pretty much forces the shell to actually the free memory that was allocated. No memory leak here.
Anyway… earlier today a thought occurred to me: “suppose I had a .NET object that handed out an STGMEDIUM, and I had to free it immediately when the consumer releases it, not whenever the next garbage collection occurs. Can that be done in the CLR?” As it turns out, the answer is yes, but it’s not trivial. I wouldn’t recommend actually doing this in a real-world situation.
The problem is that the CLR doesn’t notify the underlying managed object when the last reference to the COM-callable wrapper is released. The CCW simply removes the strong reference to the managed object, making eligible for garbage collection (barring any other references to it.) Some mechanism akin to ATL’s FinalRelease method would have been nice, but alas, there’s no such thing in managed world. Nor does the CCW call the managed object’s IDisposable.Dispose method (should it exist,) which strikes me as an appropriate thing to do, but which might lead to unexpected behavior.
To get back on topic, the solution is to do some magic pointer-swizzling on the vtable of the managed object’s CCW. The CCW is a perfectly ordinary COM object, with a perfectly ordinary vtable. Since all COM objects inherit from IUnknown, the third entry in the vtable always points to the CCW’s Release method.
The trick is to create a delegate for that pointer using Marshal.GetDelegateForFunctionPointer. COM uses the stdcall calling convention, always passing a pointer to the COM object as the first argument on the stack, so the delegate’s signature can be “int SwizzledRelease(IntPtr pUnknown)” as long the method is static.
Marshal.ReadIntPtr(pUnknown) returns the address of the vtable, and Marshal.ReadIntPtr(pVtable, 2 * IntPtr.Size) returns the pointer to the CCW’s Release method. Calling Marshal.GetDelegateForFunctionPointer with that pointer will create a delegate that calls the CCW’s original Release method. An unmanaged pointer to a delegate of the SwizzledRelease method can be obtained by using Marshal.GetFunctionPointerForDelegate. Finally, the CCW’s vtable is modified by calling Marshal.WriteIntPtr(pVTable, 2 * IntPtr.Size, pSwizzledRelease). This concludes the pointer swizzling.
Now the SwizzledRelease method will be called whenever a COM consumer calls IUnknown::Release on the managed object. All that remains is for the SwizzledRelease method to call through the delegate of the CCW’s original Release method and eventually to return the result from it. In the meantime, the managed object can be obtained using Marshal.GetObjectForIUnknown. Et voila, if the reference count returned from the CCW’s original Release method is zero, the memory specified by the STGMEDIUM can now be released right on cue.
This was a fun little exercise in messing with COM vtables. I think for my next experiment I’ll try to bypass the CCW entirely by creating a vtable on the fly. This should actually be much safer to do, since I’ll own the entire thing, and won’t be messing with someone else’s memory.
>[Huh? If you still have a reference in the pUnkForRelease then your reference count can’t be zero. -Raymond]
Oh, I create a separate object, whose sole purpose is to free the memory on its final Release. It’s this object’s IUnknown pointer I specify in the pUnkForRelease.
Actually, I found it necessary to do it this way. It appears that the shell tries to unload itself pretty damn quick when a file open/save dialog is dismissed in an application. The extension’s DllCanUnloadNow is never called and the IShelExtInit’s Release is only called once, regardless of how many references are still held.
"Metanitpicker’s corner: Only a bug hunter would care."
A rather pointless statement since this post <i>started</i> with the phrase "When you’re debugging."
Well sure, when I’m debugging I make mistakes too, and I have to debug my debugging mistakes. If I thunk twice about subtracting 8 then I’d discover how pointless that dt statement was. I’d have to go back and subtract 8 just once instead of twice. Some call this kind of observation "nitpicking".
We reserved one DWORD dwSig for every class.
When you dump the adjacent memory you will know exactly which class it is.