[ANR监控] 通过write接口获取trace信息
之前的文章,我们讲了普通应用进程,如何捕获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
。
- 有可能当前应用不是真正发生