April 24th, 2007

What is the underlying object behind a COM interface pointer?

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

Topics
Other

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.