如何“发现”WinDbg 中引用计数句柄背后的 COM 对象?

ben*_*ruk 1 .net windbg

我正在通过 WinDbg DMP 文件调查内存泄漏。我发现有很多实例AmazeType在堆中,虽然它是一个很好的类型,有办法存在太多。我想知道谁在囤积它们。

!gcroot-ingAmazeType引导我进入“引用计数句柄”。这是有道理的,因为真棒类型列表通过 COM 可调用包装器存储在 COM 对象实例的属性中。

0:047> !gcroot 00000001c093e448
HandleTable:
    00000000015041a0 (ref counted handle)
    -> 00000001c0932fd8 System.Collections.Generic.List`1[[AmazeType, AmazeAssembly]]
    -> 00000001c0927ef8 BaseAmazeType[]
    -> 00000001c093e448 AmazeType
Run Code Online (Sandbox Code Playgroud)

可以用引用计数句柄做什么00000000015041a0来获取有关 RCW 和与之相关的 COM 对象的信息?如果没有,如何交替攻击问题?

小智 5

我没有在 64 位转储中完成此操作,只有 32 位转储,但希望这对您有所帮助。

(我意识到这对你来说可能为时已晚,但希望它会帮助其他人。)

根据我的经验,COM 对象到 .NET 对象的运行方式是这样的。因此,从 .NET 对象到 COM 对象是逆向过程的一种情况。我正在使用来自真实转储的示例,但经过审查和重命名。

从 COM 到 .NET 对象。

您的 COM 对象中有一个字段,可能如下所示:

+0x30 m_iTestObject    : ATL:CComPtr<ITestObject> { 0e645150 }
Run Code Online (Sandbox Code Playgroud)

这是间接指向 clr!Unknown_QueryInterface 吗?如果不是,那就不是 CCW:

0:000> dds poi(0e645150) L1
0631c090  07466260 clr!Unknown_QueryInterface
Run Code Online (Sandbox Code Playgroud)

好吧,这是一个 CCW。我们回到原始地址 0e645150,此时我们必须检查一些偏移量,因为它们似乎在 .NET 运行时之间有所不同。到目前为止,我发现的三个偏移量是 -0x0c、-0x10 和 -0x14。显然,在 64 位下偏移量可能不同,但是您可以看到尝试不同的偏移量然后针对结果运行 !mdt 应该很容易:

0:000> dds poi(0e645150-0x0c) L1
2506a4c0  00000000
0:000> dds poi(0e645150-0x10) L1
01785bd8  08acc744
0:000> dds poi(0e645150-0x14) L1
00000000  ????????
Run Code Online (Sandbox Code Playgroud)

我们现在可以对每个结果运行 !mdt (sosex) 或 !do。碰巧的是,其中只有一个实际上给了我们一个有效地址,即 08acc744:

0:000> !mdt 08acc744
08acc744 (TestAssembly.TestObject)
    requests:08accba8 (System.Collections.Generic.List`1[[TestAssembly.TestRequest, TestAssembly]])
    notifySink:08aee138 (TestAssembly.SinkWrapper)
Run Code Online (Sandbox Code Playgroud)

回溯到指向这个 .NET 对象的 COM 对象:

我们从 08acc744 处的同一个 .NET 对象开始并检查其引用。(您已经使用 !gcroot 查找 RefCounted 句柄):

0:000> !refs 08acc744
Objects referenced by 08ACC744 
  NONE

Objects that reference 08ACC744 (TestAssembly.TestObject):
  RefCounted Handle at 01785BD8, AppDomain=0456F158
Run Code Online (Sandbox Code Playgroud)

好的,所以我们有了手柄。什么指向它?

0:000> s -d 0x00000000 L?0xffffffff 01785bd8 
0e645140  01785bd8 2506a4c0 0630ec60 00000000  .[x....%`.0.....
Run Code Online (Sandbox Code Playgroud)

所以这个句柄是从 0e645140 指向的。我们知道,下一级不是直接指向这个地址,而是从这里指向一个偏移量。我们能找到任何指向这些偏移地址的东西吗?

0:000> s -d 0x00000000 L?0xffffffff 0e645140+0x0c
0:000> s -d 0x00000000 L?0xffffffff 0e645140+0x10 
27f90d68  0e645150 25d100e8 27fab0f8 00000000  PQd....%...'....
0:000> s -d 0x00000000 L?0xffffffff 0e645140+0x14 
Run Code Online (Sandbox Code Playgroud)

我们在 27f90d68 找到了一个指针,我们希望它是 COM 对象内的一个字段。让我们转储该地址周围的内存:

0:000> dds 27f90d68-0x40
27f90d28  0537f8b8
27f90d2c  00000000
27f90d30  559965bd
27f90d34  88000000
27f90d38  15ffe604 TestCore2!ATL::CComObject<CTestContainer>::`vftable'
27f90d3c  15ffe5e4 TestCore2!ATL::CComObject<CTestContainer>::`vftable'
27f90d40  00000002
27f90d44  0000000e
27f90d48  ffffffff
27f90d4c  0000000f
27f90d50  00000010
27f90d54  1dc721d8
27f90d58  00000000
27f90d5c  27d6ef80
27f90d60  262b1ec0
27f90d64  00000001
27f90d68  0e645150
27f90d6c  25d100e8
27f90d70  27fab0f8
27f90d74  00000000
27f90d78  559965b4
Run Code Online (Sandbox Code Playgroud)

我们可以看到一个带有 vftable 的对象,在我们的地址之前开始,所以希望我们的地址是该对象内的一个字段。(请注意,对象很可能在 vftable 条目之间存在间隙,例如 ATL 接收器指针之类的东西可以驻留,但碰巧这是对象的开始)。

0:000> dt 27f90d38  TestCore2!ATL::CComObject<CTestContainer>
   +0x000 __VFN_table : 0x15ffe604 
   =16010f38 _tih             : ATL::CComTypeInfoHolder
   +0x004 __VFN_table : 0x15ffe5e4 
   =16010f58 _tih             : ATL::CComTypeInfoHolder
   +0x008 m_dwRef          : 0n2
   +0x008 m_pOuterUnknown  : 0x00000002 IUnknown
   +0x00c m_nStateOrdinal  : 0n14
   +0x010 m_nKeyOrdinal    : 0n-1
   +0x014 m_nErrorCodeOrdinal : 0n15
   +0x018 m_nErrorTextOrdinal : 0n16
...
   +0x30 m_iTestObject    : ATL:CComPtr<ITestObject> { 0e645150 }
Run Code Online (Sandbox Code Playgroud)

这样我们就从 .NET 对象返回到指向它的 COM 对象。

我希望这很有用。