我们知道Java中的Crash我们一般可以通过try/catch进行捕获并获取具体的崩溃信息,然后进行处理。但是相对于Java Crash,Native Crash具有上下文不全,出错信息模糊,难以捕获等特点。这篇文章我们就来聊一聊如何在Android平台上捕获Native Crash。
相关文档
相关平台
相关方案
基础概念
信号量机制
信号量是进程之间相互传递消息的一种方法,信号全部为软中断信号。
信号产生
硬件方式
●用户输入: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可以查看信号表。
不可靠信号
取值 | 名称 | 解释 | 默认动作 |
1 | SIGHUP | 挂起 | |
2 | SIGINT | 中断 | |
3 | SIGQUIT | 退出 | |
4 | SIGILL | 非法指令 | |
5 | SIGTRAP | 断点或陷阱指令 | |
6 | SIGABRT | abort发出的信号 | |
7 | SIGBUS | 非法内存访问 | |
8 | SIGFPE | 浮点异常 | |
9 | SIGKILL | kill信号 | 不能被忽略、处理和阻塞 |
10 | SIGUSR1 | 用户信号1 | |
11 | SIGSEGV | 无效内存访问 | |
12 | SIGUSR2 | 用户信号2 | |
13 | SIGPIPE | 管道破损,没有读端的管道写数据 | |
14 | SIGALRM | alarm发出的信号 | |
15 | SIGTERM | 终止信号 | |
16 | SIGSTKFLT | 栈溢出 | |
17 | SIGCHLD | 子进程退出 | 默认忽略 |
18 | SIGCONT | 进程继续 | |
19 | SIGSTOP | 进程停止 | 不能被忽略、处理和阻塞 |
20 | SIGTSTP | 进程停止 | |
21 | SIGTTIN | 进程停止,后台进程从终端读数据时 | |
22 | SIGTTOU | 进程停止,后台进程想终端写数据时 | |
23 | SIGURG | I/O有紧急数据到达当前进程 | 默认忽略 |
24 | SIGXCPU | 进程的CPU时间片到期 | |
25 | SIGXFSZ | 文件大小的超出上限 | |
26 | SIGVTALRM | 虚拟时钟超时 | |
27 | SIGPROF | profile时钟超时 | |
28 | SIGWINCH | 窗口大小改变 | 默认忽略 |
29 | SIGIO | I/O相关 | |
30 | SIGPWR | 关机 | 默认忽略 |
31 | SIGSYS | 系统调用异常 |
信号注册&注销
在进程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