本篇是 bugly 一篇关于 native crash 捕获的文章的练习。由于他文章中已经给出了相关的大部分知识点,这里我就仅仅补充一些细节,并给出一个完整的 demo。建议大家先阅读 Android 平台 Native 代码的崩溃捕获机制及实现
,熟悉一下相关的知识。
相关代码可以在 https://github.com/Jekton/NativeCrashCatching(目标平台是 Android 8) 找到。
设置 signal handler 的运行堆栈
static void SetUpStack() {
stack_t stack{};
stack.ss_sp = new(std::nothrow) char[SIGSTKSZ];
if (!stack.ss_sp) {
LOGW(kTag, "fail to alloc stack for crash catching");
return;
}
stack.ss_size = SIGSTKSZ;
stack.ss_flags = 0;
if (stack.ss_sp) {
if (sigaltstack(&stack, nullptr) != 0) {
LOGERRNO(kTag, "fail to setup signal stack");
}
}
}
SIGSTKSZ
是一个 signal.h
预定义的常量,我们可以直接用它做目标的栈大小。LOGERRNO, LOGD, LOGE
等是我自己定义的打印 Android log 的宏。
设置信号处理函数
static std::map<int, struct sigaction> sOldHandlers;
static void SetUpSigHandler() {
struct sigaction action{};
action.sa_sigaction = SignalHandler;
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
int signals[] = {
SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGPIPE
};
struct sigaction old_action;
for (auto signo : signals) {
if (sigaction(signo, &action, &old_action) == -1) {
LOGERRNO(kTag, "fail to set signal handler for signo %d", signo);
} else {
if (old_action.sa_handler != SIG_DFL && old_action.sa_handler != SIG_IGN) {
sOldHandlers[signo] = old_action;
}
}
}
}
这里我们把旧的 signal handler 保存起来,执行完我们自己的函数后,再调用它们:
static void SignalHandler(int signo, siginfo_t* info, void* context) {
DumpSignalInfo(info);
DumpStacks(context);
CallOldHandler(signo, info, context);
exit(0);
}
static void CallOldHandler(int signo, siginfo_t* info, void* context) {
auto it = sOldHandlers.find(signo);
if (it != sOldHandlers.end()) {
if (it->second.sa_flags & SA_SIGINFO) {
it->second.sa_sigaction(signo, info, context);
} else {
it->second.sa_handler(signo);
}
}
}
DumpSignalInfo
用来打印 siginfo_t
,DumpStacks
用来打印堆栈,很快我们就会看到他的实现。
打印 siginfo_t
打印 siginfo_t
没什么技术含量,就只是根据 signo
和 si_code
打印对应的消息。
static void DumpSignalInfo(siginfo_t* info) {
switch (info->si_signo) {
case SIGILL:
LOGI(kTag, "signal SIGILL caught");
switch (info->si_code) {
case ILL_ILLOPC:
LOGI(kTag, "illegal opcode");
break;
case ILL_ILLOPN:
LOGI(kTag, "illegal operand");
break;
case ILL_ILLADR:
LOGI(kTag, "illegal addressing mode");
break;
case ILL_ILLTRP:
LOGI(kTag, "illegal trap");
break;
case ILL_PRVOPC:
LOGI(kTag, "privileged opcode");
break;
case ILL_PRVREG:
LOGI(kTag, "privileged register");
break;
case ILL_COPROC:
LOGI(kTag, "coprocessor error");
break;
case ILL_BADSTK:
LOGI(kTag, "internal stack error");
break;
default:
LOGI(kTag, "code = %d", info->si_code);
break;
}
break;
case SIGFPE:
LOGI(kTag, "signal SIGFPE caught");
switch (info->si_code) {
case FPE_INTDIV:
LOGI(kTag, "integer divide by zero");
break;
case FPE_INTOVF:
LOGI(kTag, "integer overflow");
break;
case FPE_FLTDIV:
LOGI(kTag, "floating-point divide by zero");
break;
case FPE_FLTOVF:
LOGI(kTag, "floating-point overflow");
break;
case FPE_FLTUND:
LOGI(kTag, "floating-point underflow");
break;
case FPE_FLTRES:
LOGI(kTag, "floating-point inexact r