Google-Breadpad Linux
preface
To read this article, you need to have a brief understanding of how BreakPad works and how to use it. This section was covered in the google-BreakPad – Use section in the previous document
The overall profile
This document will start with the breakpad client source code. In part 1, we will not analyze how to write the dump file. Instead, we will analyze the entire breakpad execution process
Source code analysis
- Source code analysis
- ExceptionHandler constructor
- InstallHandlersLocaled function
- ExceptionHandler: : InstallHandlersLocked function
- ExceptionHandler: : SignalHandler function
- ExceptionHandler: : HandleSignal function
- ExceptionHandler: : GenerateDump function
- ExceptionHandler: : ThreadEntry function
- ExceptionHandler: : DoDump function
- Overall flow chart
ExceptionHandler constructor
Source code files: SRC/client/Linux/handler/exception_handler. Cc: 219
// The source code will remove some useless code, such as Android specific handling code, V8 specific handling code, and lots of comments
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(a);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(a);InstallHandlersLocked(a); } g_handler_stack_->push_back(this);
pthread_mutex_unlock(&g_handler_stack_mutex_);
}
Copy the code
Parameter Description:
descriptor
Is aMinidumpDescriptor
Descriptor, which can describe the form in which you dump writes, such as adoptmicrodump
(minimum dump information), or pass in a file descriptor, socket socket, dump final input file name, etcfilter
Is a callback function that will be used after an exception is raisedbreakpad
Called without doing any real operation, terminates if filter returns falsebreakpad
Next movecallback
Dump is also a callback function, but it is called after a write to dump, and returns some write results, such as whether dumper was successfully generated, where is the generation path, etcinstall_handler
True will be written when an exception terminatesminidump
File that is false will only be called manuallyWriteMinidump
“Will be writtenserver_fd
inout-process
Mode when passed to the server fd, inin-process
We pass in negative 1
Constructor flow:
- It comes in first by judgment
server_fd
Is it greater than or equal to 0, ifserver_fd
Greater than or equal to is equal toout-process
Will be initializedcrash_generation_client_
object - And the next judgment is
in-process
And the minidump descriptor is not in the form of a file descriptor and the minidump write is not in the form of microdump, which means that the minidump descriptor is initialized in directory mode, and thenminidump_descriptor_.UpdatePath
This line of code simply generates a UUID and concatenates the existing dump directory. - Initialize the
g_handler_stack_
.g_handler_stack_
Is avector<ExceptionHandler*>
.ExcetipnHandler
There can be multiple instances of “, so use a container to store them. - judge
install_handler
Is it true? True calls two functionsInstallAlternateStackLocked
.InstallHandlersLocaled
And then we will analyze these two functions - Insert the current ExceptionHandler object into g_handler_STACK_
The entire initialization process of the ExceptionHandler constructor is not complicated, and it does only a few things when the out-of-process case is removed.
InstallHandlersLocaled function
Source code files: SRC/client/Linux/handler/exception_handler. Cc: 131
// The source code will remove some useless code, such as Android specific handling code, V8 specific handling code, and lots of comments
void InstallAlternateStackLocked(a) {
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; }}Copy the code
InstallAlternateStackLocaked this function is only doing one thing, to establish an alternative stack, for use in subsequent signal processing handler, why do you want to build an alternative stack? The reason is that this signal may be generated due to a stack overflow, so it is necessary to use an alternatvie stack when executing Signal’s handler
There are three steps to establish and use alternate Signal Stack:
- Allocate a block of memory to
alternate signal stack
use - use
sigaltstack
System call Settingsalternate signal stack
And stack size - Pass when setting up the Signal Handler (SIGAction API)
SA_ONSTACK
Flag tells the system that this handler uses alternate Stack
Read sigaltStack (2) – Linux Manual Page (man7.org)
InstallAlternateStackLocaked made alternate signal stack of the first two steps, the final step will be done in InstallHandlersLocakd function.
ExceptionHandler: : InstallHandlersLocked function
Source code files: SRC/client/Linux/handler/exception_handler. Cc: 275
// The source code will remove some useless code, such as Android specific handling code, V8 specific handling code, and lots of comments
bool ExceptionHandler::InstallHandlersLocked(a) {
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) {
// Ignore errors
}
}
handlers_installed = true;
return true;
}
Copy the code
The InstallHandlersLocaked function sets the signal to be captured to handle handlers and completes the last step of the alternate Signal stack above.
- Retain all previous old processing handlers and return if retention fails
- Block all signals triggered by kExcetpionSignals (kExceptionSignals =) during a signal processing handler
SIGSEGV
.SIGABRT
.SIGFPE
.SIGILL
.SIGBUS
.SIGTRAP
). - Set up the
SignalHandler
Go to sa.sa_sigAction and set sa.sa_flags toSA_ONSTACK | SIGINFO
And set all kExcetptionSignals signal handlers to sa.sa_sigAction, sa.sa_flags. Sa.sa_flags in hereSA_ONSTACK
Flag tells the system that this handler uses alternate stack, andSA_SIGINFO
Flag tells the system to use sa_sigaction instead of the sa.sa_handler parameter.
This completes the initialization of ExceptionHandler. If any signal in kExceptionSignals is triggered, it will go to SignalHandler. The entire initialization of the preceding ExceptionHandler function was run in a normal environment, whereas the subsequent SignalHandler function started in a crash environment
ExceptionHandler: : SignalHandler function
Source code files: SRC/client/Linux/handler/exception_handler. Cc: 275
// The source code will remove some useless code, such as Android specific handling code, V8 specific handling code, and lots of comments
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(a); }pthread_mutex_unlock(&g_handler_stack_mutex_);
}
Copy the code
The SignalHandler function runs in a corrupted thread and in the alternate Stack.
This SignalHandler function will be called when a crash is triggered. SignalHandler mainly does:
- If (ExceptionHandler); if (ExceptionHandler); if (ExceptionHandler)
sigaction
Set the signal processing function, if not resets the signal processing mode to the correct processing mode. - call
HandleSignal
function - Follow-up cleaning
The core of SignalHandler is to call the HandleSignal function to do the specific logic processing
ExceptionHandler: : HandleSignal function
Source code files: 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_);
}
Copy the code
HandlSignal is a very important function that runs in a crash environment like the one above.
- Passed in when the ExceptionHandler construct is called
filter_
Callback, which terminates subsequent processing if it returns false - Check if the signal is trusted and the trust will be used
pcrtl
Set up theRP_SETDUMPABLE
, indicating that dump is allowed to be generated - Initialize the
g_crash_context_
The global variable - call
GenrateDump
The dump function does the actual dump generation
ExceptionHandler: : GenerateDump function
Source code files: 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(a); 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(a);int status = 0;
const int r = HANDLE_EINTR(sys_waitpid(child, &status, __WALL));
sys_close(fdes[1]);
boolsuccess = r ! =- 1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
if (callback_)
success = callback_(minidump_descriptor_, callback_context_, success);
return success;
}
Copy the code
This function is also a very core function, but it really only does one thing: create a child process using the Clone system call and wait for the child process to terminate.
- Create a stack memory area for
clone
Function USES - fill
thread_arg
Parameters in order toclone
function - In order to synchronize the child process with the parent process, the parent process needs the parent process to allow the child process to trace its own data before writing dump
ptrace
, so pipes are used to do this synchronization, and the child will wait for the parent to finish setting upptrace
Then you can start the follow-up work. - call
clone
Function, sets the handler functionThreadEntry
- The parent process waits for the child process to finish. After the child process finishes, the parent process checks the running status of the child process and invokes the
ExceptionHandler
The set inside will call the function that isMinidumpcallback
Tell the running status.
ExceptionHandler: : ThreadEntry function
Source code files: 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(a);sys_close(thread_arg->handler->fdes[0]);
return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context,
thread_arg->context_size) == false;
}
Copy the code
The ThreadEntry function is particularly simple to implement. It waits for the parent to complete the ptrace setting and then calls the DoDump function to write the dump.
ExceptionHandler: : DoDump function
Source code files: 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(a);const uintptr_t principal_mapping_address =
minidump_descriptor_.address_within_principal_mapping(a);const bool sanitize_stacks = minidump_descriptor_.sanitize_stacks(a);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);
}
Copy the code
The DoDump function is also very simple to implement, with different WriteMinidump operations based on the minidump_descirptor_ descriptor type. We only care if it is a path, which is the last WriteMinidump function call of DoDump.
The entire Breakpad process is now complete.
Overall flow chart
The WriteMinidump will be the focus of the next source code analysis