myzhan

移植内存泄漏检测工具到鸿蒙系统

· myzhan

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,欢迎试用。