Google-Breadpad linux源码解析 (一)
前言
阅读本文你需要简单了解breakpad的原理以及使用方式,在前面一篇文档有对这一部分内容进行介绍google-breakpad - 使用篇
整体概要
这篇文档将会从breakpad的client端的源码开始分析,在篇幅(一)中将不会具体分析如何写入dump文件,而是分析整个breakpad执行的流程,当然仅仅针对linux源码
源码分析
ExceptionHandler 构造函数
源码文件:src/client/linux/handler/exception_handler.cc:219
// 源代码中会去除一些无用代码,如Android特定处理代码,v8特定处理代码,以及大量注释
ExceptionHandler::ExceptionHandler(const MinidumpDescriptor& descriptor,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
bool install_handler,
const int server_fd)
: filter_(filter),
callback_(callback),
callback_context_(callback_context),
minidump_descriptor_(descriptor),
crash_handler_(NULL) {
if (server_fd >= 0)
crash_generation_client_.reset(CrashGenerationClient::TryCreate(server_fd));
if (!IsOutOfProcess() && !minidump_descriptor_.IsFD() &&
!minidump_descriptor_.IsMicrodumpOnConsole())
minidump_descriptor_.UpdatePath();
pthread_mutex_lock(&g_handler_stack_mutex_);
memset(&g_crash_context_, 0, sizeof(g_crash_context_));
if (!g_handler_stack_)
g_handler_stack_ = new std::vector<ExceptionHandler*>;
if (install_handler) {
InstallAlternateStackLocked();
InstallHandlersLocked();
}
g_handler_stack_->push_back(this);
pthread_mutex_unlock(&g_handler_stack_mutex_);
}
参数解释:
descriptor是一个MinidumpDescriptor描述符,他可以描述你dump写入的形式,如采用microdump(最小的dump信息),或者传入一个文件描述符、socket套接字、dump最终输入文件名等等filter是一个回调函数,这个回调函数将会在触发异常之后breakpad没有做任何实质操作之前被调用,如果filter返回false,将会终止breakpad接下来的动作callback也是一个回调函数,不过它在dump写入之后调用,并且会返回一些写入结果,如dumper是否生成成功,生成路径在哪等install_handler为true的时候将会在发生异常终止的时候写入minidump文件,为false的时候将只会在手动调用WriteMinidump时候才会写入server_fd在out-process模式的时候传入服务端的fd,在in-process的时候传入-1
构造函数流程:
- 首先通过判断传入进来的
server_fd是否大于等于0,如果server_fd大于等于表示形式是out-process,将会初始化crash_generation_client_对象 - 接下来判断是
in-process, 并且minidump的描述符不是采用一个文件描述符形式和minidump写入不是采用microdump形式,就表示这个minidump描述符的初始化采用的是目录方式,然后minidump_descriptor_.UpdatePath这一行代码其实只是干了生成一个uuid然后拼接上原有的dump生成目录。 - 初始化
g_handler_stack_,g_handler_stack_是一个vector<ExceptionHandler*>,ExcetipnHandler的实例可能有多个,所以用一个容器来保存起来。 - 判断
install_handler是否为true,为true会调用两个函数InstallAlternateStackLocked,InstallHandlersLocaled, 后面再分析这两个函数 - 将当前ExceptionHandler对象插入到g_handler_stack_中
ExceptionHandler构造函数的整个初始化流程并不复杂,去除out-process的这种情况,它就只干了几件事情。
InstallHandlersLocaled 函数
源码文件:src/client/linux/handler/exception_handler.cc:131
// 源代码中会去除一些无用代码,如Android特定处理代码,v8特定处理代码,以及大量注释
void InstallAlternateStackLocked() {
if (stack_installed)
return;
memset(&old_stack, 0, sizeof(old_stack));
memset(&new_stack, 0, sizeof(new_stack));
static const unsigned kSigStackSize = std::max(16384, SIGSTKSZ);
if (sys_sigaltstack(NULL, &old_stack) == -1 || !old_stack.ss_sp ||
old_stack.ss_size < kSigStackSize) {
new_stack.ss_sp = calloc(1, kSigStackSize);
new_stack.ss_size = kSigStackSize;
if (sys_sigaltstack(&new_stack, NULL) == -1) {
free(new_stack.ss_sp);
return;
}
stack_installed = true;
}
}
InstallAlternateStackLocaked这个函数只是在做一件事情,建立一个alternative stack,给后续signal处理handler使用,为什么要建立一个alternative stack?原因是这个信号的产生可能是由于堆栈溢出,所以在执行signal的handler时使用一个alternatvie stack是很有必要的
alternate signal stack建立与使用分为三个步骤:
- 分配一块内存给
alternate signal stack使用 - 使用
sigaltstack系统调用设置alternate signal stack的首地址和stack大小 - 在建立signal handler(sigaction api)的时候通过
SA_ONSTACKflag告知系统这个处理handler使用alternate stack
具体请阅读sigaltstack(2) - Linux manual page (man7.org)
InstallAlternateStackLocaked做了alternate signal stack的前两个步骤,最后一个步骤会在InstallHandlersLocakd函数里面完成。
ExceptionHandler::InstallHandlersLocked 函数
源码文件:src/client/linux/handler/exception_handler.cc:275
// 源代码中会去除一些无用代码,如Android特定处理代码,v8特定处理代码,以及大量注释
bool ExceptionHandler::InstallHandlersLocked() {
if (handlers_installed)
return false;
for (int i = 0; i < kNumHandledSignals; ++i) {
if (sigaction(kExceptionSignals[i], NULL, &old_handlers[i]) == -1)
return false;
}
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
for (int i = 0; i < kNumHandledSignals; ++i)
sigaddset(&sa.sa_mask, kExceptionSignals[i]);
sa.sa_sigaction = SignalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
for (int i = 0; i < kNumHandledSignals; ++i) {
if (sigaction(kExceptionSignals[i], &sa, NULL) == -1) {
// 忽略错误
}
}
handlers_installed = true;
return true;
}
InstallHandlersLocaked函数主要作用是给需要捕获的signal设置处理handler,并且完成上面剩下的alternate signal stack的最后一步。
- 保留之前所有的旧的处理handler,如果保留失败返回
- 在某个信号处理handler时阻塞所有kExcetpionSignals触发的signal(kExceptionSignals =
SIGSEGV,SIGABRT,SIGFPE,SIGILL,SIGBUS,SIGTRAP)。 - 设置
SignalHandler到sa.sa_sigaction, 设置sa.sa_flags为SA_ONSTACK | SIGINFO, 并将所有的kExcetptionSignals里的信号处理handler都设置成sa.sa_sigaction, sa.sa_flags。这里面sa.sa_flags的SA_ONSTACKflag就是告知系统这个处理handler采用alternate stack,而SA_SIGINFOflag告诉系统使用sa_sigaction来代替sa.sa_handler参数。
到这里整个ExceptionHandler的初始化流程就完成了,可以看到如果触发了kExceptionSignals里面的任何一个信号都会走到SignalHandler函数里面进行处理,在前面ExceptionHandler的整个初始化都是在一个正常环境运行,而后面SignalHandler函数开始都是运行在一个崩溃环境中
ExceptionHandler::SignalHandler 函数
源码文件:src/client/linux/handler/exception_handler.cc:275
// 源代码中会去除一些无用代码,如Android特定处理代码,v8特定处理代码,以及大量注释
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
if (g_first_chance_handler_ != nullptr &&
g_first_chance_handler_(sig, info, uc)) {
return;
}
pthread_mutex_lock(&g_handler_stack_mutex_);
struct sigaction cur_handler;
if (sigaction(sig, NULL, &cur_handler) == 0 &&
cur_handler.sa_sigaction == SignalHandler &&
(cur_handler.sa_flags & SA_SIGINFO) == 0) {
sigemptyset(&cur_handler.sa_mask);
sigaddset(&cur_handler.sa_mask, sig);
cur_handler.sa_sigaction = SignalHandler;
cur_handler.sa_flags = SA_ONSTACK | SA_SIGINFO;
if (sigaction(sig, &cur_handler, NULL) == -1) {
InstallDefaultHandler(sig);
}
pthread_mutex_unlock(&g_handler_stack_mutex_);
return;
}
bool handled = false;
for (int i = g_handler_stack_->size() - 1; !handled && i >= 0; --i) {
handled = (*g_handler_stack_)[i]->HandleSignal(sig, info, uc);
}
if (handled) {
InstallDefaultHandler(sig);
} else {
RestoreHandlersLocked();
}
pthread_mutex_unlock(&g_handler_stack_mutex_);
}
SignalHandler函数运行在一个被破坏的线程中,而且还是运行在alternate stack环境中。
在触发崩溃时这个SignalHandler函数将会被调用,SignalHandler主要做了:
- 第一个if是怕外部使用者又使用了signal函数对之前设置的信号做了处理,导致不可必要的崩溃,这个if会具体判断是否是由最开始ExceptionHandler初始化时
sigaction设置的信号处理函数,如果不是将重新设置这个信号的处理方式到正确处理方式。 - 调用
HandleSignal函数 - 进行后续清理工作
SignalHandler里面核心就是调用HandleSignal函数做具体的逻辑处理
ExceptionHandler::HandleSignal 函数
源码文件:src/client/linux/handler/exception_handler.cc:440
bool ExceptionHandler::HandleSignal(int /*sig*/, siginfo_t* info, void* uc) {
if (filter_ && !filter_(callback_context_))
return false;
bool signal_trusted = info->si_code > 0;
bool signal_pid_trusted = info->si_code == SI_USER ||
info->si_code == SI_TKILL;
if (signal_trusted || (signal_pid_trusted && info->si_pid == getpid())) {
sys_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
}
memset(&g_crash_context_, 0, sizeof(g_crash_context_));
memcpy(&g_crash_context_.siginfo, info, sizeof(siginfo_t));
memcpy(&g_crash_context_.context, uc, sizeof(ucontext_t));
ucontext_t* uc_ptr = (ucontext_t*)uc;
if (uc_ptr->uc_mcontext.fpregs) {
memcpy(&g_crash_context_.float_state, uc_ptr->uc_mcontext.fpregs,
sizeof(g_crash_context_.float_state));
}
g_crash_context_.tid = syscall(__NR_gettid);
if (crash_handler_ != NULL) {
if (crash_handler_(&g_crash_context_, sizeof(g_crash_context_),
callback_context_)) {
return true;
}
}
return GenerateDump(&g_crash_context_);
}
HandlSignal时一个非常重要的函数,它和上面的一样运行在一个崩溃环境。
- 调用ExceptionHandler构造时传入的
filter_回调,如果回调返回false将终止后续的处理 - 检查信号是否为可信的,可信将利用
pcrtl设置RP_SETDUMPABLE,表示允许dump生成 - 初始化
g_crash_context_全局变量 - 调用
GenrateDump函数做实际dump生成操作
ExceptionHandler::GenerateDump 函数
源码文件:src/client/linux/handler/exception_handler.cc:498
bool ExceptionHandler::GenerateDump(CrashContext* context) {
if (IsOutOfProcess())
return crash_generation_client_->RequestDump(context, sizeof(*context));
static const unsigned kChildStackSize = 16000;
PageAllocator allocator;
uint8_t* stack = reinterpret_cast<uint8_t*>(allocator.Alloc(kChildStackSize));
if (!stack)
return false;
stack += kChildStackSize;
my_memset(stack - 16, 0, 16);
ThreadArgument thread_arg;
thread_arg.handler = this;
thread_arg.minidump_descriptor = &minidump_descriptor_;
thread_arg.pid = getpid();
thread_arg.context = context;
thread_arg.context_size = sizeof(*context);
if (sys_pipe(fdes) == -1) {
fdes[0] = fdes[1] = -1;
}
const pid_t child = sys_clone(
ThreadEntry, stack, CLONE_FS | CLONE_UNTRACED, &thread_arg, NULL, NULL,
NULL);
if (child == -1) {
sys_close(fdes[0]);
sys_close(fdes[1]);
return false;
}
sys_close(fdes[0]);
sys_prctl(PR_SET_PTRACER, child, 0, 0, 0);
SendContinueSignalToChild();
int status = 0;
const int r = HANDLE_EINTR(sys_waitpid(child, &status, __WALL));
sys_close(fdes[1]);
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
if (callback_)
success = callback_(minidump_descriptor_, callback_context_, success);
return success;
}
这个函数也是一个非常核心的函数,但是它其实呢只干了一件事情,利用clone系统调用建立一个子进程,然后等待子进程终止。
- 创建一块stack内存区域用于
clone函数使用 - 填充
thread_arg参数为了clone函数 - 开启一个管道为了实现子进程和父进程的同步,这里由于子进程在写dump之前需要父进程允许子进程跟踪自己的
ptrace,所以采用了管道来做这个同步,子进程会一直等待父进程设置完ptrace之后再开始后续工作。 - 调用
clone函数,设置处理函数ThreadEntry - 父进程一直等待子进程结束,子进程结束之后,判断子进程运行状态,并且调用
ExceptionHandler里面设置的会调用函数即Minidumpcallback告知运行状态。
ExceptionHandler::ThreadEntry 函数
源码文件:src/client/linux/handler/exception_handler.cc:422
int ExceptionHandler::ThreadEntry(void* arg) {
const ThreadArgument* thread_arg = reinterpret_cast<ThreadArgument*>(arg);
sys_close(thread_arg->handler->fdes[1]);
thread_arg->handler->WaitForContinueSignal();
sys_close(thread_arg->handler->fdes[0]);
return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context,
thread_arg->context_size) == false;
}
ThreadEntry函数实现特别简单,首先等待父进程的ptrace设置完成,然后调用DoDump函数做dump写入操作。
ExceptionHandler::DoDump 函数
源码文件:src/client/linux/handler/exception_handler.cc:601
bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
size_t context_size) {
const bool may_skip_dump =
minidump_descriptor_.skip_dump_if_principal_mapping_not_referenced();
const uintptr_t principal_mapping_address =
minidump_descriptor_.address_within_principal_mapping();
const bool sanitize_stacks = minidump_descriptor_.sanitize_stacks();
if (minidump_descriptor_.IsMicrodumpOnConsole()) {
return google_breakpad::WriteMicrodump(
crashing_process,
context,
context_size,
mapping_list_,
may_skip_dump,
principal_mapping_address,
sanitize_stacks,
*minidump_descriptor_.microdump_extra_info());
}
if (minidump_descriptor_.IsFD()) {
return google_breakpad::WriteMinidump(minidump_descriptor_.fd(),
minidump_descriptor_.size_limit(),
crashing_process,
context,
context_size,
mapping_list_,
app_memory_list_,
may_skip_dump,
principal_mapping_address,
sanitize_stacks);
}
return google_breakpad::WriteMinidump(minidump_descriptor_.path(),
minidump_descriptor_.size_limit(),
crashing_process,
context,
context_size,
mapping_list_,
app_memory_list_,
may_skip_dump,
principal_mapping_address,
sanitize_stacks);
}
DoDump函数实现也非常简单,只是根据minidump_descirptor_描述符类型来做不同的WriteMinidump操作,我们呢现在只关心他是一个path的情况,也就是DoDump的最后一个WriteMinidump函数调用。
目前整个Breakpad的整体流程就已经完成。
整体流程图
在下一篇的源码分析中将会主力分析WriteMinidump