移植内存泄漏检测工具到鸿蒙系统
memory-leak-detector 是字节开源的一款上基于 hook 来实现的 Android 内存泄漏检测工具。 最近在鸿蒙 Next 系统上遇到一些资源泄漏的问题,于是萌生了把这个工具移植到鸿蒙 Next 系统上的想法,丰富一下工具库。考虑到 Android 和鸿蒙都是 基于 Linux,这个移植应该是行得通的。移植的策略,就是先把工程搭起来,编译通过。遇到错误,如果是 Android 特有的,就注释掉,如果是功能必须的,则修改代码解决。
先说一下 memory-leak-detector 的原理。它利用了多种函数 hook 技术,在运行时将分配内存的函数,跳转到代理函数,在代理函数里面调用实际的内存分配函数,如果分配成功, 则以分配的内存地址为 key,记录当前的堆栈信息。同样,它也 hook 了对应的释放内存的函数,跳转到代理函数,然后删除堆栈记录。如果一块内存只有分配没有释放,那它的分配 堆栈会一直存在记录里面,在输出报告的时候会输出。从原理也能知道,报告里面输出的,并不一定是泄漏,也有可能只是还没到释放时间而已,所以报告要结合实际代码进行分析确认。
malloc 代理函数的例子:
static void *malloc_proxy(size_t size) {
if (isPss && size >= limit && !(uintptr_t) pthread_getspecific(guard)) {
pthread_setspecific(guard, (void *) 1);
// malloc_origin 是实际的内存分配函数
void *address = malloc_origin(size);
if (address != NULL) {
// 如果分配成功,则记录当前的堆栈
insert_memory_backtrace(address, size);
}
pthread_setspecific(guard, (void *) 0);
return address;
} else {
// 不满足配置条件,不记录,直接分配
return malloc_origin(size);
}
}
free 代理函数的例子:
static void free_proxy(void *address) {
if ((isVss | isPss) && address && !(uintptr_t) pthread_getspecific(guard)) {
pthread_setspecific(guard, (void *) 1);
// 释放内存
free_origin(address);
// 删除记录
cache->remove((uintptr_t) address);
pthread_setspecific(guard, (void *) 0);
} else {
// 不满足配置条件,直接释放
free_origin(address);
}
}
根据配置需求的不同,memory-leak-detector 使用了两种 hook 机制,分别是 Inline Hook 和 PLT Hook。与 hook 有关的机制不在这里展开说了, Native Hook 技术,天使还是魔鬼?一文有非常详尽的描述。
接下来,分析一下 Android 和鸿蒙的差异。从鸿蒙 Next 开始,已经完全删除掉 Android Framework 层,可以认为它们已经是两个系统。 那跟内存泄漏检测有关的,主要有哪些差异会影响呢?
组件 | Android | 鸿蒙 | 备注 |
---|---|---|---|
CPU | arm32 和 arm64 | 只有 arm64 | 移植过程可以删掉 arm32 部分的代码 |
C 标准库 | bionic | 鸿蒙 Fork 的 musl | Fork 仓库地址 |
FFI | Java JNI | JS NAPI | 对外暴露接口需要 |
实际移植下来,用到的 xDL,xHook 和 And64InlineHook 等库兼容性都不错。大部分的工作,都只是在处理 Android 的 bionic 和鸿蒙的 musl 的差异。
折腾两天,初步跑通了,放在 myzhan/HarmonyLeaksDetector,欢迎试用。