之前的文章,咱们讲了一般使用进程,如何捕获ANR的产生[监控] ANR捕获,这些关键你有必要知道。

今天这篇文章,咱们讲讲,当ANR产生后,咱们如何获取trace文件。

trace文件是分析ANR最重要的东西之一,可是高版别的Android体系约束了一般使用对data/anr下文件的读取权限,那么咱们怎样拿到trace文件呢?

现在干流的办法有两种,一种是手动调dumpForSigQuit办法,生成一份trace文件;另一种办法是hook trace文件的write接口,获取SignalCatcher线程生成的trace文件。

第一种办法会添加一次dump操作,形成不必要的开支;第二种办法,额外的开支十分小,是一种更轻量更好的办法。所以这篇文章,咱们只介绍第二种办法。

hook write接口

首要流程如下:

  • 在收到sigquit信号后,开始hook接口,首要hook connect/openwrite接口。
  • 当调用connect/open办法时,判别是否为trace文件,如果是则记载当时的线程id(此线程为SignalCatcher线程)
  • 当调用到write接口时,判别是否为上一步记载的SignalCatcher线程,如果是,则标识此刻是trace文件的写入,将buffer的内容写入咱们的trace文件。

下面贴一下具体的源码

1. hook connect/open 和 write接口

void hookAnrTraceWrite(bool isSiUser) {
    int apiLevel = getApiLevel();
    if (isHooking) {
        return;
    }
    isHooking = true;
    if (apiLevel >= 27) {
        xhook_grouped_register(HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE, ".*libcutils\\.so$",
                               "connect", (void *) my_connect, (void **) (&original_connect));
    } else {
        xhook_grouped_register(HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE, ".*libart\\.so$",
                               "open", (void *) my_open, (void **) (&original_open));
    }
    if (apiLevel >= 30 || apiLevel == 25 || apiLevel == 24) {
        xhook_grouped_register(HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE, ".*libc\\.so$",
                               "write", (void *) my_write, (void **) (&original_write));
    } else if (apiLevel == 29) {
        xhook_grouped_register(HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE, ".*libbase\\.so$",
                               "write", (void *) my_write, (void **) (&original_write));
    } else {
        xhook_grouped_register(HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE, ".*libart\\.so$",
                               "write", (void *) my_write, (void **) (&original_write));
    }
    xhook_refresh(true);
}

2. connect/open办法

SignalCatcher线程调用到connectopen办法时,会先调用到咱们的my_connectmy_open办法。

my_connectmy_open的流程类似,此处拿my_open办法举例。

int my_open(const char *pathname, int flags, mode_t mode) {
    if (pathname!= nullptr) {
        if (strcmp(pathname, HOOK_OPEN_PATH) == 0) {
            signalCatcherTid = gettid();
            isTraceWrite = true;
        }
    }
    return original_open(pathname, flags, mode);
}

my_connectmy_open办法首要流程:

  • 判别当时翻开的文件是否为/data/anr/traces.txt文件
  • 如果是,则设置isTraceWritetrue,记载当时的线程idsignalCatcherTid

3. write办法

当调用到write办法时,会先调用到咱们的my_write办法里。

ssize_t my_write(int fd, const void* const buf, size_t count) {
    if(isTraceWrite && gettid() == signalCatcherTid) {
        isTraceWrite = false;
        signalCatcherTid = 0;
        if (buf != nullptr) {
            if (!targetFilePath.empty()) {
                char *content = (char *) buf;
                writeAnr(content, targetFilePath);
                anrDumpTraceCallback();
            }
        }
    }
    return original_write(fd, buf, count);
}

my_write办法首要流程:

  • 判别isTraceWrite是否为true,以及调用write的线程是否为signalCatcherTid线程
  • 如果是,则将buffer中的内容,调用writeAnr办法写入targetFilePath的文件中
  • 调用anrDumpTraceCallback继续后边的上报等流程

writeAnr办法:

void writeAnr(const std::string& content, const std::string &filePath) {
    unHookAnrTraceWrite();
    std::string to;
    std::ofstream outfile;
    outfile.open(filePath);
    outfile << content;
}

writeAnr办法首要流程:

  • unhoook connect/openwrite 接口
  • content写入filePath的文件中。

总结

到这儿,经过hook write接口来获取trace文件的步骤就全部讲完了。

有几点需求注意:

  1. hook操作最好放在子线程进行.

  2. 使用hook write获取到的trace信息,仅仅体系trace.txt文件的一部分。

    • 体系trace.txt文件会包括许多进程dump信息,首要有产生ANR的进程、system_server进程、以及资源消耗top5进程等。
    • 咱们此处经过hook write得到的trace,只包括咱们自己进程dump的信息,不包括其他进程。
  3. 即便收到sigquit信号,且能获取到trace信息,也不表示使用必定产生了ANR

    • 有可能当时使用不是真实产生ANR的使用,仅仅收到了sigquit信号开始dump信息而已。
    • 使用收到sigquit必定会开始dump trace信息,可是并不必定是产生了ANR
    • 要判别是否真的是当时使用产生了ANR,还要根据主线程是否block,是否有errorState来判别当时使用是否真实产生了ANR