通过自定义SIGQUIT处理函数,收集Anr堆栈信息

2023-03-16  本文已影响0人  vb12

在android手机上, 当anr发生时系统会在/data/anr/trace.txt文件中留下堆栈信息(实际上是系统触发应用进程自己写入的).但这个文件对于没有root的手机是无法获取的, 这对于我们需要收集线上anr信息的需求来说, 是比较头疼的.

下面是一个比较常见的办法, 在native层通过自定义SIGQUIT信号处理函数, 来找到anr发生的切入时机, 手工收集线程堆栈, 并进行处理(存入本地文件,或者回传服务器).

原理

当应用进程发生anr时, System server进程(具体来说是ams)会向进程发送SIGQUIT信号, 让进程主动dump出线程堆栈信息到/data/anr/trace.txt. 这个文件对应用来说可写, 但不可读.
具体负责这个dump操作的线程是Signal Catcher线程.
我们在native层可以通过修改替换SIGQUIT的处理函数, 来得到控制权, 从而进行anr数据收集.

关键点

  1. 替换修改SIGQUIT处理函数
struct sigaction sa{};
    sa.sa_sigaction = signalHandler;
    sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART;
    // 设置一个新的sigaction
    if (sigaction(SIGQUIT, &sa, nullptr) == -1) {
        return false;
    }

signalHandler就是我们自定义的函数. 也就是我们拿到控制权后的代码执行入口

  1. 创建子线程
    为什么创建子线程, 是因为此时应用主线程被阻塞了, 无法回调java层jvm代码
std::thread javaCallbackThread(&JniSignalHandler::callJavaMethod, this, sig);
javaCallbackThread.detach();

一定记住新创建的子线程,必须先关联到jvm环境, 否则回调java代码会报错

   JNIEnv* env;
    jint attachResult = mJVM->AttachCurrentThread(&env, nullptr);
....
  1. 回调java层, 收集线程堆栈.
    当然也可以尝试在native层直接收集堆栈信息, 只是麻烦一点.

知识点

  1. 为什么主线程anr不响应了, 仍然可以执行信号处理函数
    当一个Android应用发生ANR时,主线程可能因为某些原因(如死锁、耗时操作等)处于阻塞状态。在这种情况下,主线程收到一个信号(如SIGQUIT),它通常仍然可以调用信号处理函数。

  2. 如果是在native层C++创建的线程, 是不能直接用来回调java代码的, 需要先AttachCurrentThread.

遗留问题

  1. 为什么Signal Catcher 可以得到所有线程的id(是linux线程的id,不是java thread的tid), 而我得不到, 还在想办法. 好像需要研究下art中对thread的实现
  2. 可能存在手机很卡, cpu占用高, 导致本应用没有来记得响应, 被认为anr了, 属于误伤的情况, 如何分别这总情况, 有说可以检查getProcessesInErrorState()返回的proc的condition是否被打标签认为是not respond了, 但试了下至少小米手机不行.

演示代码

https://github.com/shaopx/MyAPMTest

上一篇 下一篇

猜你喜欢

热点阅读