diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/crashreporter/breakpad-client/linux/handler | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/crashreporter/breakpad-client/linux/handler')
8 files changed, 2958 insertions, 0 deletions
diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc new file mode 100644 index 0000000000..92a0443d79 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc @@ -0,0 +1,876 @@ +// Copyright (c) 2010 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The ExceptionHandler object installs signal handlers for a number of +// signals. We rely on the signal handler running on the thread which crashed +// in order to identify it. This is true of the synchronous signals (SEGV etc), +// but not true of ABRT. Thus, if you send ABRT to yourself in a program which +// uses ExceptionHandler, you need to use tgkill to direct it to the current +// thread. +// +// The signal flow looks like this: +// +// SignalHandler (uses a global stack of ExceptionHandler objects to find +// | one to handle the signal. If the first rejects it, try +// | the second etc...) +// V +// HandleSignal ----------------------------| (clones a new process which +// | | shares an address space with +// (wait for cloned | the crashed process. This +// process) | allows us to ptrace the crashed +// | | process) +// V V +// (set signal handler to ThreadEntry (static function to bounce +// SIG_DFL and rethrow, | back into the object) +// killing the crashed | +// process) V +// DoDump (writes minidump) +// | +// V +// sys_exit +// + +// This code is a little fragmented. Different functions of the ExceptionHandler +// class run in a number of different contexts. Some of them run in a normal +// context and are easy to code, others run in a compromised context and the +// restrictions at the top of minidump_writer.cc apply: no libc and use the +// alternative malloc. Each function should have comment above it detailing the +// context which it runs in. + +#include "linux/handler/exception_handler.h" + +#include <errno.h> +#include <fcntl.h> +#include <linux/limits.h> +#include <pthread.h> +#include <sched.h> +#include <signal.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <sys/ucontext.h> +#include <sys/user.h> +#include <ucontext.h> + +#include <algorithm> +#include <utility> +#include <vector> + +#include "common/basictypes.h" +#include "common/linux/breakpad_getcontext.h" +#include "common/linux/linux_libc_support.h" +#include "common/memory_allocator.h" +#include "linux/log/log.h" +#include "linux/microdump_writer/microdump_writer.h" +#include "linux/minidump_writer/linux_dumper.h" +#include "linux/minidump_writer/minidump_writer.h" +#include "common/linux/eintr_wrapper.h" +#include "third_party/lss/linux_syscall_support.h" +#if defined(MOZ_OXIDIZED_BREAKPAD) +#include "nsString.h" +#include "mozilla/toolkit/crashreporter/rust_minidump_writer_linux_ffi_generated.h" +#endif + +#ifdef MOZ_PHC +#include "PHC.h" +#endif + +#if defined(__ANDROID__) +#include "linux/sched.h" +#endif + +#ifndef PR_SET_PTRACER +#define PR_SET_PTRACER 0x59616d61 +#endif + +#define SKIP_SIGILL(sig) if (g_skip_sigill_ && (sig == SIGILL)) continue; + +namespace google_breakpad { + +namespace { +// The list of signals which we consider to be crashes. The default action for +// all these signals must be Core (see man 7 signal) because we rethrow the +// signal after handling it and expect that it'll be fatal. +const int kExceptionSignals[] = { + SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTRAP +}; +const int kNumHandledSignals = + sizeof(kExceptionSignals) / sizeof(kExceptionSignals[0]); +struct sigaction old_handlers[kNumHandledSignals]; +bool handlers_installed = false; + +// InstallAlternateStackLocked will store the newly installed stack in new_stack +// and (if it exists) the previously installed stack in old_stack. +stack_t old_stack; +stack_t new_stack; +bool stack_installed = false; + +// Create an alternative stack to run the signal handlers on. This is done since +// the signal might have been caused by a stack overflow. +// Runs before crashing: normal context. +void InstallAlternateStackLocked() { + if (stack_installed) + return; + + memset(&old_stack, 0, sizeof(old_stack)); + memset(&new_stack, 0, sizeof(new_stack)); + + // SIGSTKSZ may be too small to prevent the signal handlers from overrunning + // the alternative stack. Ensure that the size of the alternative stack is + // large enough. + static const size_t kSigStackSize = std::max(size_t(16384), size_t(SIGSTKSZ)); + + // Only set an alternative stack if there isn't already one, or if the current + // one is too small. + 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; + } +} + +// Runs before crashing: normal context. +void RestoreAlternateStackLocked() { + if (!stack_installed) + return; + + stack_t current_stack; + if (sys_sigaltstack(NULL, ¤t_stack) == -1) + return; + + // Only restore the old_stack if the current alternative stack is the one + // installed by the call to InstallAlternateStackLocked. + if (current_stack.ss_sp == new_stack.ss_sp) { + if (old_stack.ss_sp) { + if (sys_sigaltstack(&old_stack, NULL) == -1) + return; + } else { + stack_t disable_stack; + disable_stack.ss_flags = SS_DISABLE; + if (sys_sigaltstack(&disable_stack, NULL) == -1) + return; + } + } + + free(new_stack.ss_sp); + stack_installed = false; +} + +void InstallDefaultHandler(int sig) { +#if defined(__ANDROID__) + // Android L+ expose signal and sigaction symbols that override the system + // ones. There is a bug in these functions where a request to set the handler + // to SIG_DFL is ignored. In that case, an infinite loop is entered as the + // signal is repeatedly sent to breakpad's signal handler. + // To work around this, directly call the system's sigaction. + struct kernel_sigaction sa; + memset(&sa, 0, sizeof(sa)); + sys_sigemptyset(&sa.sa_mask); + sa.sa_handler_ = SIG_DFL; + sa.sa_flags = SA_RESTART; + sys_rt_sigaction(sig, &sa, NULL, sizeof(kernel_sigset_t)); +#else + signal(sig, SIG_DFL); +#endif +} + +// The global exception handler stack. This is needed because there may exist +// multiple ExceptionHandler instances in a process. Each will have itself +// registered in this stack. +std::vector<ExceptionHandler*>* g_handler_stack_ = NULL; +pthread_mutex_t g_handler_stack_mutex_ = PTHREAD_MUTEX_INITIALIZER; + +// sizeof(CrashContext) can be too big w.r.t the size of alternatate stack +// for SignalHandler(). Keep the crash context as a .bss field. Exception +// handlers are serialized by the |g_handler_stack_mutex_| and at most one at a +// time can use |g_crash_context_|. +ExceptionHandler::CrashContext g_crash_context_; + +FirstChanceHandler g_first_chance_handler_ = nullptr; +bool g_skip_sigill_ = false; +} // namespace + +// Runs before crashing: normal context. +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) { + + g_skip_sigill_ = getenv("MOZ_DISABLE_EXCEPTION_HANDLER_SIGILL") ? true : false; + if (server_fd >= 0) + crash_generation_client_.reset(CrashGenerationClient::TryCreate(server_fd)); + + if (!IsOutOfProcess() && !minidump_descriptor_.IsFD() && + !minidump_descriptor_.IsMicrodumpOnConsole()) + minidump_descriptor_.UpdatePath(); + +#if defined(__ANDROID__) + if (minidump_descriptor_.IsMicrodumpOnConsole()) + logger::initializeCrashLogWriter(); +#endif + + pthread_mutex_lock(&g_handler_stack_mutex_); + + // Pre-fault the crash context struct. This is to avoid failing due to OOM + // if handling an exception when the process ran out of virtual memory. + 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_); +} + +// Runs before crashing: normal context. +ExceptionHandler::~ExceptionHandler() { + pthread_mutex_lock(&g_handler_stack_mutex_); + std::vector<ExceptionHandler*>::iterator handler = + std::find(g_handler_stack_->begin(), g_handler_stack_->end(), this); + g_handler_stack_->erase(handler); + if (g_handler_stack_->empty()) { + delete g_handler_stack_; + g_handler_stack_ = NULL; + RestoreAlternateStackLocked(); + RestoreHandlersLocked(); + } + pthread_mutex_unlock(&g_handler_stack_mutex_); +} + +// Runs before crashing: normal context. +// static +bool ExceptionHandler::InstallHandlersLocked() { + if (handlers_installed) + return false; + + // Fail if unable to store all the old handlers. + for (int i = 0; i < kNumHandledSignals; ++i) { + SKIP_SIGILL(kExceptionSignals[i]); + if (sigaction(kExceptionSignals[i], NULL, &old_handlers[i]) == -1) + return false; + } + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + + // Mask all exception signals when we're handling one of them. + for (int i = 0; i < kNumHandledSignals; ++i) { + SKIP_SIGILL(kExceptionSignals[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) { + SKIP_SIGILL(kExceptionSignals[i]); + if (sigaction(kExceptionSignals[i], &sa, NULL) == -1) { + // At this point it is impractical to back out changes, and so failure to + // install a signal is intentionally ignored. + } + } + handlers_installed = true; + return true; +} + +// This function runs in a compromised context: see the top of the file. +// Runs on the crashing thread. +// static +void ExceptionHandler::RestoreHandlersLocked() { + if (!handlers_installed) + return; + + for (int i = 0; i < kNumHandledSignals; ++i) { + SKIP_SIGILL(kExceptionSignals[i]); + if (sigaction(kExceptionSignals[i], &old_handlers[i], NULL) == -1) { + InstallDefaultHandler(kExceptionSignals[i]); + } + } + handlers_installed = false; +} + +// void ExceptionHandler::set_crash_handler(HandlerCallback callback) { +// crash_handler_ = callback; +// } + +// This function runs in a compromised context: see the top of the file. +// Runs on the crashing thread. +// static +void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { + + // Give the first chance handler a chance to recover from this signal + // + // This is primarily used by V8. V8 uses guard regions to guarantee memory + // safety in WebAssembly. This means some signals might be expected if they + // originate from Wasm code while accessing the guard region. We give V8 the + // chance to handle and recover from these signals first. + if (g_first_chance_handler_ != nullptr && + g_first_chance_handler_(sig, info, uc)) { + return; + } + + // All the exception signals are blocked at this point. + pthread_mutex_lock(&g_handler_stack_mutex_); + + // Sometimes, Breakpad runs inside a process where some other buggy code + // saves and restores signal handlers temporarily with 'signal' + // instead of 'sigaction'. This loses the SA_SIGINFO flag associated + // with this function. As a consequence, the values of 'info' and 'uc' + // become totally bogus, generally inducing a crash. + // + // The following code tries to detect this case. When it does, it + // resets the signal handlers with sigaction + SA_SIGINFO and returns. + // This forces the signal to be thrown again, but this time the kernel + // will call the function with the right arguments. + struct sigaction cur_handler; + if (sigaction(sig, NULL, &cur_handler) == 0 && + cur_handler.sa_sigaction == SignalHandler && + (cur_handler.sa_flags & SA_SIGINFO) == 0) { + // Reset signal handler with the right flags. + 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) { + // When resetting the handler fails, try to reset the + // default one to avoid an infinite loop here. + 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); + } + + // Upon returning from this signal handler, sig will become unmasked and then + // it will be retriggered. If one of the ExceptionHandlers handled it + // successfully, restore the default handler. Otherwise, restore the + // previously installed handler. Then, when the signal is retriggered, it will + // be delivered to the appropriate handler. + if (handled) { + InstallDefaultHandler(sig); + } else { + RestoreHandlersLocked(); + } + + pthread_mutex_unlock(&g_handler_stack_mutex_); + + // info->si_code <= 0 iff SI_FROMUSER (SI_FROMKERNEL otherwise). + if (info->si_code <= 0 || sig == SIGABRT) { + // This signal was triggered by somebody sending us the signal with kill(). + // In order to retrigger it, we have to queue a new signal by calling + // kill() ourselves. The special case (si_pid == 0 && sig == SIGABRT) is + // due to the kernel sending a SIGABRT from a user request via SysRQ. + if (sys_tgkill(getpid(), syscall(__NR_gettid), sig) < 0) { + // If we failed to kill ourselves (e.g. because a sandbox disallows us + // to do so), we instead resort to terminating our process. This will + // result in an incorrect exit code. + _exit(1); + } + } else { + // This was a synchronous signal triggered by a hard fault (e.g. SIGSEGV). + // No need to reissue the signal. It will automatically trigger again, + // when we return from the signal handler. + } +} + +struct ThreadArgument { + pid_t pid; // the crashing process + const MinidumpDescriptor* minidump_descriptor; + ExceptionHandler* handler; + const void* context; // a CrashContext structure + size_t context_size; +}; + +// This is the entry function for the cloned process. We are in a compromised +// context here: see the top of the file. +// static +int ExceptionHandler::ThreadEntry(void *arg) { + const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg); + + // Close the write end of the pipe. This allows us to fail if the parent dies + // while waiting for the continue signal. + sys_close(thread_arg->handler->fdes[1]); + + // Block here until the crashing process unblocks us when + // we're allowed to use ptrace + 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; +} + +#ifdef MOZ_PHC +static void GetPHCAddrInfo(siginfo_t* siginfo, + mozilla::phc::AddrInfo* addr_info) { + // Is this a crash involving a PHC allocation? + if (siginfo->si_signo == SIGSEGV || siginfo->si_signo == SIGBUS) { + mozilla::phc::IsPHCAllocation(siginfo->si_addr, addr_info); + } +} +#endif + +// This function runs in a compromised context: see the top of the file. +// Runs on the crashing thread. +bool ExceptionHandler::HandleSignal(int /*sig*/, siginfo_t* info, void* uc) { + mozilla::phc::AddrInfo* addr_info = nullptr; +#ifdef MOZ_PHC + addr_info = &mozilla::phc::gAddrInfo; + GetPHCAddrInfo(info, addr_info); +#endif + + if (filter_ && !filter_(callback_context_)) + return false; + + // Allow ourselves to be dumped if the signal is trusted. + 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); + } + + // Fill in all the holes in the struct to make Valgrind happy. + 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)); +#if defined(__aarch64__) + ucontext_t* uc_ptr = (ucontext_t*)uc; + struct fpsimd_context* fp_ptr = + (struct fpsimd_context*)&uc_ptr->uc_mcontext.__reserved; + if (fp_ptr->head.magic == FPSIMD_MAGIC) { + memcpy(&g_crash_context_.float_state, fp_ptr, + sizeof(g_crash_context_.float_state)); + } +#elif !defined(__ARM_EABI__) && !defined(__mips__) + // FP state is not part of user ABI on ARM Linux. + // In case of MIPS Linux FP state is already part of ucontext_t + // and 'float_state' is not a member of CrashContext. + 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)); + } +#endif + 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_, addr_info); +} + +// This is a public interface to HandleSignal that allows the client to +// generate a crash dump. This function may run in a compromised context. +bool ExceptionHandler::SimulateSignalDelivery(int sig) { + siginfo_t siginfo = {}; + // Mimic a trusted signal to allow tracing the process (see + // ExceptionHandler::HandleSignal(). + siginfo.si_code = SI_USER; + siginfo.si_pid = getpid(); + ucontext_t context; + getcontext(&context); + return HandleSignal(sig, &siginfo, &context); +} + +// This function may run in a compromised context: see the top of the file. +bool ExceptionHandler::GenerateDump( + CrashContext *context, const mozilla::phc::AddrInfo* addr_info) { + if (IsOutOfProcess()) { + bool success = + crash_generation_client_->RequestDump(context, sizeof(*context)); + + if (callback_) { + success = + callback_(minidump_descriptor_, callback_context_, addr_info, success); + } + + return success; + } + + // Allocating too much stack isn't a problem, and better to err on the side + // of caution than smash it into random locations. + static const unsigned kChildStackSize = 16000; + PageAllocator allocator; + uint8_t* stack = reinterpret_cast<uint8_t*>(allocator.Alloc(kChildStackSize)); + if (!stack) + return false; + // clone() needs the top-most address. (scrub just to be safe) + 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); + + // We need to explicitly enable ptrace of parent processes on some + // kernels, but we need to know the PID of the cloned process before we + // can do this. Create a pipe here which we can use to block the + // cloned process after creating it, until we have explicitly enabled ptrace + if (sys_pipe(fdes) == -1) { + // Creating the pipe failed. We'll log an error but carry on anyway, + // as we'll probably still get a useful crash report. All that will happen + // is the write() and read() calls will fail with EBADF + static const char no_pipe_msg[] = "ExceptionHandler::GenerateDump " + "sys_pipe failed:"; + logger::write(no_pipe_msg, sizeof(no_pipe_msg) - 1); + logger::write(strerror(errno), strlen(strerror(errno))); + logger::write("\n", 1); + + // Ensure fdes[0] and fdes[1] are invalid file descriptors. + 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; + } + + if (child != 0) { + static const char clonedMsg[] = + "ExceptionHandler::GenerateDump cloned child "; + char pidMsg[32] = {}; + + unsigned int pidLen = my_uint_len(child); + my_uitos(pidMsg, child, pidLen); + + logger::write(clonedMsg, my_strlen(clonedMsg)); + logger::write(pidMsg, pidLen); + logger::write("\n", 1); + } else { + static const char childMsg[] = + "ExceptionHandler::GenerateDump I'm the child\n"; + logger::write(childMsg, my_strlen(childMsg)); + } + + // Close the read end of the pipe. + sys_close(fdes[0]); + // Allow the child to ptrace us + 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]); + + if (r == -1) { + static const char msg[] = "ExceptionHandler::GenerateDump waitpid failed:"; + logger::write(msg, sizeof(msg) - 1); + logger::write(strerror(errno), strlen(strerror(errno))); + logger::write("\n", 1); + } + + bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; + if (callback_) + success = + callback_(minidump_descriptor_, callback_context_, addr_info, success); + return success; +} + +// This function runs in a compromised context: see the top of the file. +void ExceptionHandler::SendContinueSignalToChild() { + static const char okToContinueMessage = 'a'; + int r; + r = HANDLE_EINTR(sys_write(fdes[1], &okToContinueMessage, sizeof(char))); + if (r == -1) { + static const char msg[] = "ExceptionHandler::SendContinueSignalToChild " + "sys_write failed:"; + logger::write(msg, sizeof(msg) - 1); + logger::write(strerror(errno), strlen(strerror(errno))); + logger::write("\n", 1); + } + + const char* msg = "ExceptionHandler::SendContinueSignalToChild sent continue signal to child\n"; + logger::write(msg, my_strlen(msg)); +} + +// This function runs in a compromised context: see the top of the file. +// Runs on the cloned process. +void ExceptionHandler::WaitForContinueSignal() { + int r; + char receivedMessage; + + const char* waitMsg = "ExceptionHandler::WaitForContinueSignal waiting for continue signal...\n"; + logger::write(waitMsg, my_strlen(waitMsg)); + + r = HANDLE_EINTR(sys_read(fdes[0], &receivedMessage, sizeof(char))); + if (r == -1) { + static const char msg[] = "ExceptionHandler::WaitForContinueSignal " + "sys_read failed:"; + logger::write(msg, sizeof(msg) - 1); + logger::write(strerror(errno), strlen(strerror(errno))); + logger::write("\n", 1); + } +} + +// This function runs in a compromised context: see the top of the file. +// Runs on the cloned process. +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); +} + +// static +bool ExceptionHandler::WriteMinidump(const string& dump_path, + MinidumpCallback callback, + void* callback_context) { + MinidumpDescriptor descriptor(dump_path); + ExceptionHandler eh(descriptor, NULL, callback, callback_context, false, -1); + return eh.WriteMinidump(); +} + +// In order to making using EBP to calculate the desired value for ESP +// a valid operation, ensure that this function is compiled with a +// frame pointer using the following attribute. This attribute +// is supported on GCC but not on clang. +#if defined(__i386__) && defined(__GNUC__) && !defined(__clang__) +__attribute__((optimize("no-omit-frame-pointer"))) +#endif +bool ExceptionHandler::WriteMinidump() { + if (!IsOutOfProcess() && !minidump_descriptor_.IsFD() && + !minidump_descriptor_.IsMicrodumpOnConsole()) { + // Update the path of the minidump so that this can be called multiple times + // and new files are created for each minidump. This is done before the + // generation happens, as clients may want to access the MinidumpDescriptor + // after this call to find the exact path to the minidump file. + minidump_descriptor_.UpdatePath(); + } else if (minidump_descriptor_.IsFD()) { + // Reposition the FD to its beginning and resize it to get rid of the + // previous minidump info. + lseek(minidump_descriptor_.fd(), 0, SEEK_SET); + ignore_result(ftruncate(minidump_descriptor_.fd(), 0)); + } + + // Allow this process to be dumped. + sys_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + + CrashContext context; + int getcontext_result = getcontext(&context.context); + if (getcontext_result) + return false; + +#if defined(__i386__) + // In CPUFillFromUContext in minidumpwriter.cc the stack pointer is retrieved + // from REG_UESP instead of from REG_ESP. REG_UESP is the user stack pointer + // and it only makes sense when running in kernel mode with a different stack + // pointer. When WriteMiniDump is called during normal processing REG_UESP is + // zero which leads to bad minidump files. + if (!context.context.uc_mcontext.gregs[REG_UESP]) { + // If REG_UESP is set to REG_ESP then that includes the stack space for the + // CrashContext object in this function, which is about 128 KB. Since the + // Linux dumper only records 32 KB of stack this would mean that nothing + // useful would be recorded. A better option is to set REG_UESP to REG_EBP, + // perhaps with a small negative offset in case there is any code that + // objects to them being equal. + context.context.uc_mcontext.gregs[REG_UESP] = + context.context.uc_mcontext.gregs[REG_EBP] - 16; + // The stack saving is based off of REG_ESP so it must be set to match the + // new REG_UESP. + context.context.uc_mcontext.gregs[REG_ESP] = + context.context.uc_mcontext.gregs[REG_UESP]; + } +#endif + +#if !defined(__ARM_EABI__) && !defined(__aarch64__) && !defined(__mips__) + // FPU state is not part of ARM EABI ucontext_t. + memcpy(&context.float_state, context.context.uc_mcontext.fpregs, + sizeof(context.float_state)); +#endif + context.tid = sys_gettid(); + + // Add an exception stream to the minidump for better reporting. + memset(&context.siginfo, 0, sizeof(context.siginfo)); + context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED; +#if defined(__i386__) + context.siginfo.si_addr = + reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_EIP]); +#elif defined(__x86_64__) + context.siginfo.si_addr = + reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_RIP]); +#elif defined(__arm__) + context.siginfo.si_addr = + reinterpret_cast<void*>(context.context.uc_mcontext.arm_pc); +#elif defined(__aarch64__) + context.siginfo.si_addr = + reinterpret_cast<void*>(context.context.uc_mcontext.pc); +#elif defined(__mips__) + context.siginfo.si_addr = + reinterpret_cast<void*>(context.context.uc_mcontext.pc); +#else +#error "This code has not been ported to your platform yet." +#endif + + // nullptr here for phc::AddrInfo* is ok because this is not a crash. + return GenerateDump(&context, nullptr); +} + +void ExceptionHandler::AddMappingInfo(const string& name, + const wasteful_vector<uint8_t>& identifier, + uintptr_t start_address, + size_t mapping_size, + size_t file_offset) { + MappingInfo info; + info.start_addr = start_address; + info.size = mapping_size; + info.offset = file_offset; + strncpy(info.name, name.c_str(), sizeof(info.name) - 1); + info.name[sizeof(info.name) - 1] = '\0'; + + MappingEntry mapping; + mapping.first = info; + mapping.second.assign(identifier.begin(), identifier.end()); + mapping_list_.push_back(mapping); +} + +void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) { + AppMemoryList::iterator iter = + std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr); + if (iter != app_memory_list_.end()) { + // Don't allow registering the same pointer twice. + return; + } + + AppMemory app_memory; + app_memory.ptr = ptr; + app_memory.length = length; + app_memory_list_.push_back(app_memory); +} + +void ExceptionHandler::UnregisterAppMemory(void* ptr) { + AppMemoryList::iterator iter = + std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr); + if (iter != app_memory_list_.end()) { + app_memory_list_.erase(iter); + } +} + +// static +bool ExceptionHandler::WriteMinidumpForChild(pid_t child, + pid_t child_blamed_thread, + const string& dump_path, + MinidumpCallback callback, + void* callback_context) { + // This function is not run in a compromised context. + MinidumpDescriptor descriptor(dump_path); + descriptor.UpdatePath(); +#if defined(MOZ_OXIDIZED_BREAKPAD) + nsCString error_msg; + if (!write_minidump_linux(descriptor.path(), child, child_blamed_thread, &error_msg)) + return false; +#else + if (!google_breakpad::WriteMinidump(descriptor.path(), + child, + child_blamed_thread)) + return false; +#endif + + // nullptr here for phc::AddrInfo* is ok because this is not a crash. + return callback ? callback(descriptor, callback_context, nullptr, true) + : true; +} + +void SetFirstChanceExceptionHandler(FirstChanceHandler callback) { + g_first_chance_handler_ = callback; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.h b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.h new file mode 100644 index 0000000000..46ad399419 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.h @@ -0,0 +1,289 @@ +// Copyright (c) 2010 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ +#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ + +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <sys/ucontext.h> + +#include <string> + +#include "linux/crash_generation/crash_generation_client.h" +#include "linux/handler/minidump_descriptor.h" +#include "linux/minidump_writer/minidump_writer.h" +#include "common/scoped_ptr.h" +#include "common/using_std_string.h" +#include "google_breakpad/common/minidump_format.h" + +#ifdef MOZ_PHC +#include "PHC.h" +#else +namespace mozilla { namespace phc { class AddrInfo {}; } } +#endif + +namespace google_breakpad { + +// ExceptionHandler +// +// ExceptionHandler can write a minidump file when an exception occurs, +// or when WriteMinidump() is called explicitly by your program. +// +// To have the exception handler write minidumps when an uncaught exception +// (crash) occurs, you should create an instance early in the execution +// of your program, and keep it around for the entire time you want to +// have crash handling active (typically, until shutdown). +// (NOTE): There should be only be one this kind of exception handler +// object per process. +// +// If you want to write minidumps without installing the exception handler, +// you can create an ExceptionHandler with install_handler set to false, +// then call WriteMinidump. You can also use this technique if you want to +// use different minidump callbacks for different call sites. +// +// In either case, a callback function is called when a minidump is written, +// which receives the full path or file descriptor of the minidump. The +// caller can collect and write additional application state to that minidump, +// and launch an external crash-reporting application. +// +// Caller should try to make the callbacks as crash-friendly as possible, +// it should avoid use heap memory allocation as much as possible. + +class ExceptionHandler { + public: + // A callback function to run before Breakpad performs any substantial + // processing of an exception. A FilterCallback is called before writing + // a minidump. |context| is the parameter supplied by the user as + // callback_context when the handler was created. + // + // If a FilterCallback returns true, Breakpad will continue processing, + // attempting to write a minidump. If a FilterCallback returns false, + // Breakpad will immediately report the exception as unhandled without + // writing a minidump, allowing another handler the opportunity to handle it. + typedef bool (*FilterCallback)(void *context); + + // A callback function to run after the minidump has been written. + // |descriptor| contains the file descriptor or file path containing the + // minidump. |context| is the parameter supplied by the user as + // callback_context when the handler was created. |succeeded| indicates + // whether a minidump file was successfully written. + // + // If an exception occurred and the callback returns true, Breakpad will + // treat the exception as fully-handled, suppressing any other handlers from + // being notified of the exception. If the callback returns false, Breakpad + // will treat the exception as unhandled, and allow another handler to handle + // it. If there are no other handlers, Breakpad will report the exception to + // the system as unhandled, allowing a debugger or native crash dialog the + // opportunity to handle the exception. Most callback implementations + // should normally return the value of |succeeded|, or when they wish to + // not report an exception of handled, false. Callbacks will rarely want to + // return true directly (unless |succeeded| is true). + typedef bool (*MinidumpCallback)(const MinidumpDescriptor& descriptor, + void* context, + const mozilla::phc::AddrInfo* addr_info, + bool succeeded); + + // In certain cases, a user may wish to handle the generation of the minidump + // themselves. In this case, they can install a handler callback which is + // called when a crash has occurred. If this function returns true, no other + // processing of occurs and the process will shortly be crashed. If this + // returns false, the normal processing continues. + typedef bool (*HandlerCallback)(const void* crash_context, + size_t crash_context_size, + void* context); + + // Creates a new ExceptionHandler instance to handle writing minidumps. + // Before writing a minidump, the optional |filter| callback will be called. + // Its return value determines whether or not Breakpad should write a + // minidump. The minidump content will be written to the file path or file + // descriptor from |descriptor|, and the optional |callback| is called after + // writing the dump file, as described above. + // If install_handler is true, then a minidump will be written whenever + // an unhandled exception occurs. If it is false, minidumps will only + // be written when WriteMinidump is called. + // If |server_fd| is valid, the minidump is generated out-of-process. If it + // is -1, in-process generation will always be used. + ExceptionHandler(const MinidumpDescriptor& descriptor, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + bool install_handler, + const int server_fd); + ~ExceptionHandler(); + + const MinidumpDescriptor& minidump_descriptor() const { + return minidump_descriptor_; + } + + void set_minidump_descriptor(const MinidumpDescriptor& descriptor) { + minidump_descriptor_ = descriptor; + } + + void set_crash_handler(HandlerCallback callback) { + crash_handler_ = callback; + } + + void set_crash_generation_client(CrashGenerationClient* client) { + crash_generation_client_.reset(client); + } + + // Writes a minidump immediately. This can be used to capture the execution + // state independently of a crash. + // Returns true on success. + // If the ExceptionHandler has been created with a path, a new file is + // generated for each minidump. The file path can be retrieved in the + // MinidumpDescriptor passed to the MinidumpCallback or by accessing the + // MinidumpDescriptor directly from the ExceptionHandler (with + // minidump_descriptor()). + // If the ExceptionHandler has been created with a file descriptor, the file + // descriptor is repositioned to its beginning and the previous generated + // minidump is overwritten. + // Note that this method is not supposed to be called from a compromised + // context as it uses the heap. + bool WriteMinidump(); + + // Convenience form of WriteMinidump which does not require an + // ExceptionHandler instance. + static bool WriteMinidump(const string& dump_path, + MinidumpCallback callback, + void* callback_context); + + // Write a minidump of |child| immediately. This can be used to + // capture the execution state of |child| independently of a crash. + // Pass a meaningful |child_blamed_thread| to make that thread in + // the child process the one from which a crash signature is + // extracted. + // + // WARNING: the return of this function *must* happen before + // the code that will eventually reap |child| executes. + // Otherwise there's a pernicious race condition in which |child| + // exits, is reaped, another process created with its pid, then that + // new process dumped. + static bool WriteMinidumpForChild(pid_t child, + pid_t child_blamed_thread, + const string& dump_path, + MinidumpCallback callback, + void* callback_context); + + // This structure is passed to minidump_writer.h:WriteMinidump via an opaque + // blob. It shouldn't be needed in any user code. + struct CrashContext { + siginfo_t siginfo; + pid_t tid; // the crashing thread. + ucontext_t context; +#if !defined(__ARM_EABI__) && !defined(__mips__) + // #ifdef this out because FP state is not part of user ABI for Linux ARM. + // In case of MIPS Linux FP state is already part of ucontext_t so + // 'float_state' is not required. + fpstate_t float_state; +#endif + }; + + // Returns whether out-of-process dump generation is used or not. + bool IsOutOfProcess() const { + return crash_generation_client_.get() != NULL; + } + + // Add information about a memory mapping. This can be used if + // a custom library loader is used that maps things in a way + // that the linux dumper can't handle by reading the maps file. + void AddMappingInfo(const string& name, + const wasteful_vector<uint8_t>& identifier, + uintptr_t start_address, + size_t mapping_size, + size_t file_offset); + + // Register a block of memory of length bytes starting at address ptr + // to be copied to the minidump when a crash happens. + void RegisterAppMemory(void* ptr, size_t length); + + // Unregister a block of memory that was registered with RegisterAppMemory. + void UnregisterAppMemory(void* ptr); + + // Force signal handling for the specified signal. + bool SimulateSignalDelivery(int sig); + + // Report a crash signal from an SA_SIGINFO signal handler. + bool HandleSignal(int sig, siginfo_t* info, void* uc); + + private: + // Save the old signal handlers and install new ones. + static bool InstallHandlersLocked(); + // Restore the old signal handlers. + static void RestoreHandlersLocked(); + + void PreresolveSymbols(); + bool GenerateDump(CrashContext *context, + const mozilla::phc::AddrInfo* addr_info); + void SendContinueSignalToChild(); + void WaitForContinueSignal(); + + static void SignalHandler(int sig, siginfo_t* info, void* uc); + static int ThreadEntry(void* arg); + bool DoDump(pid_t crashing_process, const void* context, + size_t context_size); + + const FilterCallback filter_; + const MinidumpCallback callback_; + void* const callback_context_; + + scoped_ptr<CrashGenerationClient> crash_generation_client_; + + MinidumpDescriptor minidump_descriptor_; + + // Must be volatile. The compiler is unaware of the code which runs in + // the signal handler which reads this variable. Without volatile the + // compiler is free to optimise away writes to this variable which it + // believes are never read. + volatile HandlerCallback crash_handler_; + + // We need to explicitly enable ptrace of parent processes on some + // kernels, but we need to know the PID of the cloned process before we + // can do this. We create a pipe which we can use to block the + // cloned process after creating it, until we have explicitly enabled + // ptrace. This is used to store the file descriptors for the pipe + int fdes[2] = {-1, -1}; + + // Callers can add extra info about mappings for cases where the + // dumper code cannot extract enough information from /proc/<pid>/maps. + MappingList mapping_list_; + + // Callers can request additional memory regions to be included in + // the dump. + AppMemoryList app_memory_list_; +}; + +typedef bool (*FirstChanceHandler)(int, siginfo_t*, void*); +void SetFirstChanceExceptionHandler(FirstChanceHandler callback); + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler_unittest.cc new file mode 100644 index 0000000000..8fa59456c9 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler_unittest.cc @@ -0,0 +1,1290 @@ +// Copyright (c) 2010 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <poll.h> +#include <pthread.h> +#include <stdint.h> +#include <unistd.h> +#include <signal.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/wait.h> +#if defined(__mips__) +#include <sys/cachectl.h> +#endif + +#include <string> + +#include "breakpad_googletest_includes.h" +#include "linux/handler/exception_handler.h" +#include "linux/minidump_writer/minidump_writer.h" +#include "common/linux/eintr_wrapper.h" +#include "common/linux/ignore_ret.h" +#include "common/linux/linux_libc_support.h" +#include "common/tests/auto_tempdir.h" +#include "common/using_std_string.h" +#include "third_party/lss/linux_syscall_support.h" +#include "google_breakpad/processor/minidump.h" + +using namespace google_breakpad; + +namespace { + +// Flush the instruction cache for a given memory range. +// Only required on ARM and mips. +void FlushInstructionCache(const char* memory, uint32_t memory_size) { +#if defined(__arm__) + long begin = reinterpret_cast<long>(memory); + long end = begin + static_cast<long>(memory_size); +# if defined(__ANDROID__) + // Provided by Android's <unistd.h> + cacheflush(begin, end, 0); +# elif defined(__linux__) + // GLibc/ARM doesn't provide a wrapper for it, do a direct syscall. +# ifndef __ARM_NR_cacheflush +# define __ARM_NR_cacheflush 0xf0002 +# endif + syscall(__ARM_NR_cacheflush, begin, end, 0); +# else +# error "Your operating system is not supported yet" +# endif +#elif defined(__mips__) +# if defined(__ANDROID__) + // Provided by Android's <unistd.h> + long begin = reinterpret_cast<long>(memory); + long end = begin + static_cast<long>(memory_size); +#if _MIPS_SIM == _ABIO32 + cacheflush(begin, end, 0); +#else + syscall(__NR_cacheflush, begin, end, ICACHE); +#endif +# elif defined(__linux__) + // See http://www.linux-mips.org/wiki/Cacheflush_Syscall. + cacheflush(const_cast<char*>(memory), memory_size, ICACHE); +# else +# error "Your operating system is not supported yet" +# endif +#endif +} + +void sigchld_handler(int signo) { } + +int CreateTMPFile(const string& dir, string* path) { + string file = dir + "/exception-handler-unittest.XXXXXX"; + const char* c_file = file.c_str(); + // Copy that string, mkstemp needs a C string it can modify. + char* c_path = strdup(c_file); + const int fd = mkstemp(c_path); + if (fd >= 0) + *path = c_path; + free(c_path); + return fd; +} + +class ExceptionHandlerTest : public ::testing::Test { + protected: + void SetUp() { + // We need to be able to wait for children, so SIGCHLD cannot be SIG_IGN. + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigchld_handler; + ASSERT_NE(sigaction(SIGCHLD, &sa, &old_action), -1); + } + + void TearDown() { + sigaction(SIGCHLD, &old_action, NULL); + } + + struct sigaction old_action; +}; + + +void WaitForProcessToTerminate(pid_t process_id, int expected_status) { + int status; + ASSERT_NE(HANDLE_EINTR(waitpid(process_id, &status, 0)), -1); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(expected_status, WTERMSIG(status)); +} + +// Reads the minidump path sent over the pipe |fd| and sets it in |path|. +void ReadMinidumpPathFromPipe(int fd, string* path) { + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fd; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); + ASSERT_EQ(1, r); + ASSERT_TRUE(pfd.revents & POLLIN); + + int32_t len; + ASSERT_EQ(static_cast<ssize_t>(sizeof(len)), read(fd, &len, sizeof(len))); + ASSERT_LT(len, 2048); + char* filename = static_cast<char*>(malloc(len + 1)); + ASSERT_EQ(len, read(fd, filename, len)); + filename[len] = 0; + close(fd); + *path = filename; + free(filename); +} + +} // namespace + +TEST(ExceptionHandlerTest, SimpleWithPath) { + AutoTempDir temp_dir; + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1); + EXPECT_EQ(temp_dir.path(), handler.minidump_descriptor().directory()); + string temp_subdir = temp_dir.path() + "/subdir"; + handler.set_minidump_descriptor(MinidumpDescriptor(temp_subdir)); + EXPECT_EQ(temp_subdir, handler.minidump_descriptor().directory()); +} + +TEST(ExceptionHandlerTest, SimpleWithFD) { + AutoTempDir temp_dir; + string path; + const int fd = CreateTMPFile(temp_dir.path(), &path); + ExceptionHandler handler(MinidumpDescriptor(fd), NULL, NULL, NULL, true, -1); + close(fd); +} + +static bool DoneCallback(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + if (!succeeded) + return false; + + if (!descriptor.IsFD()) { + int fd = reinterpret_cast<intptr_t>(context); + uint32_t len = 0; + len = my_strlen(descriptor.path()); + IGNORE_RET(HANDLE_EINTR(sys_write(fd, &len, sizeof(len)))); + IGNORE_RET(HANDLE_EINTR(sys_write(fd, descriptor.path(), len))); + } + return true; +} + +#ifndef ADDRESS_SANITIZER + +// This is a replacement for "*reinterpret_cast<volatile int*>(NULL) = 0;" +// It is needed because GCC is allowed to assume that the program will +// not execute any undefined behavior (UB) operation. Further, when GCC +// observes that UB statement is reached, it can assume that all statements +// leading to the UB one are never executed either, and can completely +// optimize them out. In the case of ExceptionHandlerTest::ExternalDumper, +// GCC-4.9 optimized out the entire set up of ExceptionHandler, causing +// test failure. +volatile int *p_null; // external linkage, so GCC can't tell that it + // remains NULL. Volatile just for a good measure. +static void DoNullPointerDereference() { + *p_null = 1; +} + +void ChildCrash(bool use_fd) { + AutoTempDir temp_dir; + int fds[2] = {0}; + int minidump_fd = -1; + string minidump_path; + if (use_fd) { + minidump_fd = CreateTMPFile(temp_dir.path(), &minidump_path); + } else { + ASSERT_NE(pipe(fds), -1); + } + + const pid_t child = fork(); + if (child == 0) { + { + google_breakpad::scoped_ptr<ExceptionHandler> handler; + if (use_fd) { + handler.reset(new ExceptionHandler(MinidumpDescriptor(minidump_fd), + NULL, NULL, NULL, true, -1)); + } else { + close(fds[0]); // Close the reading end. + void* fd_param = reinterpret_cast<void*>(fds[1]); + handler.reset(new ExceptionHandler(MinidumpDescriptor(temp_dir.path()), + NULL, DoneCallback, fd_param, + true, -1)); + } + // Crash with the exception handler in scope. + DoNullPointerDereference(); + } + } + if (!use_fd) + close(fds[1]); // Close the writting end. + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); + + if (!use_fd) + ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path)); + + struct stat st; + ASSERT_EQ(0, stat(minidump_path.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + unlink(minidump_path.c_str()); +} + +TEST(ExceptionHandlerTest, ChildCrashWithPath) { + ASSERT_NO_FATAL_FAILURE(ChildCrash(false)); +} + +TEST(ExceptionHandlerTest, ChildCrashWithFD) { + ASSERT_NO_FATAL_FAILURE(ChildCrash(true)); +} + +#if !defined(__ANDROID_API__) || __ANDROID_API__ >= __ANDROID_API_N__ +static void* SleepFunction(void* unused) { + while (true) usleep(1000000); + return NULL; +} + +static void* CrashFunction(void* b_ptr) { + pthread_barrier_t* b = reinterpret_cast<pthread_barrier_t*>(b_ptr); + pthread_barrier_wait(b); + DoNullPointerDereference(); + return NULL; +} + +// Tests that concurrent crashes do not enter a loop by alternately triggering +// the signal handler. +TEST(ExceptionHandlerTest, ParallelChildCrashesDontHang) { + AutoTempDir temp_dir; + const pid_t child = fork(); + if (child == 0) { + google_breakpad::scoped_ptr<ExceptionHandler> handler( + new ExceptionHandler(MinidumpDescriptor(temp_dir.path()), NULL, NULL, + NULL, true, -1)); + + // We start a number of threads to make sure handling the signal takes + // enough time for the second thread to enter the signal handler. + int num_sleep_threads = 100; + google_breakpad::scoped_array<pthread_t> sleep_threads( + new pthread_t[num_sleep_threads]); + for (int i = 0; i < num_sleep_threads; ++i) { + ASSERT_EQ(0, pthread_create(&sleep_threads[i], NULL, SleepFunction, + NULL)); + } + + int num_crash_threads = 2; + google_breakpad::scoped_array<pthread_t> crash_threads( + new pthread_t[num_crash_threads]); + // Barrier to synchronize crashing both threads at the same time. + pthread_barrier_t b; + ASSERT_EQ(0, pthread_barrier_init(&b, NULL, num_crash_threads + 1)); + for (int i = 0; i < num_crash_threads; ++i) { + ASSERT_EQ(0, pthread_create(&crash_threads[i], NULL, CrashFunction, &b)); + } + pthread_barrier_wait(&b); + for (int i = 0; i < num_crash_threads; ++i) { + ASSERT_EQ(0, pthread_join(crash_threads[i], NULL)); + } + } + + // Wait a while until the child should have crashed. + usleep(1000000); + // Kill the child if it is still running. + kill(child, SIGKILL); + + // If the child process terminated by itself, it will have returned SIGSEGV. + // If however it got stuck in a loop, it will have been killed by the + // SIGKILL. + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); +} +#endif // !defined(__ANDROID_API__) || __ANDROID_API__ >= __ANDROID_API_N__ + +static bool DoneCallbackReturnFalse(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + return false; +} + +static bool DoneCallbackReturnTrue(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + return true; +} + +static bool DoneCallbackRaiseSIGKILL(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + raise(SIGKILL); + return true; +} + +static bool FilterCallbackReturnFalse(void* context) { + return false; +} + +static bool FilterCallbackReturnTrue(void* context) { + return true; +} + +// SIGKILL cannot be blocked and a handler cannot be installed for it. In the +// following tests, if the child dies with signal SIGKILL, then the signal was +// redelivered to this handler. If the child dies with SIGSEGV then it wasn't. +static void RaiseSIGKILL(int sig) { + raise(SIGKILL); +} + +static bool InstallRaiseSIGKILL() { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = RaiseSIGKILL; + return sigaction(SIGSEGV, &sa, NULL) != -1; +} + +static void CrashWithCallbacks(ExceptionHandler::FilterCallback filter, + ExceptionHandler::MinidumpCallback done, + string path) { + ExceptionHandler handler( + MinidumpDescriptor(path), filter, done, NULL, true, -1); + // Crash with the exception handler in scope. + DoNullPointerDereference(); +} + +TEST(ExceptionHandlerTest, RedeliveryOnFilterCallbackFalse) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ASSERT_TRUE(InstallRaiseSIGKILL()); + CrashWithCallbacks(FilterCallbackReturnFalse, NULL, temp_dir.path()); + } + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +TEST(ExceptionHandlerTest, RedeliveryOnDoneCallbackFalse) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ASSERT_TRUE(InstallRaiseSIGKILL()); + CrashWithCallbacks(NULL, DoneCallbackReturnFalse, temp_dir.path()); + } + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +TEST(ExceptionHandlerTest, NoRedeliveryOnDoneCallbackTrue) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ASSERT_TRUE(InstallRaiseSIGKILL()); + CrashWithCallbacks(NULL, DoneCallbackReturnTrue, temp_dir.path()); + } + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); +} + +TEST(ExceptionHandlerTest, NoRedeliveryOnFilterCallbackTrue) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ASSERT_TRUE(InstallRaiseSIGKILL()); + CrashWithCallbacks(FilterCallbackReturnTrue, NULL, temp_dir.path()); + } + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); +} + +TEST(ExceptionHandlerTest, RedeliveryToDefaultHandler) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + // Custom signal handlers, which may have been installed by a test launcher, + // are undesirable in this child. + signal(SIGSEGV, SIG_DFL); + + CrashWithCallbacks(FilterCallbackReturnFalse, NULL, temp_dir.path()); + } + + // As RaiseSIGKILL wasn't installed, the redelivery should just kill the child + // with SIGSEGV. + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); +} + +// Check that saving and restoring the signal handler with 'signal' +// instead of 'sigaction' doesn't make the Breakpad signal handler +// crash. See comments in ExceptionHandler::SignalHandler for full +// details. +TEST(ExceptionHandlerTest, RedeliveryOnBadSignalHandlerFlag) { + AutoTempDir temp_dir; + const pid_t child = fork(); + if (child == 0) { + // Install the RaiseSIGKILL handler for SIGSEGV. + ASSERT_TRUE(InstallRaiseSIGKILL()); + + // Create a new exception handler, this installs a new SIGSEGV + // handler, after saving the old one. + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallbackReturnFalse, NULL, true, -1); + + // Install the default SIGSEGV handler, saving the current one. + // Then re-install the current one with 'signal', this loses the + // SA_SIGINFO flag associated with the Breakpad handler. + sighandler_t old_handler = signal(SIGSEGV, SIG_DFL); + ASSERT_NE(reinterpret_cast<void*>(old_handler), + reinterpret_cast<void*>(SIG_ERR)); + ASSERT_NE(reinterpret_cast<void*>(signal(SIGSEGV, old_handler)), + reinterpret_cast<void*>(SIG_ERR)); + + // Crash with the exception handler in scope. + DoNullPointerDereference(); + } + // SIGKILL means Breakpad's signal handler didn't crash. + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +TEST(ExceptionHandlerTest, StackedHandlersDeliveredToTop) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()), + NULL, + NULL, + NULL, + true, + -1); + CrashWithCallbacks(NULL, DoneCallbackRaiseSIGKILL, temp_dir.path()); + } + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +TEST(ExceptionHandlerTest, StackedHandlersNotDeliveredToBottom) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()), + NULL, + DoneCallbackRaiseSIGKILL, + NULL, + true, + -1); + CrashWithCallbacks(NULL, NULL, temp_dir.path()); + } + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); +} + +TEST(ExceptionHandlerTest, StackedHandlersFilteredToBottom) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()), + NULL, + DoneCallbackRaiseSIGKILL, + NULL, + true, + -1); + CrashWithCallbacks(FilterCallbackReturnFalse, NULL, temp_dir.path()); + } + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +TEST(ExceptionHandlerTest, StackedHandlersUnhandledToBottom) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()), + NULL, + DoneCallbackRaiseSIGKILL, + NULL, + true, + -1); + CrashWithCallbacks(NULL, DoneCallbackReturnFalse, temp_dir.path()); + } + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +namespace { +const int kSimpleFirstChanceReturnStatus = 42; +bool SimpleFirstChanceHandler(int, siginfo_t*, void*) { + _exit(kSimpleFirstChanceReturnStatus); +} +} + +TEST(ExceptionHandlerTest, FirstChanceHandlerRuns) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1); + google_breakpad::SetFirstChanceExceptionHandler(SimpleFirstChanceHandler); + DoNullPointerDereference(); + } + int status; + ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(kSimpleFirstChanceReturnStatus, WEXITSTATUS(status)); +} + +#endif // !ADDRESS_SANITIZER + +const unsigned char kIllegalInstruction[] = { +#if defined(__mips__) + // mfc2 zero,Impl - usually illegal in userspace. + 0x48, 0x00, 0x00, 0x48 +#else + // This crashes with SIGILL on x86/x86-64/arm. + 0xff, 0xff, 0xff, 0xff +#endif +}; + +// Test that memory around the instruction pointer is written +// to the dump as a MinidumpMemoryRegion. +TEST(ExceptionHandlerTest, InstructionPointerMemory) { + AutoTempDir temp_dir; + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const uint32_t kMemorySize = 256; // bytes + const int kOffset = kMemorySize / 2; + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallback, reinterpret_cast<void*>(fds[1]), + true, -1); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, kIllegalInstruction, sizeof(kIllegalInstruction)); + FlushInstructionCache(memory, kMemorySize); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + } + close(fds[1]); + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGILL)); + + string minidump_path; + ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path)); + + struct stat st; + ASSERT_EQ(0, stat(minidump_path.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT(0U, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemorySize, region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t prefix_bytes[kOffset]; + uint8_t suffix_bytes[kMemorySize - kOffset - sizeof(kIllegalInstruction)]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset, kIllegalInstruction, + sizeof(kIllegalInstruction)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(kIllegalInstruction), + suffix_bytes, sizeof(suffix_bytes)) == 0); + + unlink(minidump_path.c_str()); +} + +// Test that the memory region around the instruction pointer is +// bounded correctly on the low end. +TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) { + AutoTempDir temp_dir; + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const uint32_t kMemorySize = 256; // bytes + const int kOffset = 0; + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallback, reinterpret_cast<void*>(fds[1]), + true, -1); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, kIllegalInstruction, sizeof(kIllegalInstruction)); + FlushInstructionCache(memory, kMemorySize); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + } + close(fds[1]); + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGILL)); + + string minidump_path; + ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path)); + + struct stat st; + ASSERT_EQ(0, stat(minidump_path.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT(0U, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemorySize / 2, region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t suffix_bytes[kMemorySize / 2 - sizeof(kIllegalInstruction)]; + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes + kOffset, kIllegalInstruction, + sizeof(kIllegalInstruction)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(kIllegalInstruction), + suffix_bytes, sizeof(suffix_bytes)) == 0); + unlink(minidump_path.c_str()); +} + +// Test that the memory region around the instruction pointer is +// bounded correctly on the high end. +TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) { + AutoTempDir temp_dir; + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + // Use 4k here because the OS will hand out a single page even + // if a smaller size is requested, and this test wants to + // test the upper bound of the memory range. + const uint32_t kMemorySize = 4096; // bytes + const int kOffset = kMemorySize - sizeof(kIllegalInstruction); + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallback, reinterpret_cast<void*>(fds[1]), + true, -1); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, kIllegalInstruction, sizeof(kIllegalInstruction)); + FlushInstructionCache(memory, kMemorySize); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + } + close(fds[1]); + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGILL)); + + string minidump_path; + ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path)); + + struct stat st; + ASSERT_EQ(0, stat(minidump_path.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + // Read the minidump. Locate the exception record and the memory list, and + // then ensure that there is a memory region in the memory list that covers + // the instruction pointer from the exception record. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT(0U, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + const size_t kPrefixSize = 128; // bytes + EXPECT_EQ(kPrefixSize + sizeof(kIllegalInstruction), region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t prefix_bytes[kPrefixSize]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); + EXPECT_TRUE(memcmp(bytes + kPrefixSize, + kIllegalInstruction, sizeof(kIllegalInstruction)) == 0); + + unlink(minidump_path.c_str()); +} + +#ifndef ADDRESS_SANITIZER + +// Ensure that an extra memory block doesn't get added when the instruction +// pointer is not in mapped memory. +TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) { + AutoTempDir temp_dir; + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallback, reinterpret_cast<void*>(fds[1]), + true, -1); + // Try calling a NULL pointer. + typedef void (*void_function)(void); + // Volatile markings are needed to keep Clang from generating invalid + // opcodes. See http://crbug.com/498354 for details. + volatile void_function memory_function = + reinterpret_cast<void_function>(NULL); + memory_function(); + // not reached + exit(1); + } + close(fds[1]); + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); + + string minidump_path; + ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path)); + + struct stat st; + ASSERT_EQ(0, stat(minidump_path.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is no memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + + MinidumpContext* exception_context = exception->GetContext(); + ASSERT_TRUE(exception_context); + + uint64_t instruction_pointer; + ASSERT_TRUE(exception_context->GetInstructionPointer(&instruction_pointer)); + EXPECT_EQ(instruction_pointer, 0u); + + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(memory_list); + + unsigned int region_count = memory_list->region_count(); + ASSERT_GE(region_count, 1u); + + for (unsigned int region_index = 0; + region_index < region_count; + ++region_index) { + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionAtIndex(region_index); + uint64_t region_base = region->GetBase(); + EXPECT_FALSE(instruction_pointer >= region_base && + instruction_pointer < region_base + region->GetSize()); + } + + unlink(minidump_path.c_str()); +} + +#endif // !ADDRESS_SANITIZER + +// Test that anonymous memory maps can be annotated with names and IDs. +TEST(ExceptionHandlerTest, ModuleInfo) { + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const uint32_t kMemorySize = sysconf(_SC_PAGESIZE); + const char* kMemoryName = "a fake module"; + const uint8_t kModuleGUID[sizeof(MDGUID)] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF + }; + const string module_identifier = "33221100554477668899AABBCCDDEEFF0"; + + // Get some memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + + PageAllocator allocator; + auto_wasteful_vector<uint8_t, sizeof(MDGUID)> guid(&allocator); + guid.assign(std::begin(kModuleGUID), std::end(kModuleGUID)); + AutoTempDir temp_dir; + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1); + + // Add info about the anonymous memory mapping. + handler.AddMappingInfo(kMemoryName, + guid, + kMemoryAddress, + kMemorySize, + 0); + ASSERT_TRUE(handler.WriteMinidump()); + + const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor(); + // Read the minidump. Load the module list, and ensure that the mmap'ed + // |memory| is listed with the given module name and debug ID. + Minidump minidump(minidump_desc.path()); + ASSERT_TRUE(minidump.Read()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* module = + module_list->GetModuleForAddress(kMemoryAddress); + ASSERT_TRUE(module); + + EXPECT_EQ(kMemoryAddress, module->base_address()); + EXPECT_EQ(kMemorySize, module->size()); + EXPECT_EQ(kMemoryName, module->code_file()); + EXPECT_EQ(module_identifier, module->debug_identifier()); + + unlink(minidump_desc.path()); +} + +#ifndef ADDRESS_SANITIZER + +static const unsigned kControlMsgSize = + CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); + +static bool +CrashHandler(const void* crash_context, size_t crash_context_size, + void* context) { + const int fd = (intptr_t) context; + int fds[2]; + if (pipe(fds) == -1) { + // There doesn't seem to be any way to reliably handle + // this failure without the parent process hanging + // At least make sure that this process doesn't access + // unexpected file descriptors + fds[0] = -1; + fds[1] = -1; + } + struct kernel_msghdr msg = {0}; + struct kernel_iovec iov; + iov.iov_base = const_cast<void*>(crash_context); + iov.iov_len = crash_context_size; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + char cmsg[kControlMsgSize]; + memset(cmsg, 0, kControlMsgSize); + msg.msg_control = cmsg; + msg.msg_controllen = sizeof(cmsg); + + struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); + hdr->cmsg_level = SOL_SOCKET; + hdr->cmsg_type = SCM_RIGHTS; + hdr->cmsg_len = CMSG_LEN(sizeof(int)); + *((int*) CMSG_DATA(hdr)) = fds[1]; + hdr = CMSG_NXTHDR((struct msghdr*) &msg, hdr); + hdr->cmsg_level = SOL_SOCKET; + hdr->cmsg_type = SCM_CREDENTIALS; + hdr->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + struct ucred *cred = reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); + cred->uid = getuid(); + cred->gid = getgid(); + cred->pid = getpid(); + + ssize_t ret = HANDLE_EINTR(sys_sendmsg(fd, &msg, 0)); + sys_close(fds[1]); + if (ret <= 0) + return false; + + char b; + IGNORE_RET(HANDLE_EINTR(sys_read(fds[0], &b, 1))); + + return true; +} + +TEST(ExceptionHandlerTest, ExternalDumper) { + int fds[2]; + ASSERT_NE(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds), -1); + static const int on = 1; + setsockopt(fds[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler(MinidumpDescriptor("/tmp1"), NULL, NULL, + reinterpret_cast<void*>(fds[1]), true, -1); + handler.set_crash_handler(CrashHandler); + DoNullPointerDereference(); + } + close(fds[1]); + struct msghdr msg = {0}; + struct iovec iov; + static const unsigned kCrashContextSize = + sizeof(ExceptionHandler::CrashContext); + char context[kCrashContextSize]; + char control[kControlMsgSize]; + iov.iov_base = context; + iov.iov_len = kCrashContextSize; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = kControlMsgSize; + + const ssize_t n = HANDLE_EINTR(recvmsg(fds[0], &msg, 0)); + ASSERT_EQ(static_cast<ssize_t>(kCrashContextSize), n); + ASSERT_EQ(kControlMsgSize, msg.msg_controllen); + ASSERT_EQ(static_cast<__typeof__(msg.msg_flags)>(0), msg.msg_flags); + ASSERT_EQ(0, close(fds[0])); + + pid_t crashing_pid = -1; + int signal_fd = -1; + for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; + hdr = CMSG_NXTHDR(&msg, hdr)) { + if (hdr->cmsg_level != SOL_SOCKET) + continue; + if (hdr->cmsg_type == SCM_RIGHTS) { + const unsigned len = hdr->cmsg_len - + (((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr); + ASSERT_EQ(sizeof(int), len); + signal_fd = *(reinterpret_cast<int*>(CMSG_DATA(hdr))); + } else if (hdr->cmsg_type == SCM_CREDENTIALS) { + const struct ucred *cred = + reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); + crashing_pid = cred->pid; + } + } + + ASSERT_NE(crashing_pid, -1); + ASSERT_NE(signal_fd, -1); + + AutoTempDir temp_dir; + string templ = temp_dir.path() + "/exception-handler-unittest"; + ASSERT_TRUE(WriteMinidump(templ.c_str(), crashing_pid, context, + kCrashContextSize)); + static const char b = 0; + ASSERT_EQ(1, (HANDLE_EINTR(write(signal_fd, &b, 1)))); + ASSERT_EQ(0, close(signal_fd)); + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); + + struct stat st; + ASSERT_EQ(0, stat(templ.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + unlink(templ.c_str()); +} + +#endif // !ADDRESS_SANITIZER + +TEST(ExceptionHandlerTest, WriteMinidumpExceptionStream) { + AutoTempDir temp_dir; + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, NULL, + NULL, false, -1); + ASSERT_TRUE(handler.WriteMinidump()); + + string minidump_path = handler.minidump_descriptor().path(); + + // Read the minidump and check the exception stream. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + const MDRawExceptionStream* raw = exception->exception(); + ASSERT_TRUE(raw); + EXPECT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED, + raw->exception_record.exception_code); +} + +TEST(ExceptionHandlerTest, GenerateMultipleDumpsWithFD) { + AutoTempDir temp_dir; + string path; + const int fd = CreateTMPFile(temp_dir.path(), &path); + ExceptionHandler handler(MinidumpDescriptor(fd), NULL, NULL, NULL, false, -1); + ASSERT_TRUE(handler.WriteMinidump()); + // Check by the size of the data written to the FD that a minidump was + // generated. + off_t size = lseek(fd, 0, SEEK_CUR); + ASSERT_GT(size, 0); + + // Generate another minidump. + ASSERT_TRUE(handler.WriteMinidump()); + size = lseek(fd, 0, SEEK_CUR); + ASSERT_GT(size, 0); +} + +TEST(ExceptionHandlerTest, GenerateMultipleDumpsWithPath) { + AutoTempDir temp_dir; + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, NULL, + NULL, false, -1); + ASSERT_TRUE(handler.WriteMinidump()); + + const MinidumpDescriptor& minidump_1 = handler.minidump_descriptor(); + struct stat st; + ASSERT_EQ(0, stat(minidump_1.path(), &st)); + ASSERT_GT(st.st_size, 0); + string minidump_1_path(minidump_1.path()); + // Check it is a valid minidump. + Minidump minidump1(minidump_1_path); + ASSERT_TRUE(minidump1.Read()); + unlink(minidump_1.path()); + + // Generate another minidump, it should go to a different file. + ASSERT_TRUE(handler.WriteMinidump()); + const MinidumpDescriptor& minidump_2 = handler.minidump_descriptor(); + ASSERT_EQ(0, stat(minidump_2.path(), &st)); + ASSERT_GT(st.st_size, 0); + string minidump_2_path(minidump_2.path()); + // Check it is a valid minidump. + Minidump minidump2(minidump_2_path); + ASSERT_TRUE(minidump2.Read()); + unlink(minidump_2.path()); + + // 2 distinct files should be produced. + ASSERT_STRNE(minidump_1_path.c_str(), minidump_2_path.c_str()); +} + +// Test that an additional memory region can be added to the minidump. +TEST(ExceptionHandlerTest, AdditionalMemory) { + const uint32_t kMemorySize = sysconf(_SC_PAGESIZE); + + // Get some heap memory. + uint8_t* memory = new uint8_t[kMemorySize]; + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + + // Stick some data into the memory so the contents can be verified. + for (uint32_t i = 0; i < kMemorySize; ++i) { + memory[i] = i % 255; + } + + AutoTempDir temp_dir; + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1); + + // Add the memory region to the list of memory to be included. + handler.RegisterAppMemory(memory, kMemorySize); + handler.WriteMinidump(); + + const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor(); + + // Read the minidump. Ensure that the memory region is present + Minidump minidump(minidump_desc.path()); + ASSERT_TRUE(minidump.Read()); + + MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(dump_memory_list); + const MinidumpMemoryRegion* region = + dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemoryAddress, region->GetBase()); + EXPECT_EQ(kMemorySize, region->GetSize()); + + // Verify memory contents. + EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize)); + + delete[] memory; +} + +// Test that a memory region that was previously registered +// can be unregistered. +TEST(ExceptionHandlerTest, AdditionalMemoryRemove) { + const uint32_t kMemorySize = sysconf(_SC_PAGESIZE); + + // Get some heap memory. + uint8_t* memory = new uint8_t[kMemorySize]; + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + + AutoTempDir temp_dir; + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1); + + // Add the memory region to the list of memory to be included. + handler.RegisterAppMemory(memory, kMemorySize); + + // ...and then remove it + handler.UnregisterAppMemory(memory); + handler.WriteMinidump(); + + const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor(); + + // Read the minidump. Ensure that the memory region is not present. + Minidump minidump(minidump_desc.path()); + ASSERT_TRUE(minidump.Read()); + + MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(dump_memory_list); + const MinidumpMemoryRegion* region = + dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress); + EXPECT_FALSE(region); + + delete[] memory; +} + +static bool SimpleCallback(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + string* filename = reinterpret_cast<string*>(context); + *filename = descriptor.path(); + return true; +} + +TEST(ExceptionHandlerTest, WriteMinidumpForChild) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + HANDLE_EINTR(read(fds[0], &b, sizeof(b))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + AutoTempDir temp_dir; + string minidump_filename; + ASSERT_TRUE( + ExceptionHandler::WriteMinidumpForChild(child, child, + temp_dir.path(), SimpleCallback, + (void*)&minidump_filename)); + + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + // Check that the crashing thread is the main thread of |child| + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + uint32_t thread_id; + ASSERT_TRUE(exception->GetThreadID(&thread_id)); + EXPECT_EQ(child, static_cast<int32_t>(thread_id)); + + const MDRawExceptionStream* raw = exception->exception(); + ASSERT_TRUE(raw); + EXPECT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED, + raw->exception_record.exception_code); + + close(fds[1]); + unlink(minidump_filename.c_str()); +} diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.cc b/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.cc new file mode 100644 index 0000000000..b0ef05bd9d --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "linux/handler/guid_generator.h" + +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +// +// GUIDGenerator +// +// This class is used to generate random GUID. +// Currently use random number to generate a GUID since Linux has +// no native GUID generator. This should be OK since we don't expect +// crash to happen very offen. +// +class GUIDGenerator { + public: + static uint16_t BytesToUInt16(const uint8_t bytes[]) { + return ((uint16_t) bytes[1] << 8) | ((uint16_t) bytes[0]); + } + + // The last field in a GUID is 48 bits long so we're converting only 6 bytes + static uint64_t BytesToUInt48(const uint8_t bytes[]) { + return ((uint64_t) bytes[0] << 40) | ((uint64_t) bytes[1] << 32) | + ((uint64_t) bytes[2] << 24) | ((uint64_t) bytes[3] << 16) | + ((uint64_t) bytes[4] << 8) | (uint64_t) bytes[5]; + } + + static void UInt32ToBytes(uint8_t bytes[], uint32_t n) { + bytes[0] = n & 0xff; + bytes[1] = (n >> 8) & 0xff; + bytes[2] = (n >> 16) & 0xff; + bytes[3] = (n >> 24) & 0xff; + } + + static bool CreateGUID(GUID *guid) { + InitOnce(); + guid->data1 = random(); + guid->data2 = (uint16_t)(random()); + guid->data3 = (uint16_t)(random()); + UInt32ToBytes(&guid->data4[0], random()); + UInt32ToBytes(&guid->data4[4], random()); + return true; + } + + private: + static void InitOnce() { + pthread_once(&once_control, &InitOnceImpl); + } + + static void InitOnceImpl() { + srandom(time(NULL)); + } + + static pthread_once_t once_control; +}; + +pthread_once_t GUIDGenerator::once_control = PTHREAD_ONCE_INIT; + +bool CreateGUID(GUID *guid) { + return GUIDGenerator::CreateGUID(guid); +} + +// Parse guid to string. +bool GUIDToString(const GUID *guid, char *buf, size_t buf_len) { + // Should allow more space the the max length of GUID. + assert(buf_len > kGUIDStringLength); + int num = snprintf(buf, buf_len, kGUIDFormatString, + guid->data1, guid->data2, guid->data3, + GUIDGenerator::BytesToUInt16(&(guid->data4[0])), + GUIDGenerator::BytesToUInt48(&(guid->data4[2]))); + if (num != kGUIDStringLength) + return false; + + buf[num] = '\0'; + return true; +} diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.h b/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.h new file mode 100644 index 0000000000..de97eda1cb --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.h @@ -0,0 +1,48 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef LINUX_HANDLER_GUID_GENERATOR_H__ +#define LINUX_HANDLER_GUID_GENERATOR_H__ + +#include "google_breakpad/common/minidump_format.h" + +typedef MDGUID GUID; + +// Format string for parsing GUID. +const char kGUIDFormatString[] = "%08x-%04x-%04x-%04x-%012" PRIx64; +// Length of GUID string. Don't count the ending '\0'. +const size_t kGUIDStringLength = 36; + +// Create a guid. +bool CreateGUID(GUID *guid); + +// Get the string from guid. +bool GUIDToString(const GUID *guid, char *buf, size_t buf_len); + +#endif diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/microdump_extra_info.h b/toolkit/crashreporter/breakpad-client/linux/handler/microdump_extra_info.h new file mode 100644 index 0000000000..bf01f0c7b1 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/handler/microdump_extra_info.h @@ -0,0 +1,52 @@ +// Copyright 2015 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CLIENT_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_ +#define CLIENT_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_ + +namespace google_breakpad { + +struct MicrodumpExtraInfo { + // Strings pointed to by this struct are not copied, and are + // expected to remain valid for the lifetime of the process. + const char* build_fingerprint; + const char* product_info; + const char* gpu_fingerprint; + const char* process_type; + + MicrodumpExtraInfo() + : build_fingerprint(NULL), + product_info(NULL), + gpu_fingerprint(NULL), + process_type(NULL) {} +}; + +} + +#endif // CLIENT_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_ diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.cc b/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.cc new file mode 100644 index 0000000000..21cf4b0dce --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2012 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <stdio.h> + +#include "linux/handler/guid_generator.h" +#include "linux/handler/minidump_descriptor.h" + +namespace google_breakpad { + +//static +const MinidumpDescriptor::MicrodumpOnConsole + MinidumpDescriptor::kMicrodumpOnConsole = {}; + +MinidumpDescriptor::MinidumpDescriptor(const MinidumpDescriptor& descriptor) + : mode_(descriptor.mode_), + fd_(descriptor.fd_), + directory_(descriptor.directory_), + c_path_(NULL), + size_limit_(descriptor.size_limit_), + address_within_principal_mapping_( + descriptor.address_within_principal_mapping_), + skip_dump_if_principal_mapping_not_referenced_( + descriptor.skip_dump_if_principal_mapping_not_referenced_), + sanitize_stacks_(descriptor.sanitize_stacks_), + microdump_extra_info_(descriptor.microdump_extra_info_) { + // The copy constructor is not allowed to be called on a MinidumpDescriptor + // with a valid path_, as getting its c_path_ would require the heap which + // can cause problems in compromised environments. + assert(descriptor.path_.empty()); +} + +MinidumpDescriptor& MinidumpDescriptor::operator=( + const MinidumpDescriptor& descriptor) { + assert(descriptor.path_.empty()); + + mode_ = descriptor.mode_; + fd_ = descriptor.fd_; + directory_ = descriptor.directory_; + path_.clear(); + if (c_path_) { + // This descriptor already had a path set, so generate a new one. + c_path_ = NULL; + UpdatePath(); + } + size_limit_ = descriptor.size_limit_; + address_within_principal_mapping_ = + descriptor.address_within_principal_mapping_; + skip_dump_if_principal_mapping_not_referenced_ = + descriptor.skip_dump_if_principal_mapping_not_referenced_; + sanitize_stacks_ = descriptor.sanitize_stacks_; + microdump_extra_info_ = descriptor.microdump_extra_info_; + return *this; +} + +void MinidumpDescriptor::UpdatePath() { + assert(mode_ == kWriteMinidumpToFile && !directory_.empty()); + + GUID guid; + char guid_str[kGUIDStringLength + 1]; + if (!CreateGUID(&guid) || !GUIDToString(&guid, guid_str, sizeof(guid_str))) { + assert(false); + } + + path_.clear(); + path_ = directory_ + "/" + guid_str + ".dmp"; + c_path_ = path_.c_str(); +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.h b/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.h new file mode 100644 index 0000000000..c3deae8a74 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.h @@ -0,0 +1,199 @@ +// Copyright (c) 2012 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_ +#define CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_ + +#include <assert.h> +#include <sys/types.h> + +#include <string> + +#include "linux/handler/microdump_extra_info.h" +#include "common/using_std_string.h" + +// This class describes how a crash dump should be generated, either: +// - Writing a full minidump to a file in a given directory (the actual path, +// inside the directory, is determined by this class). +// - Writing a full minidump to a given fd. +// - Writing a reduced microdump to the console (logcat on Android). +namespace google_breakpad { + +class MinidumpDescriptor { + public: + struct MicrodumpOnConsole {}; + static const MicrodumpOnConsole kMicrodumpOnConsole; + + MinidumpDescriptor() + : mode_(kUninitialized), + fd_(-1), + size_limit_(-1), + address_within_principal_mapping_(0), + skip_dump_if_principal_mapping_not_referenced_(false) {} + + explicit MinidumpDescriptor(const string& directory) + : mode_(kWriteMinidumpToFile), + fd_(-1), + directory_(directory), + c_path_(NULL), + size_limit_(-1), + address_within_principal_mapping_(0), + skip_dump_if_principal_mapping_not_referenced_(false), + sanitize_stacks_(false) { + assert(!directory.empty()); + } + + explicit MinidumpDescriptor(int fd) + : mode_(kWriteMinidumpToFd), + fd_(fd), + c_path_(NULL), + size_limit_(-1), + address_within_principal_mapping_(0), + skip_dump_if_principal_mapping_not_referenced_(false), + sanitize_stacks_(false) { + assert(fd != -1); + } + + explicit MinidumpDescriptor(const MicrodumpOnConsole&) + : mode_(kWriteMicrodumpToConsole), + fd_(-1), + size_limit_(-1), + address_within_principal_mapping_(0), + skip_dump_if_principal_mapping_not_referenced_(false), + sanitize_stacks_(false) {} + + explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor); + MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor); + + static MinidumpDescriptor getMicrodumpDescriptor(); + + bool IsFD() const { return mode_ == kWriteMinidumpToFd; } + + int fd() const { return fd_; } + + string directory() const { return directory_; } + + const char* path() const { return c_path_; } + + bool IsMicrodumpOnConsole() const { + return mode_ == kWriteMicrodumpToConsole; + } + + // Updates the path so it is unique. + // Should be called from a normal context: this methods uses the heap. + void UpdatePath(); + + off_t size_limit() const { return size_limit_; } + void set_size_limit(off_t limit) { size_limit_ = limit; } + + uintptr_t address_within_principal_mapping() const { + return address_within_principal_mapping_; + } + void set_address_within_principal_mapping( + uintptr_t address_within_principal_mapping) { + address_within_principal_mapping_ = address_within_principal_mapping; + } + + bool skip_dump_if_principal_mapping_not_referenced() { + return skip_dump_if_principal_mapping_not_referenced_; + } + void set_skip_dump_if_principal_mapping_not_referenced( + bool skip_dump_if_principal_mapping_not_referenced) { + skip_dump_if_principal_mapping_not_referenced_ = + skip_dump_if_principal_mapping_not_referenced; + } + + bool sanitize_stacks() const { return sanitize_stacks_; } + void set_sanitize_stacks(bool sanitize_stacks) { + sanitize_stacks_ = sanitize_stacks; + } + + MicrodumpExtraInfo* microdump_extra_info() { + assert(IsMicrodumpOnConsole()); + return µdump_extra_info_; + } + + private: + enum DumpMode { + kUninitialized = 0, + kWriteMinidumpToFile, + kWriteMinidumpToFd, + kWriteMicrodumpToConsole + }; + + // Specifies the dump mode (see DumpMode). + DumpMode mode_; + + // The file descriptor where the minidump is generated. + int fd_; + + // The directory where the minidump should be generated. + string directory_; + + // The full path to the generated minidump. + string path_; + + // The C string of |path_|. Precomputed so it can be access from a compromised + // context. + const char* c_path_; + + off_t size_limit_; + + // This member points somewhere into the main module for this + // process (the module that is considerered interesting for the + // purposes of debugging crashes). + uintptr_t address_within_principal_mapping_; + + // If set, threads that do not reference the address range + // associated with |address_within_principal_mapping_| will not have their + // stacks logged. + bool skip_dump_if_principal_mapping_not_referenced_; + + // If set, stacks are sanitized to remove PII. This involves + // overwriting any pointer-aligned words that are not either + // pointers into a process mapping or small integers (+/-4096). This + // leaves enough information to unwind stacks, and preserve some + // register values, but elides strings and other program data. + bool sanitize_stacks_; + + // The extra microdump data (e.g. product name/version, build + // fingerprint, gpu fingerprint) that should be appended to the dump + // (microdump only). Microdumps don't have the ability of appending + // extra metadata after the dump is generated (as opposite to + // minidumps MIME fields), therefore the extra data must be provided + // upfront. Any memory pointed to by members of the + // MicrodumpExtraInfo struct must be valid for the lifetime of the + // process (read: the caller has to guarantee that it is stored in + // global static storage.) + MicrodumpExtraInfo microdump_extra_info_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_ |