目录

dl_iterate_phdr 的 ANR 问题

最近一直在修改一个库,这个库是基于字节开源的 memory-leak-detector 修改的

由于近期增加了一系列的 hook,hook 方法众多,且几乎全量 hook 了所有 so,导致启动时会有 ANR 发生

ANR 原因说明

发生 ANR 的核心原因比较神奇。

背景

memory-leak-detector 使用的 xhook 作为 native hook(可能当时还没有 bhook)

xhook 一个比较不智能的地方,就是无法自动 hook 后续加载的 so

于是,memory-leak-detector  使用了 xdl(hook dlopen) + xhook 组合的方式,实现了这个功能。但是,问题很大。

原因

dl_iterate_phdr 是一个底层方法,他可以获取到so基址

xdl 包装了一层,使用 xdl_iterate_phdr,同样允许传入 callback,在 callback 循环中获取到了 so 基址,生成 elf 结构(其实也就是获得了所有地址),进行函数替换(hook过程),同时需要修改这片内存为可写

问题就发生在这里

通过读取 /proc/self/maps 来获取当前内存地址的状态并修改为可写,但是 memory-leak-detector 的逻辑是:

xdl_iterate_phdr(dl_iterate_callback, NULL, XDL_FULL_PATHNAME);

// 循环每个内存中的 so 依次回调
int dl_iterate_callback(dl_phdr_info *info, size_t size, void *data) {
    if (info == NULL) {
        return 0;
    }
    return common_callback(info->dlpi_name, get_so_base(info), data);
}

// 没什么细节
int common_callback(const char *name, uintptr_t base, void *data) {
    int ret = 0;
    // 省略
    ret = default_callback(name, base);
   	// 省略
    return ret;
}

int default_callback(const char *name, uintptr_t base) {
	// 省略
    tryHookAllFunc(elf);
    // 省略
    return 0;
}

static void tryHookAllFunc(xh_elf_t elf) {
    for (int i = 0; i < sizeof(sPltGot) / sizeof(sPltGot[0]); i++) {
		// 问题就在这里,xh_elf_hook 每次调用均会触发一次 proc/self/maps 的 IO 操作,这部分属于 xhook 的源码
        xh_elf_hook(&elf, (const char *) sPltGot[i][0], (void *) sPltGot[i][1], NULL);
    }
}

于是,整个过程,读取 proc/self/maps 的次数 = o 个数 * hook 方法个数,机器越低端越漫长,最终 ANR。

既然耗时,移动到子线程是否可以呢不可以

dl_iterate_phdr 带一个 linker 的全局锁,影响范围很大,activity 也会无法跳转。

如何解决

添加缓存层

我们其实只是关注 so 相关的内存状态,proc/self/maps 在没有新的 so 被加载的时候,已经被加载进内存的 so,地址没有任何变化,那我们就可以进行一次缓存。

在启动时,延迟一段时间初始化 nativedump,这个时候,so 的加载分为两种情况

  • 此时 so 已经被加载
    这种情况,就很简单,共用同一份当前时刻的 maps 的缓存,只要进行一次真实读取
  • 此时 so 还未被加载  
    这种情况,需要刷新缓存。但是,我们已经延迟了初始化,所以,未被加载的 so,应该是零星几个,分别在不同时间触发几次真实 IO,是可以接受的

这样,就解决了问题,实际测试,会有轻微的掉帧(因为无论如何 dl_iterate_phdr 都是带锁的,在读取 maps 时都会阻塞)

实现

一开始在尝试,自己进行解决,后来发现 matrix 同样发现了这个问题,且对于 xhook 做了很多修修补补:

可以去查阅 matrix 的 xhook 改进版

Introduce maps and use it for replacing parsing maps multiple times.
8877ece7 tomystang tomystang@tencent.com