【问题标题】:_CrtMemDumpAllObjectsSince not returning expected results_CrtMemDumpAllObjectsSince 未返回预期结果
【发布时间】:2019-07-31 18:41:01
【问题描述】:

我正在使用 _CrtMemCheckpoint_CrtMemDumpAllObjectsSince 来跟踪我的 dll 中可能存在的内存泄漏。

在 DllMain 中,当检测到 DLL_PROCESS_ATTACH 时,会调用一个 init 函数,该函数会在全局 _CrtMemState 变量 startState 上调用 _CrtMemCheckpoint(&startState)。当检测到 DLL_PROCESS_DETACH 时,将调用一个退出函数,该函数调用 _CrtMemDumpAllObjectsSince(&startState)。这返回

ExitInstance()Dumping objects ->
{8706} normal block at 0x07088200, 8 bytes long.
 Data: <p v     > 70 FF 76 07 01 01 CD CD 
{8705} normal block at 0x07084D28, 40 bytes long.
 Data: <                > 00 00 00 10 FF FF FF FF FF FF FF FF 00 00 00 00 
{4577} normal block at 0x070845F0, 40 bytes long.
 Data: <dbV             > 64 62 56 0F 01 00 00 00 FF FF FF FF FF FF FF FF 
{166} normal block at 0x028DD4B8, 40 bytes long.
 Data: <dbV             > 64 62 56 0F 01 00 00 00 FF FF FF FF FF FF FF FF 
{87} normal block at 0x02889BA8, 12 bytes long.
 Data: < P          > DC 50 90 02 00 00 00 00 01 00 00 00 

到目前为止一切顺利,除了最后三个条目(4577、166 和 87)也在 startState 中。 IE。如果我在 Init 函数和 Exit 函数中运行 _CrtDumpMemoryLeaks(),这些条目都在两个列表中。

documentation 是这样说的:

_CrtMemDumpAllObjectsSince 使用 state 参数的值来确定在哪里启动转储操作。从开始倾倒 一个指定的堆状态,状态参数必须是一个指向 _CrtMemState 结构,在调用 _CrtMemDumpAllObjectsSince 之前已由 _CrtMemCheckpoint 填充。

这让我相信 startState 中跟踪的项目将被排除在输出之外。在调用 _CrtMemCheckpoint 的 Init 函数结束时,大约有 4700 次分配调用。 _CrtMemDumpAllObjectsSince 不应该只转储在该检查点调用之后分配的对象吗?

我错过了什么?

【问题讨论】:

  • 您是否考虑过只使用LeakSanitizer 而不是尝试手动推出劣质解决方案?
  • 哇,不,我没有考虑为我的 Windows DLL 使用 Linux 和 OS X 上支持的 Clang 工具
  • clang sanitizers 可以在 Windows 上使用。
  • 我所知道的就是我可以在这里阅读的内容:github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer,它非常明确地说明了支持的内容。无论哪种方式,将我现有的项目转换为新工具都不是一件容易的事。
  • 只是想帮忙。至于支持不同的工具,这通常是个好主意。在多个平台上使用多个编译器进行构建往往会清除大量错误 :)

标签: c++ c dll memory-leaks


【解决方案1】:


这显然很奇怪,但它(部分)完成了工作,但是以一种过分热心的方式。
这些功能已有数十年的历史了,所以不是错误的,但设计得并不完全。

事实上,“旧”状态中的某些东西在您的“自”状态之后会发生变化。
所以问题是“是的,它确实反映了自那以后的变化,但它是致命的泄漏吗?”
DLL 的延迟初始化频繁且放大。
还有很多复杂的对象,比如 map/string/array/list,它们会延迟内部缓冲区的分配。
坏消息是,几乎所有声明为“静态”的复杂对象实际上都是在首次使用时初始化的。

因此这些更改应该显示在_CrtMemDumpAllObjectsSince 中,因为它们更改了内存分配。
不幸的是,显示是如此粗糙且未经过滤,以至于它还显示了太多不相关的块(未修改)。

典型的最大罪魁祸首是使用“realloc”改变旧alloc的状态

这甚至可能看起来很奇怪,因为它们可能会消失,
例如,当一个真正的malloc 在 stat snapshoot 之后生成时,因为这个会做一种“重置”用于转储的低水位标记,将其设置为更高的级别。而这会神奇地让你的一堆“额外显示”消失。
如果你在做多线程,行为会更加不稳定,因为它很容易变得不重复。

注意:
它没有显示文件名和行号这一事实表明它在这里处理的是预初始化。
所以罪魁祸首很可能是在main()(或InitInstance();)之前启动的静态复杂对象

长:
_CrtMemDumpAllObjectsSince很痛苦!
事实上,无用信息可能如此混乱,以至于它违背了每天/每天简单使用_CrtMemDumpAllObjectsSince 的目的。 (它激发了美好的本质)
解决方法
不简单!
您可以尝试在执行“自”状态快照之后执行 malloc 然后释放,以便取笑此标记。
但为了更安全和更可控,不幸的是,我确实看到了自己编写自己的_MyCrtMemDumpAllObjectsSince 从原始 MS 结构转储的方法。 这是由static void __cdecl dump_all_object_since_nolock(_CrtMemState const* const state) throw() 启发的
(请参阅“debug_heap.cpp”)版权所有 Microsoft!

“按原样”提供的代码更能激发灵感。

但在对_CrtMemState 的工作方式进行一些解释之前:
_CrtMemState 状态确实有一个指针“pBlockHeader”,它通常是“_CrtMemBlockHeader*”的双链表的链接
这个列表实际上不仅仅是一次构建时的快照,而是对所有正在使用的内存块的选择(不清楚如何),其排列方式使得“当前状态”直接由 '@987654335 指向@'
就这样
-> _block_header_prev 允许探索旧区块
-> _block_header_next 允许探索较新的方块(您在“Since”概念中寻找的果汁,但非常危险,因为没有结束标记)

棘手的部分:
MS维护一个重要的内部静态_CrtMemBlockHeader*称为__acrt_first_block 这个__acrt_first_block 在分配和重新分配期间不断变化

但是,_MyCrtMemDumpAllObjectsSince 转储确实从这个 __acrt_first_block 开始并继续(使用 _block_header_next)直到找到一个 NULL ptr
处理的第一个块由这个__acrt_first_block 决定,您发送的“状态”不超过转储的停止。

否则,_CrtMemDumpAllObjectsSince 并没有真正转储“自”状态
但是从__acrt_first_block 转储到你的“since”状态。

“for”循环显示从“开始”(自)到“结束”(自以来最旧的修改)的块,这太过分了。
这是有道理的,但这也包括未修改的转储块。显示我们不关心的内容。

MS结构巧妙,可以直接使用,但不保证微软以后会保持同样的重要结构_CrtMemBlockHeader
但在过去的 15 年里,我没有看到它有任何变化(我也没有预见到他们会改变战略和关键结构的任何原因。)

我不喜欢复制/粘贴 MS 代码并使用我的搭载代码解析链接器
所以我使用的解决方法是基于拦截发送到“输出”窗口的文本消息的能力,解码所有信息并将其存储在我自己的银行中

下面的结构给出了使用锁定的静态结构来存储所有信息的截取概念

_CrtSetReportHook2(_CRT_RPTHOOK_INSTALL,MyReportHookDumpFilter);
_CrtMemDumpAllObjectsSince(state);  // CONTRARY to what it says, this thing seems to dump everything until old_state
_CrtSetReportHook2(_CRT_RPTHOOK_REMOVE,MyReportHookDumpFilter);
_MyReportHookDumpFilterCommand(_CUST_SORT,NULL);
_MyReportHookDumpFilterCommand(_CUST_DUMP,NULL);

`_MyReportHookDumpFilterCommand` 确实会检查是否存在根本未修改的块,并避免在其转储阶段显示这些块
把它作为代码的灵感来简化展示。

如果有人有更简单的使用方法,请分享!

【讨论】:

  • Kudo 回答了一个 2.5 岁的问题。我知道将来寻找此信息的人会很感激。
猜你喜欢
  • 2011-08-09
  • 2010-09-22
  • 2021-05-17
  • 2018-02-19
  • 2018-03-23
  • 2020-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多