如何“发现”WinDbg中ref计数句柄后面的COM对象?

时间:2017-08-09 14:46:28

标签: .net windbg

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

!gcroot - ing AmazeType引导我进入“ref计数句柄”。这是有道理的,因为awesome类型列表通过COM Callable Wrapper存储在COM对象实例的属性中。

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

如何使用ref计数句柄00000000015041a0来获取有关RCW及其相关COM对象的信息?如果没有,怎么可能交替攻击这个问题?

1 个答案:

答案 0 :(得分:1)

我尚未在64位转储中完成此操作,仅在32位转储中完成了此操作,但希望对您有所帮助。

(我意识到这对您来说可能为时已晚,但希望对您有所帮助。)

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

从COM到.NET对象。

您的COM对象中包含一个字段,该字段可能看起来像这样:

+0x30 m_iTestObject    : ATL:CComPtr<ITestObject> { 0e645150 }

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

0:000> dds poi(0e645150) L1
0631c090  07466260 clr!Unknown_QueryInterface

好的,这是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  ????????

我们现在可以对每个结果运行!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)

追溯到指向此.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

好的,所以我们有了手柄。这说明了什么?

0:000> s -d 0x00000000 L?0xffffffff 01785bd8 
0e645140  01785bd8 2506a4c0 0630ec60 00000000  .[x....%`.0.....

因此该句柄从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 

我们在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

我们可以看到一个对象,该对象的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 }

因此,这使我们从.NET对象一直回到指向它的COM对象。

我希望这很有用。