What is the underlying object behind a COM interface pointer?

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 IStream * lives at 0x000c7568 and its vtable is 0x1c9c8e84. Whose stream implementation is it?

0:000> ln 0x1c9c8e84
(1c9c8e84)   ABC!CAlphaStream::`vftable'

Aha, it's a CAlphaStream from ABC.DLL. Let's take a look at it:

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 m_pszName as a string?" If you issue the .enable_unicode 1 command, then the debugger will treat pointers to unsigned short as if they were pointers to Unicode strings. (By default, only pointers to wchar_t are treated as pointers to Unicode strings.)

Okay, back to the structure dump. It doesn't look right at all. The reference count is some absurd value, the vtable at offset 0x00c is a bogus pointer, the name in m_pszName is garbage, pretty much every field aside from the initial vtable and the vtable at offset 0x008 is blatantly wrong.

What happened? Well, clearly we were given a "q" pointer; i.e., a pointer to one of the vtables other than the first one. We have to adjust the pointer so it points to the start of the object instead of the middle.

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 IUnknown implementation is the first base class. This is not guaranteed to be the case but it usually is.)

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 0x008 or the one at offset 0x00c. If we moved it to offset 0x00c, then that would move the 0x00000014 currently at offset 0x00c down twelve bytes, placing it at offset 0x018, right at m_pBuffer. But obviously 0x00000014 is not a valid buffer pointer, so 0x00c can't be the correct adjustment. On the other hand, if we put our vtable at offset 0x008, then that would move 0x000c7844 into the m_pBuffer position, which is not too unreasonable. Therefore, I would guess that the adjustor is eight, yielding the same structure dump that we got by dumping the vtable to see the adjustor.

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)
  1. MS says:

    Wow, thanks for elucidating this stuff!  

  2. mikeb says:

    Great stuff – this has been Ctrl-C/Ctrl-V’ed right into my debugging notes file.

  3. Metathunk says:

    0x000c7568

    we just need to subtract eight

    0x000c7560-8

    This obviously doesn’t smell right

    Metanitpicker’s corner:  Only a bug hunter would care.

  4. 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 ;).

  5. MadQ says:

    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.

    [Huh? If you still have a reference in the pUnkForRelease then your reference count can’t be zero. -Raymond]

    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.

  6. MadQ says:

    >[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.

    [That separate object increments your DLL object count, so it can’t be zero. And that aggressive unload isn’t coming from the shell; it’s coming from CoUninitialize. -Raymond]
  7. Rick C says:

    "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."

  8. Metadebugger says:

    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".

  9. We reserved one DWORD dwSig for every class.

    When you dump the adjacent memory you will know exactly which class it is.

    [This works great as long as you don’t interoperate with components written by other people (e.g., you never call CoCreateInstance). -Raymond]

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index