之前的文章,咱们讲了一般使用进程,如何捕获ANR的产生[监控] ANR捕获,这些关键你有必要知道。
今天这篇文章,咱们讲讲,当ANR产生后,咱们如何获取trace文件。
trace文件是分析ANR最重要的东西之一,可是高版别的Android体系约束了一般使用对data/anr下文件的读取权限,那么咱们怎样拿到trace文件呢?
现在干流的办法有两种,一种是手动调dumpForSigQuit办法,生成一份trace文件;另一种办法是hook trace文件的write接口,获取SignalCatcher线程生成的trace文件。
第一种办法会添加一次dump操作,形成不必要的开支;第二种办法,额外的开支十分小,是一种更轻量更好的办法。所以这篇文章,咱们只介绍第二种办法。
hook write接口
首要流程如下:
- 在收到
sigquit信号后,开始hook接口,首要hook connect/open和write接口。 - 当调用
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线程调用到connect或open办法时,会先调用到咱们的my_connect或my_open办法。
my_connect和my_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_connect和my_open办法首要流程:
- 判别当时翻开的文件是否为
/data/anr/traces.txt文件 - 如果是,则设置
isTraceWrite为true,记载当时的线程id为signalCatcherTid
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/open和write接口 - 将
content写入filePath的文件中。
总结
到这儿,经过hook write接口来获取trace文件的步骤就全部讲完了。
有几点需求注意:
-
hook操作最好放在子线程进行. -
使用
hook write获取到的trace信息,仅仅体系trace.txt文件的一部分。- 体系
trace.txt文件会包括许多进程的dump信息,首要有产生ANR的进程、system_server进程、以及资源消耗top5进程等。 - 咱们此处经过
hook write得到的trace,只包括咱们自己进程dump的信息,不包括其他进程。
- 体系
-
即便收到
sigquit信号,且能获取到trace信息,也不表示使用必定产生了ANR。- 有可能当时使用不是真实产生
ANR的使用,仅仅收到了sigquit信号开始dump信息而已。 - 使用收到
sigquit必定会开始dump trace信息,可是并不必定是产生了ANR。 - 要判别是否真的是当时使用产生了
ANR,还要根据主线程是否block,是否有errorState来判别当时使用是否真实产生了ANR。
- 有可能当时使用不是真实产生
