翻译《The Old New Thing》- What is the underlying object behind a COM interface pointer?

53 篇文章 2 订阅
7 篇文章 0 订阅

What is the underlying object behind a COM interface pointer? - The Old New Thing (microsoft.com)icon-default.png?t=N7T8https://devblogs.microsoft.com/oldnewthing/20070424-00/?p=27143

Raymond Chen 2007年04月24日


COM接口指针背后的底层对象是什么?

简要

Raymond Chen在这篇文章中分享了在调试COM接口时确定底层对象的技巧,包括如何使用调试器命令和一些简单的数学来调整指针,以便正确地查看对象的内容。他还提到了一些在调试过程中应该注意的事项,比如vtable的位置、引用计数的值,以及字符串成员的内容。

正文

        当你在调试时,你可能有一个指向COM接口的指针,并想知道它的底层对象是什么。有时,这个技巧可能不奏效,因为接口指针实际上指向一个存根或代理,但在没有涉及封送(marshalling)的情况下,这个方法效果很好。(这个技术同样适用于许多C++编译器,对于任何具有虚方法和因此具有虚函数表(vtable)的对象。)

        回想一下,COM对象的布局要求指向COM接口的指针指向对象的vtable,而vtable是关键。

0:000> dv
            pstm = 0x000c7568
0:000> dt psf
Local var @ 0x7cc2c Type IStream*
0x000c7568
   +0x000 __VFN_table : 0x1c9c8e84

        到目前为止,我们只知道我们的IStream *位于0x000c7568,它的vtable是0x1c9c8e84。它是谁的流实现呢?

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

        啊哈,它是来自ABC.DLLCAlphaStream。让我们来看一下:

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

        “嘿,你是如何让调试器将m_pszName作为字符串转储的呢?” 如果你执行.enable_unicode 1命令,调试器将把指向unsigned short的指针视为指向Unicode字符串的指针。(默认情况下,只有指向wchar_t的指针被视为指向Unicode字符串的指针。)

        好的,回到结构转储。它看起来根本不对。 引用计数是一个荒谬的值,偏移0x00c处的vtable是一个错误的指针,m_pszName中的名字是垃圾,除了初始的vtable和偏移0x008处的vtable之外,几乎所有字段都明显是错误的。

        发生了什么? 显然,我们得到一个“q”指针;也就是说,指向除了第一个之外的某个vtable的指针。我们必须调整指针,使其指向对象的开头而不是中间。

        我们如何进行这种调整? 有一种系统化的方法和一种快速而简单的方法。

        系统化的方法是使用调整器(adjustor thunks)来告诉你需要将指针调整多少,以便从辅助vtable移动到主vtable。(这假设主IUnknown实现是第一个基类。这不一定是这样,但通常如此。)

0:000> dps 1c9c8e84 l1
1c9c8e84  1c9eb08e ABC![thunk]:CAlphaStream::QueryInterface`adjustor{8}'

        啊哈,这些调整器调整了八个字节,所以我们只需要从我们的指针中减去八个字节,就可以得到对象的起始地址。

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

        啊,这看起来好多了。注意,引用计数是一个更合理的值,两个,名字指针看起来不错,缓冲区大小和位置看起来现实多了。

        现在,我不会费心去处理整个调整器thunk的事情。相反,我依赖于“假设它大多是正确的”原则:假设对象没有损坏,只是通过眼睛调整指针,直到字段对齐。

        让我们再次看看原始的(错误的)转储:

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

        这显然不对,但我们该怎么做才能让事情对齐呢? 嗯,我们知道我们有的vtable必须进入其他两个vtable插槽中的一个,要么是偏移0x008的插槽,要么是偏移0x00c的插槽。 如果我们把它移到偏移0x00c,那么当前在偏移0x00c处的0x00000014将向下移动十二个字节,放在偏移0x018处,正好是m_pBuffer。但显然0x00000014不是有效的缓冲区指针,所以0x00c不可能是正确的调整。 另一方面,如果我们把我们的vtable放在偏移0x008,那么0x000c7844将移动到m_pBuffer的位置,这看起来不太不合理。因此,我猜调整器是八个,得到了我们通过转储vtable来查看调整器时得到的相同的结构转储。

        在现实生活中,我倾向于关注vtable、引用计数和任何字符串成员,因为通常很容易看出你是否正确地得到了它们。(vtable位于代码中。引用计数往往是小整数。字符串,嗯,字符串就是字符串。)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x0007

可不可奖励我吃只毛嘴鸡 馋😋

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值