一、基础铺垫
信号机制:
整个流程理解:
程序在cpu上运行的时候发生了错误,cpu发送中断指令,程序陷入内核,内核添加信号到进程的信号队列,之后程序转到用户态,进行信号检测,检查到信号队列中有新信号,进行信号处理。
用户态信号怎么处理?
应用程序被内核加载起来后,并不是先跑程序代码,而是先跑linker,linker初始化过程target进程注册信号处理函数。而通过System.loadLibrary加载的so文件,最终由linker链接到bionic,当native程序出现异常时,kernel会发送相应signal过来,target进程捕获signal,然后与debuggerd建立socket通信,交由debuggerd来执行相关dump操作。
注:
linker:Android系统加载动态链接的链接器。
debuggerd:一个init孵化的daemon进程,主要负责将进程运行时的信息dump到文件或者控制台中。
二、native crash收集流程
代码:android 6.0
2.1 信号处理函数注册:
应用程序入口begin.S通过linker初始化,调用debuggerd的debuggers_init执行信号处理函数的注册,在debuggers_signal_handler中通过sigaction,注册要接收的信号。同时通过send_debuggerd_packet建立target进程与debuggerd的socket通信连接。
注册点:
__LIBC_HIDDEN__ void debuggerd_init() {
struct sigaction action;
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_sigaction = debuggerd_signal_handler;
action.sa_flags = SA_RESTART | SA_SIGINFO;
// Use the alternate signal stack if available so we can catch stack overflows.
action.sa_flags |= SA_ONSTACK;
sigaction(SIGABRT, &action, NULL);//调用abort函数生成的信号,表示程序异常
sigaction(SIGBUS, &action, NULL);//非法地址,包括内存地址对齐出错,比如访问一个4字节的整数, 但其地址不是4的倍数
sigaction(SIGFPE, &action, NULL);//计算错误,比如除0、溢出
sigaction(SIGILL, &action, NULL);//执行了非法指令,或者试图执行数据段,堆栈溢出
sigaction(SIGPIPE, &action, NULL);//管道破裂,通常在进程间通信产生
sigaction(SIGSEGV, &action, NULL);//非法内存操作,与 SIGBUS不同,他是对合法地址的非法访问, 比如访问没有读权限的内存,向没有写权限的地址写数据
#if defined(SIGSTKFLT)
sigaction(SIGSTKFLT, &action, NULL);//协处理器堆栈错误
#endif
sigaction(SIGTRAP, &action, NULL);//断点时产生,由debugger使用
}
信号量定义:
signal.h
#define SIGHUP 1 // 终端连接结束时发出(不管正常或非正常)
#define SIGINT 2 // 程序终止(例如Ctrl-C)
#define SIGQUIT 3 // 程序退出(Ctrl-\)
#define SIGILL 4 // 执行了非法指令,或者试图执行数据段,堆栈溢出
#define SIGTRAP 5 // 断点时产生,由debugger使用
#define SIGABRT 6 // 调用abort函数生成的信号,表示程序异常
#define SIGIOT 6 // 同上,更全,IO异常也会发出
#define SIGBUS 7 // 非法地址,包括内存地址对齐出错,比如访问一个4字节的整数, 但其地址不是4的倍数
#define SIGFPE 8 // 计算错误,比如除0、溢出
#define SIGKILL 9 // 强制结束程序,具有最高优先级,本信号不能被阻塞、处理和忽略
#define SIGUSR1 10 // 未使用,保留
#define SIGSEGV 11 // 非法内存操作,与 SIGBUS不同,他是对合法地址的非法访问, 比如访问没有读权限的内存,向没有写权限的地址写数据
#define SIGUSR2 12 // 未使用,保留
#define SIGPIPE 13 // 管道破裂,通常在进程间通信产生
#define SIGALRM 14 // 定时信号,
#define SIGTERM 15 // 结束程序,类似温和的 SIGKILL,可被阻塞和处理。通常程序如 果终止不了,才会尝试SIGKILL
#define SIGSTKFLT 16 // 协处理器堆栈错误
#define SIGCHLD 17 // 子进程结束时, 父进程会收到这个信号。
#define SIGCONT 18 // 让一个停止的进程继续执行
#define SIGSTOP 19 // 停止进程,本信号不能被阻塞,处理或忽略
#define SIGTSTP 20 // 停止进程,但该信号可以被处理和忽略
#define SIGTTIN 21 // 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号
#define SIGTTOU 22 // 类似于SIGTTIN, 但在写终端时收到
#define SIGURG 23 // 有紧急数据或out-of-band数据到达socket时产生
#define SIGXCPU 24 // 超过CPU时间资源限制时发出
#define SIGXFSZ 25 // 当进程企图扩大文件以至于超过文件大小资源限制
#define SIGVTALRM 26 // 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
#define SIGPROF 27 // 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间
#define SIGWINCH 28 // 窗口大小改变时发出
#define SIGIO 29 // 文件描述符准备就绪, 可以开始进行输入/输出操作
#define SIGPOLL SIGIO // 同上,别称
#define SIGPWR 30 // 电源异常
#define SIGSYS 31 // 非法的系统调用
2.2 debuggerd收集奔溃信息
当发生crash时,kernel发送相应signal给target进程,因为target进程注册过了接收信号,因此能捕获signal进行处理,因为target进程与debuggerd建立了socket连接,此时会将action = DEBUGGER_ACTION_CRASH的消息发送给debuggerd服务端,debuggerd处理流程如下图:
debuggerd处理过程简析:
- 接收target进程发送过来的socket请求,fork子进程来处理任务。
- 子进程创建tombstone文件,并dump收集相关信息。包括设备基本信息、backtrace、stack、系统logcat等。
- socket到system_server进程走crash流程。
$:/data/tombstones # ls -al
-rw-r----- 1 tombstoned system 623950 2020-06-24 00:06 tombstone_00
-rw-r----- 1 tombstoned system 630274 2020-06-24 10:52 tombstone_01
一份tombstone文件内容:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
//基本信息
Build fingerprint: 'Xiaomi/cepheus/cepheus:10/QKQ1.190825.002/9.10.29:user/release-keys'
Revision: '0'
ABI: 'arm64'
Timestamp: 2020-06-24 00:06:13+0800
pid: 27232, tid: 27232, name: crashhandlerprj >>> com.stan.crashhandlerprj <<<
uid: 10258
//信号信息
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'FORTIFY: strcat: prevented write past end of 0-byte buffer'
x0 0000000000000000 x1 0000000000006a60 x2 0000000000000006 x3 0000007fe28391e0
x4 0000800000808080 x5 0000800000808080 x6 0000800000808080 x7 0000000000000018
...
backtrace:
#00 pc 0000000000073430 /apex/com.android.runtime/lib64/bionic/libc.so (abort+160) (BuildId: 084c8a81b8c78e19cd9a1ff6208e77cf)
#01 pc 0000000000099594 /apex/com.android.runtime/lib64/bionic/libc.so (__fortify_fatal(char const*, ...)+120) (BuildId: 084c8a81b8c78e19cd9a1ff6208e77cf)
#02 pc 000000000009a390 /apex/com.android.runtime/lib64/bionic/libc.so (__strcat_chk+84) (BuildId: 084c8a81b8c78e19cd9a1ff6208e77cf)
...
stack:
0000007fe2839140 0000007fe2839268 [stack]
0000007fe2839148 0000000000000000
0000007fe2839150 0000007fe28391b0 [stack]
...
memory near x1:
0000000000006a40 ---------------- ---------------- ................
0000000000006a50 ---------------- ---------------- ................
0000000000006a60 ---------------- ---------------- ................
...
memory map (2210 entries):
00000000'12c00000-00000000'12dbffff rw- 0 1c0000 [anon:dalvik-main space (region space)]
00000000'12dc0000-00000000'135fffff --- 0 840000 [anon:dalvik-main space (region space)]
00000000'13600000-00000000'13bbffff --- 0 5c0000 [anon:dalvik-main space (region space)]
...
//系统logcat
--------- tail end of log main
06-24 00:06:08.910 27232 27232 I FeatureParser: can't find cepheus.xml in assets/device_features/,it may be in /system/etc/device_features
06-24 00:06:08.916 27232 27232 E libc : Access denied finding property "ro.vendor.df.effect.conflict"
06-24 00:06:08.910 27232 27232 W crashhandlerprj: type=1400 audit(0.0:512880): avc: denied { read } for name="u:object_r:vendor_displayfeature_prop:s0" dev="tmpfs" ino=28719 scontext=u:r:untrusted_app:s0:c2,c257,c512,c768
...
//fd信息
open files:
fd 0: /dev/null (unowned)
fd 1: /dev/null (unowned)
fd 2: /dev/null (unowned)
fd 3: socket:[4540882] (unowned)
…
2.3 ActivityManagerService走应用crash流程
到ActivityManagerService就是走app的死亡流程了,包括crash信息写入dropbox,然后走死亡流程:handlerAppCrashLocked函数主要工作内容包括:杀进程、同时清理进程的四大组件,最终弹出crash对话框。 这部分与上一篇文章写的java crash流程一致。
$:/data/system/dropbox # ls -al
//java crash文件
-rw------- 1 system system 963 2020-07-03 16:28 [email protected]
//native crash文件 有两个
-rw------- 1 system system 12826 2020-07-03 16:28 [email protected]
-rw------- 1 system system 2449 2020-07-03 16:28 [email protected]
Process: com.stan.crashhandlerprj
PID: 29409
UID: 10258
Flags: 0x30e8bf46
Package: com.stan.crashhandlerprj v1 (1.0)
Foreground: Yes
Build: Xiaomi/cepheus/cepheus:10/QKQ1.190825.002/9.10.29:user/release-keys
java.lang.ArithmeticException: divide by zero
at com.stan.crashhandlerprj.MainActivity.onClick(MainActivity.java:39)
at android.view.View.performClick(View.java:7160)
at android.view.View.performClickInternal(View.java:7137)
at android.view.View.access$3500(View.java:810)
at android.view.View$PerformClick.run(View.java:27418)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:221)
at android.app.ActivityThread.main(ActivityThread.java:7540)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
而[email protected]和[email protected]的内容基本就是tombstone的内容。
至此native crash处理流程就分析完了。
参考:
理解Native Crash处理流程
debuggerd守护进程
Android平台Native代码的崩溃捕获机制及实现