Upstart init 正在泄漏内存,您如何调试它?

Posted

技术标签:

【中文标题】Upstart init 正在泄漏内存,您如何调试它?【英文标题】:Upstart init is leaking memory, how do you debug it? 【发布时间】:2011-04-22 17:03:15 【问题描述】:

我在 Upstart 初始化进程 (pid 1) 中出现内存泄漏,我有哪些调试选项?

编辑:为此建议我一些真正的工具,手动放置 printfs 或手动计算内存分配不会削减它。还转储 init 核心并四处寻找并不是一个真正的选择。

UPD1: valgrind 不起作用。用适当的 valgrind + init 魔法替换内核命令行上的 /sbin/init 似乎不是一个选项,因为它试图访问 /proc for self 以获取 smap,但在运行 init 之前这些不可用。

UPD2: dmalloc 也不起作用(不在 ARM 上编译)。

【问题讨论】:

当然是 Valgrind。 :-P 如果这不是幽默,请用说明做出正确的回答,好的。 请不要太短。你有足够的空间来提问。 您还需要什么信息?它泄漏了,由于它的特殊性质,我没有选择如何调试它? 关于 Valgrind 不工作,也许你可以编写一个简单的程序来挂载 /proc 和 Valgrind 需要的任何其他设置,然后执行 Valgrind。 【参考方案1】:

穷人的解决方案是只记录每个电话 mallocfree,然后梳理日志并寻找模式。

ld 提供了一个很棒的功能,可以在这里提供帮助。

--wrap=symbol

对符号使用包装函数。对符号的任何未定义引用 将被解析为“__wrap_symbol”。任何未定义的引用 to "__real_symbol" 将被解析为符号。

这可以用来为系统函数提供一个包装器。这 包装函数应称为“__wrap_symbol”。如果它愿意 调用系统函数,应该调用“__real_symbol”。

这是一个简单的例子:

void *
__wrap_malloc (size_t c)

   printf ("malloc called with %zu\n", c);
   return __real_malloc (c);

如果您使用 --wrap malloc 将其他代码与此文件链接,则所有 调用“malloc”将改为调用函数“__wrap_malloc”。 在“__wrap_malloc”中对“__real_malloc”的调用将调用真正的 “malloc”函数。

您可能还希望提供一个“__real_malloc”函数,以便 没有 --wrap 选项的链接将成功。如果你这样做,你 不应将“__real_malloc”的定义放在同一个文件中 作为“__wrap_malloc”;如果你这样做了,汇编器可能会解析调用 在链接器有机会将其包装到“malloc”之前。


更新

只是想清楚这有什么用处。

将自定义文件添加到 Upstart 的构建中。

像这样:

void*__wrap_malloc( size_t c )

   void *malloced = __real_malloc(c);
   /* log malloced with its associated backtrace*/
   /* something like: <malloced>: <bt-symbol-1>, <bt-symbol-2>, .. */
   return malloced


void __wrap_free( void* addr )

   /* log addr with its associated backtrace*/
   /* something like: <addr>: <bt-symbol-1>, <bt-symbol-2>, .. */
   __real_free(addr);

使用调试符号 (-g) 重新编译新贵,这样您就可以获得一些不错的回溯。如果您愿意,您仍然可以优化 (-O2/-O3) 代码。

将 Upstart 与额外的 LD_FLAGS --wrap=malloc--wrap=free 链接。 现在,在 Upstart 调用 malloc 的任何地方,该符号都会神奇地解析为您的新符号 __wrap_malloc。美妙的是,这对编译后的代码是透明的,因为它发生在链接时。 就像 shimming 或 instrumenting 没有任何混乱。

像往常一样运行重新编译的 Upstart,直到您确定泄漏已经发生。

查看日志以查找不匹配的 malloceds 和 addrs。

几点说明:

--wrap=symbol 功能不适用于实际上是宏的函数名称。所以请注意#define malloc nih_malloc。这就是你需要使用 --wrap=nih_malloc__wrap_nih_malloc 的 libnih。 使用 gcc 的 builtin backtracing 功能。 所有这些更改只会影响重新编译的 Upstart 可执行文件。 您可以将日志转储到 sqlite 数据库,而不是这样可以更容易地找到不匹配的 malloc 和 free。 您可以让您的日志格式为 SQL 插入语句,然后只需将它们插入数据库事后分析以进行进一步分析。

【讨论】:

请注意,如果您不想在这里使用 printf,您可以分配一个缓冲区并将日志信息放入其中,然后在 init 完成后将其转储出来(或者只是戳进去在调试器中)。我希望理想的策略是确认每次运行的结果是一致的,找出日志的哪一行包含没有匹配空闲的 malloc,然后将 __wrap_malloc 设置为该行上的陷阱——此时,您可以查看调试器中的调用堆栈并找到有问题的调用。 Upstart 使用 libnih 进行所有结构和内存处理,其他一些组件也这样做。将一些日志记录连接到 nih 的 malloc/free 并没有成功:/ 在 Upstart 中包装所有 libnih 的 API 会带来很多麻烦,但我想我真的没有选择了。 您说“将一些日志记录连接到 nih 的 malloc/free 不会成功”,但为什么不呢?如果您可以详细说明问题,也许有人可以提出解决方案。 我已经告诉过:“和其他一些组件这样做是为了”-> 有多个 nih 用户正在运行,因此在 nih 中添加登录本身会产生混乱。因此,我需要将 nih 符号包装在 init 中,这更麻烦。 --wrap=symbol 只能用来弄乱初始化符号。它不会(不能)影响图书馆的其他用户。此外,将 libnih 包装在 init 中尽可能轻松。这就是 --wrap=symbol 的发明目的。查看我的更新了解更多详情。【参考方案2】:

如何在进程上运行 pmap 并检查哪些内存段正在增长。这可能会让你对什么是吃记忆有所了解。一点点脚本就可以使这个过程几乎是自动化的**。

** 在过去的生活中,我实际上编写了一个脚本,它会为一组间隔 t 秒的正在运行的进程拍摄 n 个 pmap 快照。其输出被输入到一个 perl 脚本中,该脚本识别了改变其大小的段。我用它来定位一些商业代码中的几个内存泄漏。 [我会分享这些脚本,但它们受前雇主的知识产权(版权)保护。]

约翰

【讨论】:

这种方法没用,它的堆在增长。【参考方案3】:

您可以尝试将您的新贵版本与Google's TCMalloc 链接。它带有一个内置的heap checker。

可以通过两种方式启用堆检查器:

将环境变量HEAPCHECK 设置为 normal |严格 |严厉的。 将HEAPCHECK 设置为local 并使用HeapProfileLeakChecker 对象手动检查代码。

我不知道如何为 init 设置环境变量。

【讨论】:

TCMalloc 在 ARM 上也不起作用:/ 不过感谢您的建议。【参考方案4】:

您可以通过挂钩 malloc/free 调用来自己检测内存分配,并计算每次分配和释放的字节数。

【讨论】:

【参考方案5】:

您也可以不加改动地使用 init,但创建一个将 MALLOC_CHECK 环境变量设置为 1 or higher 的包装器。这会让你看到一些内存分配诊断。

一种变体是稍微更改 init 源代码,以便在开始使用 malloc 之前尽早设置该环境变量。

您也可以按照 AmineK 的建议将调试代码添加到 init 源代码本身。

【讨论】:

以上是关于Upstart init 正在泄漏内存,您如何调试它?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Node.js 服务器调试内存泄漏

如何从托管 C# 代码跟踪 CRT 调试内存泄漏输出的来源?

VS 继续调试会话意味着内存泄漏,关闭应用程序后?

Linux如何调试内存泄漏

Linux如何调试内存泄漏

MFC内存泄漏调试