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:

  • descriptorIs aMinidumpDescriptorDescriptor, 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, etc
  • filterIs a callback function that will be used after an exception is raisedbreakpadCalled without doing any real operation, terminates if filter returns falsebreakpadNext move
  • callbackDump 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, etc
  • install_handlerTrue will be written when an exception terminatesminidumpFile that is false will only be called manuallyWriteMinidump“Will be written
  • server_fdinout-processMode when passed to the server fd, inin-processWe pass in negative 1

Constructor flow:

  1. It comes in first by judgmentserver_fdIs it greater than or equal to 0, ifserver_fdGreater than or equal to is equal toout-processWill be initializedcrash_generation_client_object
  2. And the next judgment isin-processAnd 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_.UpdatePathThis line of code simply generates a UUID and concatenates the existing dump directory.
  3. Initialize theg_handler_stack_.g_handler_stack_Is avector<ExceptionHandler*>.ExcetipnHandlerThere can be multiple instances of “, so use a container to store them.
  4. judgeinstall_handlerIs it true? True calls two functionsInstallAlternateStackLocked.InstallHandlersLocaledAnd then we will analyze these two functions
  5. 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:

  1. Allocate a block of memory toalternate signal stackuse
  2. usesigaltstackSystem call Settingsalternate signal stackAnd stack size
  3. Pass when setting up the Signal Handler (SIGAction API)SA_ONSTACKFlag 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.

  1. Retain all previous old processing handlers and return if retention fails
  2. Block all signals triggered by kExcetpionSignals (kExceptionSignals =) during a signal processing handlerSIGSEGV.SIGABRT.SIGFPE.SIGILL.SIGBUS.SIGTRAP).
  3. Set up theSignalHandlerGo to sa.sa_sigAction and set sa.sa_flags toSA_ONSTACK | SIGINFOAnd set all kExcetptionSignals signal handlers to sa.sa_sigAction, sa.sa_flags. Sa.sa_flags in hereSA_ONSTACKFlag tells the system that this handler uses alternate stack, andSA_SIGINFOFlag 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:

  1. If (ExceptionHandler); if (ExceptionHandler); if (ExceptionHandler)sigactionSet the signal processing function, if not resets the signal processing mode to the correct processing mode.
  2. callHandleSignalfunction
  3. 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.

  1. Passed in when the ExceptionHandler construct is calledfilter_Callback, which terminates subsequent processing if it returns false
  2. Check if the signal is trusted and the trust will be usedpcrtlSet up theRP_SETDUMPABLE, indicating that dump is allowed to be generated
  3. Initialize theg_crash_context_The global variable
  4. callGenrateDumpThe 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.

  1. Create a stack memory area forcloneFunction USES
  2. fillthread_argParameters in order toclonefunction
  3. 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 dumpptrace, so pipes are used to do this synchronization, and the child will wait for the parent to finish setting upptraceThen you can start the follow-up work.
  4. callcloneFunction, sets the handler functionThreadEntry
  5. 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 theExceptionHandlerThe set inside will call the function that isMinidumpcallbackTell 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