Android底层崩溃捕获机制

我们知道Java中的Crash我们一般可以通过try/catch进行捕获并获取具体的崩溃信息,然后进行处理。但是相对于Java Crash,Native Crash具有上下文不全,出错信息模糊,难以捕获等特点。这篇文章我们就来聊一聊如何在Android平台上捕获Native Crash。

相关文档

诊断原生代码崩溃问题

相关平台

Bugly
啄木鸟平台
●Firebase

相关方案

Google Breakpad
coffeecatch

基础概念

信号量机制

信号量是进程之间相互传递消息的一种方法,信号全部为软中断信号。

信号产生

硬件方式

●用户输入:Birkin在终端上按下Ctrl+C,产生SIGINT信号。
●硬件异常:例如CPU检测到非法的内存访问,通过内核产生相应信号,并发送给事件的进程。

软件方式

通过系统调用,产生Signal信号。

●Java:调用Process.sendSignal()等。
●Native
○kill():用于向进程或者进程组发送信号。
○sigqueue():只能向一个进程发送信号,不能向进程组发送信号,主要是针对实时信号,和sigaction()组合使用,当然也可以发送非实时信号。
○alarm():用于调用进程指定时间后,发送SIGALARM信号。
○settimer():设置定时器,及时达到后给进程发送SIGALRM信号,功能比alarm强大。
○abort():向进程发送SIGABORT信号,进程默认会异常退出。
○raise():用于向进程自身发出信号。
●Kernel:调用kill_proc_info()等。

信号分类

LInux系统共定义了64种信号,分为两大类:

●不可靠信号(非实时信号):取值区间1-31,不支持排队,信号可能会丢失,比如多次发送相同的信号,进程只能收到一次。
●可靠信号(实时信号):取值区间32-64,支持排队,信号不会丢失,发送多少次就可以收到多少次。

kill -l可以查看信号表。

不可靠信号

取值名称解释默认动作
1SIGHUP挂起
2SIGINT中断
3SIGQUIT退出
4SIGILL非法指令
5SIGTRAP断点或陷阱指令
6SIGABRTabort发出的信号
7SIGBUS非法内存访问
8SIGFPE浮点异常
9SIGKILLkill信号不能被忽略、处理和阻塞
10SIGUSR1用户信号1
11SIGSEGV无效内存访问
12SIGUSR2用户信号2
13SIGPIPE管道破损,没有读端的管道写数据
14SIGALRMalarm发出的信号
15SIGTERM终止信号
16SIGSTKFLT栈溢出
17SIGCHLD子进程退出默认忽略
18SIGCONT进程继续
19SIGSTOP进程停止不能被忽略、处理和阻塞
20SIGTSTP进程停止
21SIGTTIN进程停止,后台进程从终端读数据时
22SIGTTOU进程停止,后台进程想终端写数据时
23SIGURGI/O有紧急数据到达当前进程默认忽略
24SIGXCPU进程的CPU时间片到期
25SIGXFSZ文件大小的超出上限
26SIGVTALRM虚拟时钟超时
27SIGPROFprofile时钟超时
28SIGWINCH窗口大小改变默认忽略
29SIGIOI/O相关
30SIGPWR关机默认忽略
31SIGSYS系统调用异常

信号注册&注销

在进程task_struct结构体有个成员变量struct sigpending pending(信号集),每个信号在进程中都会把信号值注册到这个变量中,

●注册:非实时信号注册时,如果该信号已经注册过,则不会再次注册,该信号会丢失。实时信号始终会再次注册。
●注销:非实时信号不可重复注册,因此只有一个sigqueue结构,当该结构被释放后,把该信号从上述成员变量中删除,则信号注销完毕。实时信号需要把多个sigqueue结构处理完毕,才把该信号从上述成员变量中删除,则信号注销完毕,

信号处理

我们可以定义自己的信号处理函数来处理Native Crash发生时发出的信号,因为我们的信号处理函数是运行在用户空间的,因此要经历内核空间的调用,具体流程如下所示:

voldsighandler(int)

UserMode

intmaino

1.在执行主控制

流程的某条指令时

因为中断,异常或

系统调用进入内核

4.信号处理函数返回时

执行特殊的系统调用

sigretum再次进内核

2.内核处理完异常

sys_sigreturno

准备回用户模式之前

dosignaio

5.返回用户模式

先处理当前进程中

3.如果信号的处理

从主控制流程中

可以递递的信号

动作自定义的信号

上次被中断的地方

处理面数如回到用户

维绩向下热行

摸式热行信号处理

函数(而不是回到

KernelMode

主控制流程)

捕捉信号

捕获机制

Android的应用进程都是由Zygote进程fork出来的,Zygote在fork进程的时候会注册一系列的信号捕获方法。如下所示:

这里面有多个捕获类。

signal_catcher:它主要处理两类信号
○SIGQUIT:kill -3产生的信号,Runtime会dump fingerprint、ABI、Build type、线程调用栈、GC Profile、虚拟内存信息/proc/self/maps等,并产生/data/anr/traces.txt文件。
○SIGUSR1:kill -10产生的信号,Runtime会强制进行GC,并保存GC Profile信息。
fault_manager.cc:它主要处理SIG
sigchain.cc:处理其他信号,处理方式是打印平台信息、进程线程信息、寄存器信息、线程调用栈、虚拟内存信息等,这些信息可以在logcat日志上看到,也能在/data/data/tombsones中看到。

异常分析

Native Crash产生的原因:

●JNI内部数组越界、缓冲区溢出、空指针、野指针。
●JNI多线程竞争。
●Android ART出现异常
●Kernel出现异常

Native Crash的类型:

●Abort:一般是是Runtime通过libc主动进行的操作。
●空指针解引用:JNI代码出现空指针。
●低地址解引用:一般是结构体指针出现空指针,访问内部变量的偏移地址。
●栈破坏:内存越界,缓冲区溢出。

Native Crash产生后会生成日志文件

搜索*** ***定位崩溃日志的起始点。

设备信息

●Build fingerprint:版本号
●Revision:Revision 指的是硬件,而不是软件。通常情况下不使用 revision,但使用 revision 有助于自动忽略由不良硬件导致的已知错误。
●ABI:CPU型号,arm、arm64、mips、mips64、x86 或 x86-64 之一。

运行时信息

●pid:发生问题的进程id
●tid:发生问题的线程id
●name;发生问题的进程名

崩溃信号

●SIGABRT:崩溃产生的信号
●SI_TKILL:出现该信号的具体原因

崩溃消息

●并非所有崩溃问题都有崩溃消息行,这是从pid/tid的最后一行严重的logcat输出中自动收集而来的,在有意终止的情况下,这可以解释自行终止的原因。

崩溃堆栈

●显示具体发出错误的地方,和Java堆栈不同,Native堆栈只有Crash函数名而没有具体的行号信息。

在分析崩溃堆栈时,我们通常会用到以下工具:

●addr2line:把Crash地址信息转换为代码的行号。
●ndk-stack:可以对堆栈多次调用addr2line。这两个工具都依赖包含符号表的so,它包含了调试信息。当没有包含符号表的so时,我们可以借助objdump工具对so进行反汇编,分析汇编代码。
●objdump:对so进行反汇编得到汇编代码,主要在addr2line无法解决问题时使用。

自定义捕获

当我们需要捕获并处理信号时,有以下流程。

1 注册信号处理函数

sigaction()函数可以读取和修改与指定信号相关联的动作,相关参数如下:

●signo:信号编码,可以是除了SIGKILL或者SIGSTOP(为这两个信号定义处理函数会导致信号安装错误)之外的任何一个特定有效的信号。
●*act:指向结构体sigaction的一个指针,该实例指定了对特定信号的处理,如果设置为空,进程会执行默认处理。
●*oact:指向结构体sigaction的一个指针,该实例保存了原来对相应信号的处理。

sigaction结构体如下所示:

C++

1

structsigaction{

void(*sa_handler)(int); /* addr of signal handler, */

/* or SIG_IGN, or SIG_DFL */

sigset_tsa_mask; /* additional signals to block */

intsa_flags; /* signal options, Figure 10.16 */

/* alternate handler */

void(*sa_sigaction)(int,siginfo_t *,void *);

};2 设置额外栈空间,因为SIGSEGV很可能是栈溢出引起的,如果在默认的栈上运行可能会破坏程序的运行现场,无法读取到正确的上下文。另外如果栈满了,系统在这个栈上再次调用SIGSEGV处理函数,会再次触发SIGSEGV信号。因而一般会开辟一块新的空间作为运行信号处理函数的栈。

C++

#include<signal.h>

intsigaltstack(conststack_t *ss,stack_t *oss);
3 兼容其他signal的处理,某些信号之前可能已经注册过信号处理函数,我们再次注册会覆盖原来的处理函数,因而当我们的信号处理函数执行完成以后,需要重新执行已有的信号处理函数。

自定义捕获Q&A

Q:文件句柄泄漏,导致创建日志文件失败,如何处理?

提前申请文件句柄预留,以防止出现这种情况。

Q:栈溢出了,导致日志生成失败,如何处理?

使用sigalstack创建新的栈执行信号处理函数,在一些特殊情况,我们可能还需要直接替换当前栈,所在也需要在堆中预留部分空间。

Q:堆内存耗尽,导致日志生成失败,如何处理?

这个时候无法安全的分配内存,也无法直接调用stl或者libc函数,因为它们的内部实现会分配堆内存,如果继续分配内存会导致堆破坏或者二次崩溃的情况,Breakpad重新封装了Linux Syscall Support来避免直接调用libc。

Q:堆破坏或者二次崩溃,导致日志生成失败,如何处理?

这种情况下,Breakpad会从原进程fork出子进程去采集崩溃线程,此外涉及Java相关的也会由子进程去操作,这样即便出现二次崩溃,只是这部分信息丢失,我们的父进程后面还可以继续获取其他信息,在一些特殊情况,我们还可能需要从子进程fork出孙进程。

文章来源于互联网:https://www.yuque.com/beesx/beesandroid/vggvzg

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=18196,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?