之前的文章,咱们讲了一般使用进程,如何捕获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
。
- 有可能当时使用不是真实产生