Linux嵌入式查找内存泄漏
LeakTracer
LeakTracer 是一个非常轻巧的内存泄露排查库。基本所有的内存泄露排查工具原理都一样,记录内存申请的现场信息,在内存释放处,将配对的现场记录删除掉。然后在程序结束或某个时间,打印出那些还尚未被移除的现场记录。
有时候有些程序,运行后,无法手动结束应用,即代码编写并不规范,没有考虑程序结束的处理情况。在使用 LeakTracer 的时候,可以选择带程序初始化完成延后一段时间,再开始记录内存申请释放。然后周期性的打印输出,查看是哪个位置内存泄漏可疑性非常大。
leaktracer_startMonitoringAllThreads();
while (!exit) {
std::this_thread::sleep_for(std::chrono::minutes(5)); // 这里最好可以更加细片化判断exit,以方便线程退出
leaktracer_writeLeaksToFile("leak_hhmmss.dump"); // 建议文件名添加时间戳便于分析和防止文件发生覆盖
}
leaktracer_stopAllMonitoring();
生成的 leak_hhmmss.dump
文件是一个文本文件,里面记录着内存申请时的函数调用栈,以及申请的内存大小,以及内存内容。LeakTracer 提供了一个 leak-analyze-addr2line
perl 脚本,其就是利用 addr2line 将记录的调用栈结合带有调试信息的可执行文件得到函数调用的代码出处,顺便统计这个调用栈在这个文件中出现的次数以及总共还存有多少释放的内存。
在使用 addr2line 过程中,有可能出现 ??:0
的打印,即分析不出代码行。出现这个问题的一个很大可能性就是,程序记录的是函数调用栈的内存地址,而 addr2line 需要的是相对偏移地址。我们在下文提及(参考 Boost.Stacktrace 提供的实现)。
valgrind
Valgrind 是内存检测非常强大的工具,它在 Linux 具有包管理器下,使用非常友好。但是在嵌入式场景下,使用就不是那么方便了。
因为其需要带调试信息的 glibc 动态库文件 libc.so.6
的库。 在具有包管理器的环境下,我们可以安装 libc6-dbg
。但是在嵌入式环境下就比较麻烦了,如果文件系统构建时,构建了调试版本的 libc.so.6
,那么拷贝至系统即可。
如果没有,那么需要我们自己找到对应版本的 glibc 源代码,然后交叉编译得到调试版本的 libc.so.6
,再将编译好的 libc.so.6
动态库拷进系统。
这一步,如果 glibc 版本号对不上或者其他原因,一不注意就会导致系统崩溃。
所以下文 交叉编译valgrind、交叉编译glibc 只是做记录使用。
交叉编译valgrind
sudo apt-get install automake
./configure --prefix=/opt/aarch64-v01c01-linux-gnu-gcc/lib/valgrind-3.22.0 --host=aarch64-linux-gnu --enable-only64bit
在将valgrind-3.22.0
部署到板子上后,需要将 libexec/valgrind
路径导出到 VALGRIND_LIB
变量:
export VALGRIND_LIB=/data/sdcard/valgrind-3.22.0/libexec/valgrind
然后执行:
valgrind --leak-check=yes danki