编码实现内存泄漏检测功能
使用脚本统计 meminfo 判断是否有内存泄漏
- 使用 bash 或 python 脚本循环抓取指定进程的 meminfo 保存到 txt 文件;
- 使用 python 脚本解析出txt 文件中的 PSS 信息,借助 pyecharts 或其他可视化三方库将数据以折线图可视化;
优点:操作简单。缺点:没有检测结果返回。
具体方案 略
C++ 编码统计 meminfo 判断是否有内存泄漏
需要在待测试程序中添加代码。
整体分为三个部分:初始化,记录数据,统计数据;
初始化:
设置保存统计数据的路径,记录内存的次数以及保存折线图的间隔;
记录数据:
2.1 调用 dumpsys meminfo 接口获得内存信息;
2.2 使用异步线程将记录到的信息按照记录的间隔绘图存储(防止程序奔溃没有保存出数据);
2.3 每次保存数据时检查内存数据判断是否有泄漏;
获取内存泄漏的检查结果:
返回是否有内存泄漏的检查结果;
优点:可以控制记录内存泄漏的时机,避免记录大量重复的 meminfo;
缺点:内存数据以kb 返回,可能会遗漏小 size 的泄漏。不能定位泄漏的代码行。
具体方案 略
Malloc Hooks 自定义内存泄漏检测逻辑
Malloc Hooks 允许程序拦截执行期间发生的所有分配/释放调用。 它仅适用于 Android P 及之后的系统。它的流程和Malloc Debug 可以说基本上一样的,只是设置的属性名不一样。
有两种方法可以启用这些 hooks,设置系统属性或环境变量,并运行应用程序/程序。
adb shell setprop libc.debug.hooks.enable 1
或 export LIBC_HOOKS_ENABLE=1
初始化过程和 malloc debug 类似,只是判断的属性不同;在malloc hooks 的初始化函数中将从 libc_malloc_hooks.so 解析出来的函数symbol 都存放到 MallocDispatch;
// malloc_common_dynamic.cpp
static constexpr char kHooksSharedLib[] = "libc_malloc_hooks.so";
static constexpr char kHooksPrefix[] = "hooks";
static constexpr char kHooksPropertyEnable[] = "libc.debug.hooks.enable";
static constexpr char kHooksEnvEnable[] = "LIBC_HOOKS_ENABLE";
...
// Initializes memory allocation framework once per process.
static void MallocInitImpl(libc_globals* globals) {
...
// Prefer malloc debug since it existed first and is a more complete
// malloc interceptor than the hooks.
bool hook_installed = false;
if (CheckLoadMallocDebug(&options)) {
hook_installed = InstallHooks(globals, options, kDebugPrefix, kDebugSharedLib);
} else if (CheckLoadMallocHooks(&options)) {
hook_installed = InstallHooks(globals, options, kHooksPrefix, kHooksSharedLib);
}
if (!hook_installed) {
if (HeapprofdShouldLoad()) {
HeapprofdInstallHooksAtInit(globals);
}
} else {
// Record the fact that incompatible hooks are active, to skip any later
// heapprofd signal handler invocations.
HeapprofdRememberHookConflict();
}
}
系统调用 malloc 函数时实际会调用到 hooks_malloc() 中开发者自行实现的逻辑。
void* hooks_malloc(size_t size) {
if (__malloc_hook != nullptr && __malloc_hook != default_malloc_hook) {
return __malloc_hook(size, __builtin_return_address(0));
}
return g_dispatch->malloc(size);
}
官方示例
void* new_malloc_hook(size_t bytes, const void* arg) {
return orig_malloc_hook(bytes, arg);
}
auto orig_malloc_hook = __malloc_hook;
__malloc_hook = new_malloc_hook;
Malloc Hooks 自定义内存泄漏检测逻辑
更新__malloc_hook 和__malloc_free 指向新增函数;
bool hooks_initialize(const MallocDispatch* malloc_dispatch, bool*, const char*) {
g_dispatch = malloc_dispatch;
// __malloc_hook = default_malloc_hook;
__malloc_hook = cus_malloc_hook;
__realloc_hook = default_realloc_hook;
// __free_hook = default_free_hook;
__free_hook = cus_free_hook;
__memalign_hook = default_memalign_hook;
return true;
}
在新增函数内实现检测逻辑;
// static int allocated_count = 0;
static void* cus_malloc_hook(size_t size, const void* ) {
auto malloced_addr = g_dispatch->malloc(size);
// printf 可能会产生循环调用!
// printf("[malloc hooks] malloc %p size %zu at %p\n", malloced_addr, size, malloc_return_addr);
error_log("[malloc hooks] malloc %p size %zu\n", malloced_addr, size);
// allocated_count++;
return malloced_addr;
}
static void cus_free_hook(void* pointer, const void* ) {
// printf 可能会产生循环调用!
// printf("[malloc hooks] free %p, at %p\n", pointer, free_addr);
error_log("[malloc hooks] free %p\n", pointer);
// allocated_count--;
g_dispatch->free(pointer);
}
在 hooks_finalize 打印统计信息或者 dump 信息到文件。
在这里直接打印的话可能计数和预期不同,因为 malloc hooks 记录了整个程序执行过程中的申请和释放,是多于测试程序里面申请和释放的次数的。
void hooks_finalize() {
// error_log("allocated_count %d\n", allocated_count);
}
避坑
- apex/com.android.runtime/lib64/libc_malloc_hooks.so没有权限替换,放到 /data/local/tmp/ 路径下也没有权限读取。临时调试建议指定路径 kHooksSharedLib[] = “/system/lib64/libc_malloc_hooks.so“;
- 不要使用 printf 打印信息,会出现malloc/free 的循环调用,程序崩溃;
通过 dlsym 库函数对 malloc/free 进行 hook
方案:
- 通过 dlsym 拿到系统 malloc/free 函数,起个别名;
- 使用 atexit 注册退出时要调用的函数;
- 用自定义的 malloc/free 函数把 libc 的 malloc/free 包装一层;
- 程序中调用 malloc/free 时实际先调用自定义的函数,之后调用实际的 malloc/free。
在增加的函数中实现: - 每次调用 malloc 和 free 时打印地址;
- 使用一个变量记录已经申请但没有释放的数量;
- 程序退出时打印没有 free 的数量;
示例:
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef void* (*malloc_func_type)(size_t size);
typedef void (*free_func_type)(void* p);
malloc_func_type malloc_origin_ = NULL;
free_func_type free_origin_ = NULL;
int enable_malloc_hook = 1;
int enable_free_hook = 1;
static size_t allocate_cnt = 0;
void* malloc(size_t size) {
void* malloced_addr = NULL;
if (enable_malloc_hook) { // 避免 printf 循环调用
enable_malloc_hook = 0;
allocate_cnt++;
malloced_addr = malloc_origin_(size);
printf("malloc %p size %zu\n", malloced_addr, size);
enable_malloc_hook = 1;
}
return malloced_addr;
}
void free(void* p) {
if (enable_free_hook) {
enable_free_hook = 0;
printf("free [%p]\n", p);
enable_free_hook = 1;
}
allocate_cnt--;
free_origin_(p);
}
void finish() { printf("allocate_cnt %zu\n", allocate_cnt); }
void f(void);
void f(void) {
// printf("[memtest] function f\n");
int* x = (int*)malloc(10 * sizeof(int));
x[0] = 0;
int* y = (int*)malloc(5 * sizeof(int));
y[0] = 0;
free(x);
}
int main(void) {
// 获取系统默认的 malloc 和 free 函数
if (malloc_origin_ == NULL) {
malloc_origin_ =
reinterpret_cast<malloc_func_type>(dlsym(RTLD_NEXT, "malloc"));
}
if (free_origin_ == NULL) {
free_origin_ = reinterpret_cast<free_func_type>(dlsym(RTLD_NEXT, "free"));
}
// printf("[memtest] hello main\n");
f();
// 注册程序退出时调用的函数
atexit(finish);
return 0;
}
输出
$ ./memtest_dlsym
malloc 0x56127a995260 size 40
malloc 0x56127a995290 size 20
free [0x56127a995260]
allocate_cnt 1
总结
本文介绍了一些自行编码实现内存泄漏检测的工具的方式,但还有很多其他可行的方案本文没有一一涵盖,比如使用宏定义替换的方式,有兴趣的读者可以多探索一下。
参考链接
- Malloc Hooks (googlesource.com)