前言
整体逻辑与 anr 分析时差不多,也是监听各种信号,然后在信号到来时进行处理。入口函数是 xc_crash_init()
相较于 anr,crash 时监听的信号多很多,但统一的处理函数都是 xc_crash_signal_handler()
xc_crash_signal_handler
代码虽然很多,但整体上分为三部分
第一部分,fork() 前的准备。主要是通过 prctl() 设置一些属性,当然在方法的最后还会恢复这些属性,主要逻辑如下:
//set dumpable
orig_dumpable = prctl(PR_GET_DUMPABLE);
errno = 0;
// 修改当前调用进程/线程的某些属性
// 第一个参数指定要修改什么。类似于 ioctl() 方法,需要指明要执行的操作
if(0 != prctl(PR_SET_DUMPABLE, 1))
{}
//set traceable (disable the ptrace restrictions introduced by Yama)
//https://www.kernel.org/doc/Documentation/security/Yama.txt
errno = 0;
if(0 != prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY))
{
// ...
}
else
{
// ...
}
// 恢复属性的也是通过 prctl() 完成,省略
第二部分:fork 出子进程并在子进程中执行相应的 dump 操作,这一部分是主要是 xc_crash_fork() 方法 第三部分:父进程使用 waitpid() 等待子进程结束,同时会记录一些结束状态。关于状态的记录参考 blog.51cto.com/u_13999641/…
以上就是关于 crash 记录的大致流程,主要逻辑还是在 xc_crash_fork() 中
xc_crash_fork
主要流程如下:
最核心的逻辑就是调用了传入 xc_crash_fork 的函数 xc_crash_exec_dumper。如下:
xc_crash_exec_dumper
分片看。
第一段,禁止标准输出和标准错误
第二段,通过 pipe 将一些信息写入到标准输入中,主要为了方便子进程读取这些信息。这一部分
int pipefd[2];
errno = 0;
if(0 != pipe2(pipefd, O_CLOEXEC)) // 创建 pipe
{
return 92;
}
int write_len = ... ; // 省略计算
errno = 0;
// fcntl() 描述符操作函数
// 第二个参数指定要执行哪个操作,第三个是传入操作的参数
// 这行代码主要是用来设置了 pipe[1] 的 buffer 大小
if(fcntl(pipefd[1], F_SETPIPE_SZ, write_len) < write_len)
{
}
//write args to pipe
struct iovec iovs[12] = {}; // 省略要写入的值
int iovs_cnt = (0 == xc_crash_spot.dump_all_threads_whitelist_len ? 11 : 12);
errno = 0;
// 通过 writev 写入数据,最终通过 pipefd[0] 可以读取到写入的数据
ssize_t ret = XCC_UTIL_TEMP_FAILURE_RETRY(writev(pipefd[1], iovs, iovs_cnt));
if((ssize_t)write_len != ret)
{
}
// 标准输入重定向到 pipefd[0],因此通过标准输入就可以读取到上面写入的数据
XCC_UTIL_TEMP_FAILURE_RETRY(dup2(pipefd[0], STDIN_FILENO));
syscall(SYS_close, pipefd[0]);
syscall(SYS_close, pipefd[1]);
第三部分,执行真正的导出。fork() 后子进程与父进程基本一致,通过 execl() 相当于清空子进程,然后执行指定 so 的 main() 函数。这里就是执行 libxcrash_dumper.so 的 main() 函数
execl(xc_crash_dumper_pathname, XCC_UTIL_XCRASH_DUMPER_FILENAME, NULL);
main
整体分析过程需要 elf 文件,寄存器相关的知识,玩不明白。只记录一些收获 :
- 使用
ptrace(PTRACE_GETREGS, tid) 可获取线程寄存器 - 使用
ptrace(PTRACE_ATTACH, tid) 及 ptrace(PTRACE_DETACH, tid) 可暂停恢复线程 - 通过读取
/proc/pid/task目录可拿到进程的所有线程 id。注意:子进程需要传入父进程的 pid 才可拿到父进程的所有线程