崩溃日志收集框架 - native crash收集流程分析

一、基础铺垫

信号机制:


整个流程理解:

程序在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收集native奔溃信息流程

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流程

AMS走应用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]

[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代码的崩溃捕获机制及实现

你可能感兴趣的:(崩溃日志收集框架 - native crash收集流程分析)