主要还是一个学习的过程,了解一下unwind库和elf文件结构,若有哪里描述有误,希望大佬们能够指出。
一、crash日志内容组成
1.1 组成示例
详细信息可见:source.android.google.cn/devices/tec…
如下是一个crash内容示例:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Android/aosp_flounder/flounder:5.1.51/AOSP/enh08201009:eng/test-keys'
Revision: '0'
ABI: 'arm'
pid: 1656, tid: 1656, name: crasher >>> crasher <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'some_file.c:123: some_function: assertion "false" failed'
r0 00000000 r1 00000678 r2 00000006 r3 f70b6dc8
r4 f70b6dd0 r5 f70b6d80 r6 00000002 r7 0000010c
r8 ffffffed r9 00000000 sl 00000000 fp ff96ae1c
ip 00000006 sp ff96ad18 lr f700ced5 pc f700dc98 cpsr 400b0010
backtrace:
#00 pc 00042c98 /system/lib/libc.so (tgkill+12)
#01 pc 00041ed1 /system/lib/libc.so (pthread_kill+32)
#02 pc 0001bb87 /system/lib/libc.so (raise+10)
#03 pc 00018cad /system/lib/libc.so (__libc_android_abort+34)
#04 pc 000168e8 /system/lib/libc.so (abort+4)
#05 pc 0001a78f /system/lib/libc.so (__libc_fatal+16)
#06 pc 00018d35 /system/lib/libc.so (__assert2+20)
#07 pc 00000f21 /system/xbin/crasher
#08 pc 00016795 /system/lib/libc.so (__libc_init+44)
#09 pc 00000abc /system/xbin/crasher
Tombstone written to: /data/tombstones/tombstone_06
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1.2 包含内容
-
设备指纹
用于记录设备相关信息
-
Revision
Revision 指的是硬件,而不是软件。通常情况下不使用 revision,但使用 revision 有助于您自动忽略由不良硬件导致的已知错误
-
ABI
ABI 是 arm、arm64、mips、mips64、x86 或 x86-64 之一,记录该值主要用于后续对信息分析时区分使用什么工具链
-
进程相关信息
包括进程号、进程名称
-
错误信号
接收的信号 (这里是SIGABRT) 以及有关如何接收该信号的更多信息 (这里是SI_TKILL)
-
中断消息
并非所有崩溃问题都会有中止消息行,但发生中止时,会出现该消息行。这是从此 pid/tid 的最后一行严重 logcat 输出中自动收集而来的,而在有意中止的情况下,这可以解释该程序自行终止的原因
-
寄存器信息
展示所有寄存器中的内容,这些内容的有用程度取决于确切的崩溃问题
-
调用栈
crash函数调用栈
二、捕获Crash
在Linux系统中,当发生异常时,内核会以信号机制通知进程,收到信号后,进程可以有以下三种处理方案:
- 忽略信号
- 使用系统默认操作
- 指定处理函数处理
很明显,我们需要自己指定信号处理函数捕捉信号。
以下代码展示了如何设置自定义信号处理方式:
- crash_dumper.h
#define SIGNAL_COUNT 8
static int signals[SIGNAL_COUNT] = {
SIGTRAP,
SIGABRT,
SIGILL,
SIGSEGV,
SIGFPE,
SIGBUS,
SIGPIPE,
SIGSYS
};
- crash_dumper.cpp
void registerSigHandler(const char *tombStonePath) {
// set dir to save tombstone
long strLen = strlen(tombStonePath);
if (tombstone_file_dir != NULL) {
free(tombstone_file_dir);
}
tombstone_file_dir = static_cast<char *>(malloc(strLen + 1));
memset(tombstone_file_dir, 0, strLen);
strcpy(tombstone_file_dir, tombStonePath);
// create new sigaction
struct sigaction newSigAction;
sigemptyset(&newSigAction.sa_mask);
newSigAction.sa_flags = SA_SIGINFO;
// set sigaction handler
newSigAction.sa_sigaction = sigHandler;
// register new sigaction handler and save old actions
for (int i = 0; i < SIGNAL_COUNT; ++i) {
sigaction(signals[i], &newSigAction, &g_oldSigActions[i]);
}
}
注意,newSigAction.sa_flags
必须包括SA_SIGINFO
这个MASK,这样,我们才能在sigHandler
函数的第二个参数中获取详细信息
void sigHandler(int sig, siginfo_t *info, void *context) {
// ......
}
该函数的第一个参数表示信号,比如上方的SIGABRT
,第二个参数表示信号携带的数据,比如pid
、uid
、code
等信息,第三个参数的类型是ucontext_t
,结构体内容如下,用来保存一些上下文信息:
typedef struct ucontext {
...
mcontext_t uc_mcontext; // 保存具体的程序执行上下文,如PC值,堆栈指针以及寄存器值等信息
...
} ucontext_t;
三、获取Header信息
获取Build fingerprint、revision、abi:
-
Build fingerprint
读取系统的
ro.build.fingerprint
字段信息即可char finger[92] = {0}; __system_property_get("ro.build.fingerprint", finger);
-
revision
读取系统的
ro.revision
字段信息即可char revision[92] = {0}; __system_property_get("ro.revision", revision);
-
abi
我们可以读取so中的内容判断,也可以在编译期就进行判断,这里选择在编译期判断
const char *getABI() { //It's usually most convenient to determine the ABI at build time using #ifdef in conjunction with: // //__arm__ for 32-bit ARM //__aarch64__ for 64-bit ARM //__i386__ for 32-bit X86 //__x86_64__ for 64-bit X86 //Note that 32-bit X86 is called __i386__, not __x86__ as you might expect! // // https://developer.android.google.cn/ndk/guides/cpu-features?hl=en #if defined(__aarch64__) return "arm64"; #elif defined(__arm__) return "arm"; #else return "unknown"; #endif }
四、获取进程信息及信号信息
在void sigHandler(int sig, siginfo_t *info, void *context)
函数中,第二个参数记录了信号相关的信息,其中就包含了进程号等信息:
4.1 获取进程号
info->_sifields._kill._pid
4.2 获取进程名称
std::string get_process_name_by_pid(const int pid) {
char processName[256] = {0};
char cmd[64] = {0};
sprintf(cmd, "/proc/%d/cmdline", pid);
FILE *f = fopen(cmd, "r");
if (f) {
size_t size;
size = fread(processName, sizeof(char), 256, f);
if (size > 0 && '\n' == processName[size - 1]) {
processName[size - 1] = '\0';
}
fclose(f);
}
return processName;
}
4.3 信号信息
- signal
即函数的第一个参数:sig
- code
可通过函数的第二个参数获取:info->si_code
五、寄存器信息
5.1 数据结构介绍
在捕捉到信号和信号附带的信息时,我们还能获取到一个context
数据,这是一个类型为ucontext_t
的结构体,其中的uc_mcontext
属性记录了当前寄存器相关信息。
对于aarch64
和arm
,这个结构体的内容是不一样的:
-
aarch64
struct sigcontext { __u64 fault_address; __u64 regs[31]; __u64 sp; __u64 pc; __u64 pstate; __u8 __reserved[4096] __attribute__((__aligned__(16))); };
寄存器描述:
-
arm
struct sigcontext { unsigned long trap_no; unsigned long error_code; unsigned long oldmask; unsigned long arm_r0; unsigned long arm_r1; unsigned long arm_r2; unsigned long arm_r3; unsigned long arm_r4; unsigned long arm_r5; unsigned long arm_r6; unsigned long arm_r7; unsigned long arm_r8; unsigned long arm_r9; unsigned long arm_r10; unsigned long arm_fp; unsigned long arm_ip; unsigned long arm_sp; unsigned long arm_lr; unsigned long arm_pc; unsigned long arm_cpsr; unsigned long fault_address; };
寄存器描述:
5.2 代码实现
模仿android 系统日志中的日志格式
std::string getRegisterInfo(const ucontext_t *ucontext) {
std::string ctxTxt;
#if defined(__aarch64__)
for (int i = 0; i < 30; ++i) {
if (i % 4 == 0) {
ctxTxt.append(" ");
}
char info[64] = {0};
sprintf(info, "x%d %016lx ", i, ucontext->uc_mcontext.regs[i]);
ctxTxt.append(info);
if ((i + 1) % 4 == 0) {
ctxTxt.append("\r\n");
}
}
ctxTxt.append("\r\n");
char spInfo[64] = {0};
sprintf(spInfo, "sp %016lx ", ucontext->uc_mcontext.sp);
char lrInfo[64] = {0};
sprintf(lrInfo, "lr %016lx ", ucontext->uc_mcontext.regs[30]);
char pcInfo[64] = {0};
sprintf(pcInfo, "pc %016lx ", ucontext->uc_mcontext.pc);
ctxTxt.append(" ").append(spInfo).append(lrInfo).append(pcInfo);
#elif defined(__arm__)
char x0Info[64] = {0};
sprintf(x0Info, "x0 %08lx ", ucontext->uc_mcontext.arm_r0);
char x1Info[64] = {0};
sprintf(x1Info, "x1 %08lx ", ucontext->uc_mcontext.arm_r1);
char x2Info[64] = {0};
sprintf(x2Info, "x2 %08lx ", ucontext->uc_mcontext.arm_r2);
char x3Info[64] = {0};
sprintf(x3Info, "x3 %08lx ", ucontext->uc_mcontext.arm_r3);
char x4Info[64] = {0};
sprintf(x4Info, "x4 %08lx ", ucontext->uc_mcontext.arm_r4);
char x5Info[64] = {0};
sprintf(x5Info, "x5 %08lx ", ucontext->uc_mcontext.arm_r5);
char x6Info[64] = {0};
sprintf(x6Info, "x6 %08lx ", ucontext->uc_mcontext.arm_r6);
char x7Info[64] = {0};
sprintf(x7Info, "x7 %08lx ", ucontext->uc_mcontext.arm_r7);
char x8Info[64] = {0};
sprintf(x8Info, "x8 %08lx ", ucontext->uc_mcontext.arm_r8);
char x9Info[64] = {0};
sprintf(x9Info, "x9 %08lx ", ucontext->uc_mcontext.arm_r9);
char x10Info[64] = {0};
sprintf(x10Info, "x10 %08lx ", ucontext->uc_mcontext.arm_r10);
char ipInfo[64] = {0};
sprintf(ipInfo, "ip %08lx ", ucontext->uc_mcontext.arm_ip);
char spInfo[64] = {0};
sprintf(spInfo, "sp %08lx ", ucontext->uc_mcontext.arm_sp);
char lrInfo[64] = {0};
sprintf(lrInfo, "lr %08lx ", ucontext->uc_mcontext.arm_lr);
char pcInfo[64] = {0};
sprintf(pcInfo, "pc %08lx ", ucontext->uc_mcontext.arm_pc);
ctxTxt.append(" ").append(x0Info).append(x1Info).append(x2Info).append(x3Info).append(
"\r\n")
.append(" ").append(x4Info).append(x5Info).append(x6Info).append(x7Info).append(
"\r\n")
.append(" ").append(x8Info).append(x9Info).append(x10Info).append("\r\n")
.append(" ").append(ipInfo).append(spInfo).append(lrInfo).append(pcInfo);
#endif
return ctxTxt;
}
六、获取buildId
6.1 ELF文件格式
so文件属于ELF文件格式,我们可以通过解析Elf文件的方式获取so的buildId,ELF文件格式介绍如下:
以下是ELF Header和Section Header的结构,下面我们将根据这个结构对so文件进行解析。
-
ELF header:(原表格太大了,删掉了原文中
e_ident[EI_OSABI]
和e_machine
中的详细说明) -
Section header:
6.2 获取buildId
6.2.1 流程说明
流程如下,我们需要先获取.shstrtab
section内容,再遍历每个section,获取每个section的名称,当名称是.note.gnu.build-id
时,获取其内容:
-
获取
.shstrtab
section下方namesSectionHeader指的就是
.shstrtab
对应的sectionHeader -
遍历每个section,得到
.note.gnu.build-id
6.2.2 手动分析
在编写代码获取buildId之前,先按照上述流程手动解析一次sectionHeader,加深对ELF文件格式的理解。
以一个ABI为armeabi-v7a的so为例,查看其16进制内容:
-
Elf_Ehdr.e_ident[EI_DATA]
位置:0x05
大小:1
值:01,表示little endian
-
Elf_Ehdr.e_shoff
起始位置:0x20
大小:4
值:0x00003218
-
Elf_Ehdr.e_shnum
起始位置:0x30
大小:2
值:0x001B
-
Elf_Ehdr.e_shstrndx
起始位置:0x32
大小:2
值:0x001A
-
Elf_Ehdr.e_shentsize
起始位置:0x2E
大小:2
值:0x0028
-
通过以上内容,即可计算
.shstrtab
所在位置-
首先计算
.shstrtab
sectionHeader的位置Elf_Shdr *namesSectionHeader = (Elf_Shdr *) (elfData + elfHeader->e_shoff + elfHeader->e_shentsize * elfHeader->e_shstrndx);
即:0x3218 + 0x28 * 0x1A = 0x3628
-
再获取
.shstrtab
section内容的位置char *sectionNames = (char *) (elfData + namesSectionHeader->sh_offset);
即0x3638(0x3628 + 0x10,0x10代表sectionHeader的sh_offset,详见上方section header的表格描述)位置开始,4个字节的内容,即:0x3106
-
-
遍历所有sectionHeader,获取section名称
在遍历之前,我们先试试手动计算,获取一个section名称,我们已经知道,
.shstrtab
这个section的header对应的位置是0x3628
,那么这个sectionName对应的nameOffset就是0x00F7
那么,可以计算sectionName的位置就是:0x3106 + 0x00F7 = 0x31FD 代码实现遍历所有section,打印名称:Elf_Ehdr *elfHeader = (Elf_Ehdr *) elfData; Elf_Shdr *namesSectionHeader = (Elf_Shdr *) (elfData + elfHeader->e_shoff + elfHeader->e_shentsize * elfHeader->e_shstrndx); char *sectionNames = (char *) (elfData + namesSectionHeader->sh_offset); for (int i = 0; i < elfHeader->e_shnum; ++i) { Elf_Shdr *sectionHeader = (Elf_Shdr *) (elfData + i * elfHeader->e_shentsize + elfHeader->e_shoff); LOGI("sectionName = %s",sectionNames + sectionHeader->sh_name); }
-
整体流程
6.2.3 解析.note.gnu.build-id
使用上述方法,遍历所有section,当name为.note.gnu.build-id
时,获取其内容:
.note.gnu.build-id
的内容组成结构为:
+----------------+
| namesz | 32-bit, size of "name" field
+----------------+
| descsz | 32-bit, size of "desc" field
+----------------+
| type | 32-bit, vendor specific "type"
+----------------+
| name | "namesz" bytes, null-terminated string
+----------------+
| desc | "descsz" bytes, binary data
+----------------+
我们需要获取的是desc
的内容,其相对于.note.gnu.build-id
而言,偏移量为4 + 4 + 4 + namesz
,数据长度为descsz
。
示例代码如下:
#define SEC_NAME_BUILD_ID ".note.gnu.build-id"
std::string getBuildId(unsigned char *elfData) {
Elf_Ehdr *elfHeader = (Elf_Ehdr *) elfData;
Elf_Shdr *namesSectionHeader = (Elf_Shdr *) (elfData + elfHeader->e_shoff +
elfHeader->e_shstrndx * elfHeader->e_shentsize);
char *sectionNames = (char *) (elfData + namesSectionHeader->sh_offset);
for (int i = 0; i < elfHeader->e_shnum; ++i) {
Elf_Shdr *sectionHeader = (Elf_Shdr *) (elfData + elfHeader->e_shoff +
i * elfHeader->e_shentsize);
if (strcmp((const char *) sectionNames + sectionHeader->sh_name, SEC_NAME_BUILD_ID) == 0) {
char *buildId = (char *) malloc(sectionHeader->sh_size);
memcpy(buildId, elfData + sectionHeader->sh_offset, sectionHeader->sh_size);
std::string buildIdStr;
// offset 0x10 (16)
// +----------------+
// | namesz | 32-bit, size of "name" field
// +----------------+
// | descsz | 32-bit, size of "desc" field
// +----------------+
// | type | 32-bit, vendor specific "type"
// +----------------+
// | name | "namesz" bytes, null-terminated string
// +----------------+
// | desc | "descsz" bytes, binary data
// +----------------+
// https://interrupt.memfault.com/blog/gnu-build-id-for-firmware
//
// 04000000 14000000 03000000 47 4e 55 00
// namesz descsz type G N U 0
// 4 + 4 + 4 + 4 = 16
int nameSize = *buildId;
int descOffset = 4 + 4 + 4 + nameSize;
for (int j = descOffset; j < sectionHeader->sh_size; ++j) {
char ch[3] = {0};
sprintf(ch, "%02x", buildId[j]);
buildIdStr += ch;
}
free(buildId);
return buildIdStr;
}
}
return "";
}
通过以上方式,我们就完成对动态库中的buildId的获取,代码不长,主要是需要了解下ELF文件结构。
七、获取调用栈
在4.1.1以上,5.0以下:可以使用安卓系统自带的libcorkscrew.so;
在5.0及以上:安卓系统中没有了libcorkscrew.so,可以使用unwind库。
这里只兼容了Android 5.0及以上设备,使用unwind库采集调用栈。
7.1 _Unwind_Backtrace函数介绍
我们可以通过_Unwind_Backtrace
获取调用栈,函数声明如下:
_Unwind_Reason_Code _Unwind_Backtrace(_Unwind_Trace_Fn trace, void * trace_argument);
-
trace
自定义的trace函数,函数签名为:
typedef _Unwind_Reason_Code (*_Unwind_Trace_Fn)(struct _Unwind_Context * context, void * arg);
-
context:
这个结构体类型没有对外暴露,用于表示程序运行时的上下文,主要就是一些寄存器的值等信息
-
arg
也就是trace_argument,用于传输自定义的附加信息
-
返回值
详见
_Unwind_Reason_Code
枚举定义
-
-
trace_argument
自定义的附加参数
-
返回值
详见
_Unwind_Reason_Code
枚举定义
7.2 dladdr函数介绍
我们可以通过dladdr
获取一些相关信息,函数声明如下:
int dladdr(const void* __addr, Dl_info* __info);
-
__addr
指令地址
-
__info
数据结构如下,包括so路径、so加载到内存中的地址、符号、符号地址,详细说明见注释
typedef struct { /* Pathname of shared object that contains address. */ const char* dli_fname; /* Address at which shared object is loaded. */ void* dli_fbase; /* Name of nearest symbol with address lower than addr. */ const char* dli_sname; /* Exact address of symbol named in dli_sname. */ void* dli_saddr; } Dl_info;
-
返回值
非0表示成功,如果该地值没有匹配到任何的库,则返回0
7.3 获取指令偏移地址、库路径、符号、符号偏移信息
大致流程如下图所示:
-
获取指令地址
_Unwind_Word ip = _Unwind_GetIP(context);
-
获取库、符号等信息
-
首先获取
Dl_info
Dl_info info; int res = dladdr((void *) ip, &info);
-
指令偏移地址
将指令地址与库基地址相减,得到指令相对于库的地址:
offset = ip - (_Unwind_Word) info.dli_saddr
-
库路径
info.dli_sname
-
符号偏移地址
info.dli_saddr
-
符号名称
info.dli_sname
-
-
示例调用代码
-
trace_argument定义,包括调用栈深度及调用栈说明信息
typedef struct TraceInfo { int depth; std::string result; } BackTraceInfo;
-
保存调用栈
void storeCallStack(std::string *result) { BackTraceInfo traceInfo; traceInfo.depth = 0; _Unwind_Backtrace(traceCallBack, &traceInfo); *result += traceInfo.result; }
-
获取每个调用的详细信息
_Unwind_Reason_Code traceCallBack(_Unwind_Context *context, void *hnd) { auto *traceHandle = (BackTraceInfo *) hnd; _Unwind_Word ip = _Unwind_GetIP(context); Dl_info info; int res = dladdr((void *) ip, &info); if (res == 0) { return _URC_END_OF_STACK; } char *desc = (char *) malloc(1024); memset(desc, 0, 1024); std::string buildId; if (info.dli_fname != NULL) { char *symbol = (char *) malloc(256); if (info.dli_sname == NULL) { strcpy(symbol, "unknown"); } else { sprintf(symbol, "%s+%ld", info.dli_sname, ip - (_Unwind_Word) info.dli_saddr); } buildId = getSharedObjectBuildId(info.dli_fname); if (buildId.length() > 0) { buildId = "(BuildId: " + buildId + ")"; } #if defined(__arm__) sprintf(desc, " #%02d pc %08lx %s (%s) ", traceHandle->depth, ip - (_Unwind_Word) info.dli_fbase, info.dli_fname, symbol); #elif defined(__aarch64__) sprintf(desc, " #%02d pc %016lx %s (%s) ", traceHandle->depth, ip - (_Unwind_Word) info.dli_fbase, info.dli_fname, symbol); #endif free(symbol); } if (traceHandle->result.length() != 0) { traceHandle->result.append("\r\n"); } traceHandle->result.append(desc).append(buildId); free(desc); ++traceHandle->depth; // FIXME: crash if call stack is over 5 on ARM32, unknown reason #if !defined(__aarch64__) && defined(__arm__) if (traceHandle->depth == 5) { return _URC_END_OF_STACK; } #endif return _URC_NO_REASON; }
-
八、写入信息到日志文件
8.1 集成以上功能,输出信息至文件
以下是sigHandler
的具体实现,其中记录所有相关信息:
void sigHandler(int sig, siginfo_t *info, void *context) {
std::string desc;
desc.append("*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\r\n")
.append(getHeaderInfo()).append("\r\n")
.append("Timestamp: ").append(getCrashTimeStamp()).append("\r\n")
.append("pid: ").append(std::to_string(info->_sifields._kill._pid))
.append(", uid: ").append(std::to_string(info->_sifields._kill._uid))
.append(", process: >>> ").append(get_process_name_by_pid(info->_sifields._kill._pid)).append(" <<<\r\n")
.append("signal ").append(std::to_string(sig)).append(" (").append(signal_names[sig])
.append("), code ").append(std::to_string(info->si_code))
.append(" (").append(si_names[info->si_code])
.append("), fault addr --------\r\n")
.append(getRegisterInfo((const ucontext_t *) context))
.append("\r\nbacktrace:");
std::string result;
storeCallStack(&result);
std::string path = tombstone_file_path;
path += getDumpFileName();
std::ofstream outStream(path.c_str(), std::ios::out);
if (outStream) {
const char *logContent = result.c_str();
outStream.write(desc.c_str(), desc.length());
outStream.write("\r\n", strlen("\r\n"));
outStream.write(logContent, result.length());
outStream.close();
}
switch (handleMode) {
case HANDLE_MODE_RAISE_ERROR:
unregisterSigHandler();
raise(sig);
break;
case HANDLE_MODE_NOTICE_CALLBACK:
noticeCallback(sig, path.c_str());
break;
case HANDLE_MODE_DO_NOTHING:
default:
break;
}
}
8.2 示例输出
完整日志:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'OnePlus/OnePlus7T_CH/OnePlus7T:10/QKQ1.190716.003/2001030004:user/release-keys'
Revision: '0'
ABI: 'arm64'
Timestamp: 2020-07-24 19:49:58+0800
pid: 11626, uid: 10675, process: >>> com.wsy.crashcatcher <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
x0 0000000000000000 x1 0000000000002d6a x2 0000000000000006 x3 0000007ffa592720
x4 0000007ffa590a00 x5 0000007ffa590a00 x6 0000007ffa590a00 x7 aa5402445aac8798
x8 0000000000000083 x9 aa5402445aac8798 x10 0000000000430000 x11 0000007d48f19000
x12 000000000000018c x13 0000000000489900 x14 0000000000000006 x15 ffffffffffffffff
x16 0000007dcc1b1cd0 x17 0000007dcc1907a0 x18 0000007dcf59a000 x19 0000007d49a10800
x20 0000000000000000 x21 0000007d49a10800 x22 0000007ffa5909a0 x23 0000007d3d7dc63b
x24 0000000000000004 x25 0000007dceff6020 x26 0000007d49a108b0 x27 0000000000000001
x28 0000007ffa590730 x29 0000007ffa5906e0
sp 0000007ffa5906d0 lr 0000007d2cd726a4 pc 0000007dcc1907a8
backtrace:
#00 pc 00000000000533d0 /data/app/com.wsy.crashcatcher-Sv1ILhrXAIcPR4jA9OCt3Q==/lib/arm64/libcrash_dumper.so (_Z14storeCallStackPNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE+68) (BuildId: 74b7663be098740b4561517c9cf17c2f057c089c)
#01 pc 0000000000049934 /data/app/com.wsy.crashcatcher-Sv1ILhrXAIcPR4jA9OCt3Q==/lib/arm64/libcrash_dumper.so (_Z10sigHandleriP7siginfoPv+872) (BuildId: 74b7663be098740b4561517c9cf17c2f057c089c)
#02 pc 000000000000063c [vdso] (__kernel_rt_sigreturn+0)
#03 pc 00000000000c27a8 /apex/com.android.runtime/lib64/bionic/libc.so (tgkill+8) (BuildId: a2584ee8458a61d422edf24b4cd23b78)
#04 pc 00000000000006a4 /data/app/com.wsy.crashcatcher-Sv1ILhrXAIcPR4jA9OCt3Q==/lib/arm64/libtest_crash.so (_Z10raiseErrori+24) (BuildId: b2cd4b1a020680de4f99e469e5544e21c4e49b5d)
#05 pc 00000000000006d0 /data/app/com.wsy.crashcatcher-Sv1ILhrXAIcPR4jA9OCt3Q==/lib/arm64/libtest_crash.so (Java_com_wsy_crashcatcher_MainActivity_nativeCrash+32) (BuildId: b2cd4b1a020680de4f99e469e5544e21c4e49b5d)
#06 pc 0000000000140354 /apex/com.android.runtime/lib64/libart.so (unknown) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#07 pc 0000000000137338 /apex/com.android.runtime/lib64/libart.so (unknown) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#08 pc 0000000000145ff0 /apex/com.android.runtime/lib64/libart.so (_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+248) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#09 pc 00000000002e394c /apex/com.android.runtime/lib64/libart.so (_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_11ShadowFrameEtPNS_6JValueE+388) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#10 pc 00000000002debac /apex/com.android.runtime/lib64/libart.so (_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+896) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#11 pc 00000000005a1138 /apex/com.android.runtime/lib64/libart.so (MterpInvokeVirtual+652) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#12 pc 0000000000131818 /apex/com.android.runtime/lib64/libart.so (unknown) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#13 pc 00000000002b4c60 /apex/com.android.runtime/lib64/libart.so (unknown) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#14 pc 00000000005926a0 /apex/com.android.runtime/lib64/libart.so (artQuickToInterpreterBridge+1036) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#15 pc 000000000014046c /apex/com.android.runtime/lib64/libart.so (unknown) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#16 pc 0000000000137338 /apex/com.android.runtime/lib64/libart.so (unknown) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#17 pc 0000000000145ff0 /apex/com.android.runtime/lib64/libart.so (_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+248) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#18 pc 00000000004b1040 /apex/com.android.runtime/lib64/libart.so (unknown) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#19 pc 00000000004b2be4 /apex/com.android.runtime/lib64/libart.so (_ZN3art12InvokeMethodERKNS_33ScopedObjectAccessAlreadyRunnableEP8_jobjectS4_S4_m+1484) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#20 pc 000000000043df18 /apex/com.android.runtime/lib64/libart.so (unknown) (BuildId: ced0e918261ca872f5cff4cdba80b1a9)
#21 pc 00000000000c2c38 /system/framework/arm64/boot.oat (unknown) (BuildId: e30afaca6cda76e4ae36a16405b32e73a1434e56)
九、Github
完整代码如下: