【问题标题】:Is there a way to locate which part of the process used the most of the memory, only looking at a generated core file?有没有办法定位进程的哪一部分使用了最多的内存,只查看生成的核心文件?
【发布时间】:2023-05-20 02:27:01
【问题描述】:

我有一个进程(每次都由看门狗启动,但由于某种原因停止了),通常使用大约 200MB 内存。一旦我看到它正在消耗内存 - 内存使用量约为 1.5-2GB,这绝对意味着某处的“内存泄漏”(引号中的“内存泄漏”,因为这不是真正的内存泄漏 - 就像分配的内存,从未释放 且无法访问 - 请注意,只使用智能指针。所以,我想到了一些巨大的容器(我没有找到)或类似的东西)

后来,由于内存使用率高,进程崩溃了,并且生成了一个核心转储 - 大约 2GB。但问题是,我无法重现这个问题,所以valgrind 在这里无济于事(我猜)。它很少发生,我无法“抓住”它。

所以,我的问题是 - 有没有办法使用 exe 和核心文件来定位进程的哪个部分使用了大部分内存?

我用gdb看了一下core文件,没有什么异常。但是核心很大,所以肯定有东西。有没有聪明的方法来理解发生了什么,或者只是猜测可能会有所帮助(但是对于这么大的 exe..,12 个线程,大约 50-100 个(可能更多)类等)

这是一个C++ 应用程序,在 RHEL5U3 上运行。

【问题讨论】:

  • 考虑用你的 coredump 标签和 5 个关注者换取带有 xK 的 C++ 标签?追随者。祝你好运。
  • 我想过这个,但我想知道它是否正确。我试试看:)
  • @Kiril Kirov -- 试试 --outofcore.com/2011/06/scripted-debug-using-gdb(我自己没试过,所以发表评论
  • @Jayan - 谢谢,我会的。
  • 实际上,Valgrind 可以提供帮助。您将寻找未初始化变量的读取、空闲后读取等。类似的事情可能经常发生,尽管它很少会引发您的问题。

标签: c++ linux unix memory-leaks gdb


【解决方案1】:

以十六进制格式(如字节/字/双字/四字)打开此核心转储。从文件的中间开始尝试注意任何重复的模式。如果找到任何东西,尝试确定一些可能的数据结构的起始地址和长度。使用这个结构的长度和内容,尝试猜测它可能是什么。使用地址,尝试找到指向该结构的指针。重复直到到达堆栈或某个全局变量。如果是堆栈变量,您将很容易知道该链从哪个函数开始。如果是全局变量,你至少知道它的类型。

如果您在 coredump 中找不到任何模式,则可能是泄漏结构非常大。只需将您在文件中看到的内容与程序中所有大型结构的可能内容进行比较即可。

更新

如果您的 coredump 具有有效的调用堆栈,您可以从检查其功能开始。寻找任何不寻常的东西。检查调用堆栈顶部附近的内存分配是否没有太多请求。检查调用堆栈函数中可能存在的无限循环。

只使用智能指针”这句话吓到我了。如果这些智能指针的很大一部分是共享指针(shared_ptr、intrusive_ptr、...),那么与其搜索巨大的容器,不如搜索共享指针循环。

更新 2

尝试确定堆在核心文件中的结束位置(brk 值)。在 gdb 下运行 coredumped 进程并使用pmap 命令(来自其他终端)。 gdb 也应该知道这个值,但我不知道如何问它...如果大部分进程的内存在brk 以上,您可以通过大内存分配来限制您的搜索(最有可能,std::vector) .

为了提高在现有 coredump 的堆区域中发现泄漏的机会,可能会使用一些编码(我自己没有这样做,只是一个理论):

  • 读取 coredump 文件,将每个值解释为一个指针(忽略代码段、未对齐的值和指向非堆区域的指针)。对列表进行排序,计算相邻元素的差异。
  • 此时整个内存被分割成许多可能的结构。计算结构大小的直方图,删除所有无关紧要的值。
  • 计算指针和结构的地址差异,这些指针所属的位置。对于每个结构大小,计算指针位移的直方图,再次删除任何不重要的值。
  • 现在您有足够的信息来猜测结构类型或构建结构的有向图。查找此图的源节点和循环。您甚至可以将此图表可视化为"list “cold” memory areas"

Coredump 文件为elf 格式。从其标头中只需要数据段的开始和大小。为简化流程,直接将其读取为线性文件,忽略结构。

【讨论】:

  • 运行strings core也可能提供一些线索。
  • Whanks,我会尝试使用十六进制编辑器和strings core..但核心是巨大的 - 2GB :x 但感谢您的想法。关于调用堆栈 - 是的,每个线程都有很好的调用堆栈(11 个线程,项目很大),但没有什么可疑的:\ 关于指针 - 是的,再次,它们中的大多数是共享 ptrs,但我已经检查了他们,一切似乎也很好。我讨厌这样的问题,因为它们不能(很容易)重现:D 再次感谢 :)
  • strings 没什么意思 - 哈哈,大约 55 万行:D
  • @Kiril Kirov -- 试试 --outofcore.com/2011/06/scripted-debug-using-gdb(我自己没试过,所以发表评论
  • 谢谢,我会试试这个 - update2。 +1 努力:)
【解决方案2】:

一旦我看到它正在消耗内存 - 内存使用量约为 1.5-2GB

这通常是错误循环误入歧途的最终结果。比如:

size_t size = 1;
p = malloc(size);
while (!enough_space(size)) {
  size *= 2;
  p = realloc(p, size);
}
// now use p to do whatever

如果enough_space() 在某些情况下错误地返回 false,您的进程将迅速增长以消耗所有可用内存。

只使用智能指针

除非您控制所有链接到进程的代码,否则上述语句是false。错误循环可能位于 libc 或您不拥有的任何其他库中。

只有猜测可能会有所帮助

差不多就是这样。 Evgeny 的回答有很好的起点,可以帮助您猜测。

【讨论】:

  • 您的代码 sn-p 中最有趣的部分是无限循环。它可能会以多种方式导致内存泄漏(在 C 或 C+ 代码中)。而且很容易找到。
  • 没有这样的循环 - 大多数东西都是 STL。关于指针 - 不,我不控制进程的所有来源,但问题不太可能出现在 libcocci 或其他一些被广泛使用的第三方库中。当然,也不是不可能。谢谢:)
【解决方案3】:

普通的内存分配器不会跟踪进程的哪一部分分配了内存——毕竟,内存无论如何都会被释放,并且指针由客户端代码持有。如果内存真的泄漏了(即没有指向它的指针),那么您几乎已经丢失并且正在查看一大块非结构化内存。

【讨论】:

  • 嗯,我不得不在我的问题中提到,我会编辑。另外,我们只使用智能指针,所以如果分配的内存没有被释放,它仍然可以访问,这是肯定的。
【解决方案4】:

Valgrind 可能会发现几个可能的错误,值得一一分析。您需要创建一个抑制文件,并像这样使用它--suppressions=/path/to/file.supp。对于valgrind 标记的每个可能的错误,要么在抑制文件中添加一个子句,要么更改您的程序。

您的程序将在Valgrind 中运行较慢,因此事件发生的时间会有所不同,因此您无法确定是否会发生错误。

valgrind 有一个 GUI,叫做 Alleyoop,但我用的不多。

【讨论】:

  • 是的,我已经这样做了,没有任何可疑之处:\ 我将尝试使用此选项,看看会发生什么。谢谢。
  • 这并没有解决所提出的问题。