xCrash anr 分析

前言

anr 的初始化方法在 NativeHandler#initialize,该方法会调用 native 方法 nativeInit(),该方法在 native 层对应的是 xc_jni_init(),然后调用 xc_trace_init() 处理跟 anr 相关的逻辑

xc_trace_init

该方法主要逻辑就是下面三行

image.png

eventfd() 在 xcrash 中是用于线程通信。最下面说过,xcrash 会新开一线程用于 dump 日志,所以在发生 anr 时会通过 xc_trace_notifier 去唤醒该线程

xcc_signal_trace_register

它的主要逻辑很简单,就是通过 sigaction() 注册了对 sigquit 的信号处理

image.png

因此,当 sigquit 信号到来时会执行 xc_trace_handler。如下

image.png

我们在 xc_trace_init() 中说过,xcrash 会单起一线程用于 dump 日志,这个线程执行的就是 xc_trace_dumper() 方法。

xc_trace_dumper

这个方法比较长,分为几个部分看。

第一部分:关联到 jvm 上,用于回调一些 java 层的代码。由于当前线程是通过 pthread_create 在 native 层创建的,所以需要关联到 jvm 后才可以执行 java 层代码

image.png

第二部分,从 xc_trace_notifier 中读数据。最开始没数据时当前线程会被阻塞,只有 anr 发生时 xc_trace_handler 才会

Xnip2022-03-14_14-40-04.png

第三部分,回调 java 层,然后向日志文件中写入一些头信息。具体头信息有哪些,不重要,忽略。在头信息以后还会写入一些别的,截图中没有,但也不重要

image.png

第四部分,解析 libc++/libart 两个 so,拿到某些函数的地址。这里面涉及到 elf 文件的解析,后面再说

image.png

第五部分,真正的导日志。其实就也调用 Runtime::DumpForSigQuit(),里面涉及到了重定向

image.png

关于重定向 dup2 的使用可参考下面程序

int fd = open("/Users/xxx/Desktop/a.txt", O_WRONLY, 0666);
// 将标准输出重定向到 fd 中
dup2(fd, STDOUT_FILENO);
// 所以这个 cout 并不会在控制台上输出,而是输出到 a.txt 中
cout << "hello world---cout" < endl ;
复制代码

总结

上面就是 xCrash 关于 anr 日志收集的整个过程,总结一下:

  1. 自定义 sigquit 信号处理程序,有信号到来时会唤醒一个子线程去 dump 日志
  2. 通过调用 Runtime 中方法,并将结果输出到日志文件完成日志收集。这里就涉及到 elf 文件的解析,以及从 elf 文件中拿到自己想要的方法的地址。在 c/c++ 中,拿到函数地址后就可以直接调用,其实有点像 java 的反射。

补充

xc_trace_load_symbols

在整个流程中最关键的就是通过解析 elf 文件拿到想要的函数的地址。这个功能在 xc_trace_load_symbols() 中

image.png

上面的几个函数在解析时用的是一串别字母,这些字母的来源有待研究。其中 _ZN3art7Runtime14DumpForSigQuitERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEE 可以从 anr 日志信息中看出,其余几个不清楚具体来源。

看到这里,整个流程只有 xc_dl_create() 与 xc_dl_sym() 两个方法没有看过。前者是通过读取 /proc/self/maps 文件找到相应 so 在本进程的地址,后者是根据映射进来的 so 文件解析出指定的某些函数。

xc_dl_create

image.png

最后的 xc_dl_parse_elf() 就是解析出所有 elf 文件中所有感兴趣的 section 和 segment。这里面需要对 elf 文件足够了解,忽略

xc_dl_sym

根据 xc_dl_create 解析出指定函数的地址。还是需要配合着 elf 文件去理解,忽略。

猜你喜欢

转载自juejin.im/post/7074859179134943263
今日推荐