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】:穷人的解决方案是只记录每个电话 malloc
和 free
,然后梳理日志并寻找模式。
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,直到您确定泄漏已经发生。
查看日志以查找不匹配的 malloced
s 和 addr
s。
几点说明:
--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 正在泄漏内存,您如何调试它?的主要内容,如果未能解决你的问题,请参考以下文章