Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 152bcf9b authored by Josh Gao's avatar Josh Gao Committed by Gerrit Code Review
Browse files

Merge "crash_dump: fork a copy of the target's address space."

parents 3fca6751 2b2ae0c8
Loading
Loading
Loading
Loading
+6 −22
Original line number Diff line number Diff line
@@ -4,9 +4,12 @@ cc_defaults {
        "-Wall",
        "-Wextra",
        "-Werror",
        "-Wno-unused-argument",
        "-Wno-unused-function",
        "-Wno-nullability-completeness",
        "-Os",
    ],
    cpp_std: "gnu++17",

    local_include_dirs: ["include"],
}
@@ -73,6 +76,7 @@ cc_library_static {

    whole_static_libs: [
        "libasync_safe",
        "libcutils",
        "libdebuggerd",
    ],

@@ -148,27 +152,6 @@ cc_library_static {
        "libdebuggerd/utility.cpp",
    ],

    target: {
        android_arm: {
            srcs: ["libdebuggerd/arm/machine.cpp"],
        },
        android_arm64: {
            srcs: ["libdebuggerd/arm64/machine.cpp"],
        },
        android_mips: {
            srcs: ["libdebuggerd/mips/machine.cpp"],
        },
        android_mips64: {
            srcs: ["libdebuggerd/mips64/machine.cpp"],
        },
        android_x86: {
            srcs: ["libdebuggerd/x86/machine.cpp"],
        },
        android_x86_64: {
            srcs: ["libdebuggerd/x86_64/machine.cpp"],
        },
    },

    local_include_dirs: ["libdebuggerd/include"],
    export_include_dirs: ["libdebuggerd/include"],

@@ -193,7 +176,6 @@ cc_test {
        "libdebuggerd/test/elf_fake.cpp",
        "libdebuggerd/test/log_fake.cpp",
        "libdebuggerd/test/open_files_list_test.cpp",
        "libdebuggerd/test/ptrace_fake.cpp",
        "libdebuggerd/test/tombstone_test.cpp",
    ],

@@ -218,6 +200,7 @@ cc_test {

    static_libs: [
        "libdebuggerd",
        "libunwindstack",
    ],

    local_include_dirs: [
@@ -264,6 +247,7 @@ cc_binary {
        "libbase",
        "liblog",
        "libprocinfo",
        "libunwindstack",
    ],
}

+297 −208

File changed.

Preview size limit exceeded, changes collapsed.

+12 −22
Original line number Diff line number Diff line
@@ -245,6 +245,8 @@ void CrasherTest::AssertDeath(int signo) {
  int status;
  pid_t pid = TIMEOUT(5, waitpid(crasher_pid, &status, 0));
  if (pid != crasher_pid) {
    printf("failed to wait for crasher (pid %d)\n", crasher_pid);
    sleep(100);
    FAIL() << "failed to wait for crasher: " << strerror(errno);
  }

@@ -341,13 +343,12 @@ TEST_F(CrasherTest, signal) {
  int intercept_result;
  unique_fd output_fd;
  StartProcess([]() {
    abort();
    while (true) {
      sleep(1);
    }
  });
  StartIntercept(&output_fd);

  // Wait for a bit, or we might end up killing the process before the signal
  // handler even gets a chance to be registered.
  std::this_thread::sleep_for(100ms);
  FinishCrasher();
  ASSERT_EQ(0, kill(crasher_pid, SIGSEGV));

  AssertDeath(SIGSEGV);
@@ -439,19 +440,6 @@ TEST_F(CrasherTest, wait_for_gdb) {
  AssertDeath(SIGABRT);
}

// wait_for_gdb shouldn't trigger on manually sent signals.
TEST_F(CrasherTest, wait_for_gdb_signal) {
  if (!android::base::SetProperty(kWaitForGdbKey, "1")) {
    FAIL() << "failed to enable wait_for_gdb";
  }

  StartProcess([]() {
    abort();
  });
  ASSERT_EQ(0, kill(crasher_pid, SIGSEGV)) << strerror(errno);
  AssertDeath(SIGSEGV);
}

TEST_F(CrasherTest, backtrace) {
  std::string result;
  int intercept_result;
@@ -596,15 +584,13 @@ TEST_F(CrasherTest, competing_tracer) {
  int intercept_result;
  unique_fd output_fd;
  StartProcess([]() {
    while (true) {
    }
    raise(SIGABRT);
  });

  StartIntercept(&output_fd);
  FinishCrasher();

  ASSERT_EQ(0, ptrace(PTRACE_SEIZE, crasher_pid, 0, 0));
  ASSERT_EQ(0, kill(crasher_pid, SIGABRT));
  FinishCrasher();

  int status;
  ASSERT_EQ(crasher_pid, waitpid(crasher_pid, &status, 0));
@@ -622,6 +608,10 @@ TEST_F(CrasherTest, competing_tracer) {
  regex += R"( \(.+debuggerd_test)";
  ASSERT_MATCH(result, regex.c_str());

  ASSERT_EQ(crasher_pid, waitpid(crasher_pid, &status, 0));
  ASSERT_TRUE(WIFSTOPPED(status));
  ASSERT_EQ(SIGABRT, WSTOPSIG(status));

  ASSERT_EQ(0, ptrace(PTRACE_DETACH, crasher_pid, 0, SIGABRT));
  AssertDeath(SIGABRT);
}
+58 −38
Original line number Diff line number Diff line
@@ -36,10 +36,14 @@
#include <unistd.h>

#include <atomic>
#include <memory>

#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <async_safe/log.h>
#include <backtrace/BacktraceMap.h>
#include <unwindstack/Memory.h>
#include <unwindstack/Regs.h>

#include "debuggerd/handler.h"
#include "tombstoned/tombstoned.h"
@@ -49,6 +53,7 @@
#include "libdebuggerd/tombstone.h"

using android::base::unique_fd;
using unwindstack::Regs;

extern "C" void __linker_enable_fallback_allocator();
extern "C" void __linker_disable_fallback_allocator();
@@ -61,7 +66,19 @@ extern "C" void __linker_disable_fallback_allocator();
// exhaustion.
static void debuggerd_fallback_trace(int output_fd, ucontext_t* ucontext) {
  __linker_enable_fallback_allocator();
  dump_backtrace_ucontext(output_fd, ucontext);
  {
    std::unique_ptr<Regs> regs;

    ThreadInfo thread;
    thread.pid = getpid();
    thread.tid = gettid();
    thread.thread_name = get_thread_name(gettid());
    thread.registers.reset(Regs::CreateFromUcontext(Regs::CurrentArch(), ucontext));

    // TODO: Create this once and store it in a global?
    std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid()));
    dump_backtrace_thread(output_fd, map.get(), thread);
  }
  __linker_disable_fallback_allocator();
}

@@ -206,7 +223,8 @@ exit:
}

static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_message) {
  // Only allow one thread to handle a crash.
  // Only allow one thread to handle a crash at a time (this can happen multiple times without
  // exit, since tombstones can be requested without a real crash happening.)
  static pthread_mutex_t crash_mutex = PTHREAD_MUTEX_INITIALIZER;
  int ret = pthread_mutex_lock(&crash_mutex);
  if (ret != 0) {
@@ -221,11 +239,13 @@ static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_mes
  if (tombstoned_connected) {
    tombstoned_notify_completion(tombstone_socket.get());
  }

  pthread_mutex_unlock(&crash_mutex);
}

extern "C" void debuggerd_fallback_handler(siginfo_t* info, ucontext_t* ucontext,
                                           void* abort_message) {
  if (info->si_signo == DEBUGGER_SIGNAL) {
  if (info->si_signo == DEBUGGER_SIGNAL && info->si_value.sival_int != 0) {
    return trace_handler(info, ucontext);
  } else {
    return crash_handler(info, ucontext, abort_message);
+150 −134
Original line number Diff line number Diff line
@@ -44,15 +44,21 @@
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>

#include <android-base/unique_fd.h>
#include <async_safe/log.h>
#include <cutils/properties.h>

#include <libdebuggerd/utility.h>

#include "dump_type.h"
#include "protocol.h"

using android::base::Pipe;
using android::base::unique_fd;

// see man(2) prctl, specifically the section about PR_GET_NAME
@@ -114,7 +120,7 @@ static void __noreturn __printflike(1, 2) fatal_errno(const char* fmt, ...) {
  va_list args;
  va_start(args, fmt);

  char buf[4096];
  char buf[256];
  async_safe_format_buffer_va_list(buf, sizeof(buf), fmt, args);
  fatal("%s: %s", buf, strerror(err));
}
@@ -147,7 +153,7 @@ static bool get_main_thread_name(char* buf, size_t len) {
 * mutex is being held, so we don't want to use any libc functions that
 * could allocate memory or hold a lock.
 */
static void log_signal_summary(int signum, const siginfo_t* info) {
static void log_signal_summary(const siginfo_t* info) {
  char thread_name[MAX_TASK_NAME_LEN + 1];  // one more for termination
  if (prctl(PR_GET_NAME, reinterpret_cast<unsigned long>(thread_name), 0, 0, 0) != 0) {
    strcpy(thread_name, "<name unknown>");
@@ -157,58 +163,20 @@ static void log_signal_summary(int signum, const siginfo_t* info) {
    thread_name[MAX_TASK_NAME_LEN] = 0;
  }

  if (signum == DEBUGGER_SIGNAL) {
  if (info->si_signo == DEBUGGER_SIGNAL) {
    async_safe_format_log(ANDROID_LOG_INFO, "libc", "Requested dump for tid %d (%s)", __gettid(),
                          thread_name);
    return;
  }

  const char* signal_name = "???";
  bool has_address = false;
  switch (signum) {
    case SIGABRT:
      signal_name = "SIGABRT";
      break;
    case SIGBUS:
      signal_name = "SIGBUS";
      has_address = true;
      break;
    case SIGFPE:
      signal_name = "SIGFPE";
      has_address = true;
      break;
    case SIGILL:
      signal_name = "SIGILL";
      has_address = true;
      break;
    case SIGSEGV:
      signal_name = "SIGSEGV";
      has_address = true;
      break;
#if defined(SIGSTKFLT)
    case SIGSTKFLT:
      signal_name = "SIGSTKFLT";
      break;
#endif
    case SIGSYS:
      signal_name = "SIGSYS";
      break;
    case SIGTRAP:
      signal_name = "SIGTRAP";
      break;
  }

  // "info" will be null if the siginfo_t information was not available.
  // Many signals don't have an address or a code.
  char code_desc[32];  // ", code -6"
  char addr_desc[32];  // ", fault addr 0x1234"
  addr_desc[0] = code_desc[0] = 0;
  if (info != nullptr) {
    async_safe_format_buffer(code_desc, sizeof(code_desc), ", code %d", info->si_code);
  const char* signal_name = get_signame(info->si_signo);
  bool has_address = signal_has_si_addr(info->si_signo, info->si_code);

  // Many signals don't have an address.
  char addr_desc[32] = "";  // ", fault addr 0x1234"
  if (has_address) {
    async_safe_format_buffer(addr_desc, sizeof(addr_desc), ", fault addr %p", info->si_addr);
  }
  }

  char main_thread_name[MAX_TASK_NAME_LEN + 1];
  if (!get_main_thread_name(main_thread_name, sizeof(main_thread_name))) {
@@ -216,8 +184,9 @@ static void log_signal_summary(int signum, const siginfo_t* info) {
  }

  async_safe_format_log(
      ANDROID_LOG_FATAL, "libc", "Fatal signal %d (%s)%s%s in tid %d (%s), pid %d (%s)", signum,
      signal_name, code_desc, addr_desc, __gettid(), thread_name, __getpid(), main_thread_name);
      ANDROID_LOG_FATAL, "libc", "Fatal signal %d (%s), code %d (%s)%s in tid %d (%s), pid %d (%s)",
      info->si_signo, signal_name, info->si_code, get_sigcode(info->si_signo, info->si_code),
      addr_desc, __gettid(), thread_name, __getpid(), main_thread_name);
}

/*
@@ -268,12 +237,44 @@ static void raise_caps() {
  }
}

static pid_t __fork() {
  return clone(nullptr, nullptr, 0, nullptr);
}

// Double-clone, with CLONE_FILES to share the file descriptor table for kcmp validation.
// Returns 0 in the orphaned child, the pid of the orphan in the original process, or -1 on failure.
static void create_vm_process() {
  pid_t first = clone(nullptr, nullptr, CLONE_FILES, nullptr);
  if (first == -1) {
    fatal_errno("failed to clone vm process");
  } else if (first == 0) {
    drop_capabilities();

    if (clone(nullptr, nullptr, CLONE_FILES, nullptr) == -1) {
      _exit(errno);
    }

    // Exit immediately on both sides of the fork.
    // crash_dump is ptracing us, so it'll get to do whatever it wants in between.
    _exit(0);
  }

  int status;
  if (TEMP_FAILURE_RETRY(waitpid(first, &status, __WCLONE)) != first) {
    fatal_errno("failed to waitpid in double fork");
  } else if (!WIFEXITED(status)) {
    fatal("intermediate process didn't exit cleanly in double fork (status = %d)", status);
  } else if (WEXITSTATUS(status)) {
    fatal("second clone failed: %s", strerror(WEXITSTATUS(status)));
  }
}

struct debugger_thread_info {
  bool crash_dump_started;
  pid_t crashing_tid;
  pid_t pseudothread_tid;
  int signal_number;
  siginfo_t* info;
  siginfo_t* siginfo;
  void* ucontext;
  uintptr_t abort_msg;
};

// Logging and contacting debuggerd requires free file descriptors, which we might not have.
@@ -284,7 +285,8 @@ struct debugger_thread_info {
static void* pseudothread_stack;

static DebuggerdDumpType get_dump_type(const debugger_thread_info* thread_info) {
  if (thread_info->signal_number == DEBUGGER_SIGNAL && thread_info->info->si_value.sival_int) {
  if (thread_info->siginfo->si_signo == DEBUGGER_SIGNAL &&
      thread_info->siginfo->si_value.sival_int) {
    return kDebuggerdNativeBacktrace;
  }

@@ -299,25 +301,58 @@ static int debuggerd_dispatch_pseudothread(void* arg) {
  }

  int devnull = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR));
  if (devnull == -1) {
    fatal_errno("failed to open /dev/null");
  } else if (devnull != 0) {
    fatal_errno("expected /dev/null fd to be 0, actually %d", devnull);
  }

  // devnull will be 0.
  TEMP_FAILURE_RETRY(dup2(devnull, STDOUT_FILENO));
  TEMP_FAILURE_RETRY(dup2(devnull, STDERR_FILENO));
  TEMP_FAILURE_RETRY(dup2(devnull, 1));
  TEMP_FAILURE_RETRY(dup2(devnull, 2));

  unique_fd pipe_read, pipe_write;
  if (!android::base::Pipe(&pipe_read, &pipe_write)) {
  unique_fd input_read, input_write;
  unique_fd output_read, output_write;
  if (!Pipe(&input_read, &input_write) != 0 || !Pipe(&output_read, &output_write)) {
    fatal_errno("failed to create pipe");
  }

  // ucontext_t is absurdly large on AArch64, so piece it together manually with writev.
  uint32_t version = 1;
  constexpr size_t expected =
      sizeof(version) + sizeof(siginfo_t) + sizeof(ucontext_t) + sizeof(uintptr_t);

  errno = 0;
  if (fcntl(output_write.get(), F_SETPIPE_SZ, expected) < static_cast<int>(expected)) {
    fatal_errno("failed to set pipe bufer size");
  }

  struct iovec iovs[4] = {
      {.iov_base = &version, .iov_len = sizeof(version)},
      {.iov_base = thread_info->siginfo, .iov_len = sizeof(siginfo_t)},
      {.iov_base = thread_info->ucontext, .iov_len = sizeof(ucontext_t)},
      {.iov_base = &thread_info->abort_msg, .iov_len = sizeof(uintptr_t)},
  };

  ssize_t rc = TEMP_FAILURE_RETRY(writev(output_write.get(), iovs, 4));
  if (rc == -1) {
    fatal_errno("failed to write crash info");
  } else if (rc != expected) {
    fatal("failed to write crash info, wrote %zd bytes, expected %zd", rc, expected);
  }

  // Don't use fork(2) to avoid calling pthread_atfork handlers.
  int forkpid = clone(nullptr, nullptr, 0, nullptr);
  if (forkpid == -1) {
  pid_t crash_dump_pid = __fork();
  if (crash_dump_pid == -1) {
    async_safe_format_log(ANDROID_LOG_FATAL, "libc",
                          "failed to fork in debuggerd signal handler: %s", strerror(errno));
  } else if (forkpid == 0) {
    TEMP_FAILURE_RETRY(dup2(pipe_write.get(), STDOUT_FILENO));
    pipe_write.reset();
    pipe_read.reset();
  } else if (crash_dump_pid == 0) {
    TEMP_FAILURE_RETRY(dup2(input_write.get(), STDOUT_FILENO));
    TEMP_FAILURE_RETRY(dup2(output_read.get(), STDIN_FILENO));
    input_read.reset();
    input_write.reset();
    output_read.reset();
    output_write.reset();

    raise_caps();

@@ -332,45 +367,49 @@ static int debuggerd_dispatch_pseudothread(void* arg) {

    execle(CRASH_DUMP_PATH, CRASH_DUMP_NAME, main_tid, pseudothread_tid, debuggerd_dump_type,
           nullptr, nullptr);

    fatal_errno("exec failed");
  } else {
    pipe_write.reset();
  }

  input_write.reset();
  output_read.reset();

  // crash_dump will ptrace and pause all of our threads, and then write to the pipe to tell
  // us to fork off a process to read memory from.
  char buf[4];
    ssize_t rc = TEMP_FAILURE_RETRY(read(pipe_read.get(), &buf, sizeof(buf)));
  rc = TEMP_FAILURE_RETRY(read(input_read.get(), &buf, sizeof(buf)));
  if (rc == -1) {
      async_safe_format_log(ANDROID_LOG_FATAL, "libc", "read of IPC pipe failed: %s",
                            strerror(errno));
    async_safe_format_log(ANDROID_LOG_FATAL, "libc", "read of IPC pipe failed: %s", strerror(errno));
    return 1;
  } else if (rc == 0) {
    async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper failed to exec");
    return 1;
  } else if (rc != 1) {
    async_safe_format_log(ANDROID_LOG_FATAL, "libc",
                          "read of IPC pipe returned unexpected value: %zd", rc);
    } else {
      if (buf[0] != '\1') {
    return 1;
  } else if (buf[0] != '\1') {
    async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper reported failure");
      } else {
        thread_info->crash_dump_started = true;
      }
    return 1;
  }
    pipe_read.reset();

  // crash_dump is ptracing us, fork off a copy of our address space for it to use.
  create_vm_process();

  input_read.reset();
  input_write.reset();

  // Don't leave a zombie child.
  int status;
    if (TEMP_FAILURE_RETRY(waitpid(forkpid, &status, 0)) == -1) {
  if (TEMP_FAILURE_RETRY(waitpid(crash_dump_pid, &status, 0)) == -1) {
    async_safe_format_log(ANDROID_LOG_FATAL, "libc", "failed to wait for crash_dump helper: %s",
                          strerror(errno));
  } else if (WIFSTOPPED(status) || WIFSIGNALED(status)) {
    async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper crashed or stopped");
      thread_info->crash_dump_started = false;
    }
  }

  syscall(__NR_exit, 0);
  return 0;
}

static void resend_signal(siginfo_t* info, bool crash_dump_started) {
static void resend_signal(siginfo_t* info) {
  // Signals can either be fatal or nonfatal.
  // For fatal signals, crash_dump will send us the signal we crashed with
  // before resuming us, so that processes using waitpid on us will see that we
@@ -379,16 +418,6 @@ static void resend_signal(siginfo_t* info, bool crash_dump_started) {
  // to deregister our signal handler for that signal before continuing.
  if (info->si_signo != DEBUGGER_SIGNAL) {
    signal(info->si_signo, SIG_DFL);
  }

  // We need to return from our signal handler so that crash_dump can see the
  // signal via ptrace and dump the thread that crashed. However, returning
  // does not guarantee that the signal will be thrown again, even for SIGSEGV
  // and friends, since the signal could have been sent manually. We blocked
  // all signals when registering the handler, so resending the signal (using
  // rt_tgsigqueueinfo(2) to preserve SA_SIGINFO) will cause it to be delivered
  // when our signal handler returns.
  if (crash_dump_started || info->si_signo != DEBUGGER_SIGNAL) {
    int rc = syscall(SYS_rt_tgsigqueueinfo, __getpid(), __gettid(), info->si_signo, info);
    if (rc != 0) {
      fatal_errno("failed to resend signal during crash");
@@ -425,7 +454,7 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
  }

  void* abort_message = nullptr;
  if (g_callbacks.get_abort_message) {
  if (signal_number != DEBUGGER_SIGNAL && g_callbacks.get_abort_message) {
    abort_message = g_callbacks.get_abort_message();
  }

@@ -439,7 +468,7 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
    // you can only set NO_NEW_PRIVS to 1, and the effect should be at worst a single missing
    // ANR trace.
    debuggerd_fallback_handler(info, static_cast<ucontext_t*>(context), abort_message);
    resend_signal(info, false);
    resend_signal(info);
    return;
  }

@@ -450,20 +479,14 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
    return;
  }

  log_signal_summary(signal_number, info);

  // If this was a fatal crash, populate si_value with the abort message address if possible.
  // Note that applications can set an abort message without aborting.
  if (abort_message && signal_number != DEBUGGER_SIGNAL) {
    info->si_value.sival_ptr = abort_message;
  }
  log_signal_summary(info);

  debugger_thread_info thread_info = {
    .crash_dump_started = false,
      .pseudothread_tid = -1,
      .crashing_tid = __gettid(),
    .signal_number = signal_number,
    .info = info
      .siginfo = info,
      .ucontext = context,
      .abort_msg = reinterpret_cast<uintptr_t>(abort_message),
  };

  // Set PR_SET_DUMPABLE to 1, so that crash_dump can ptrace us.
@@ -472,7 +495,8 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
    fatal_errno("failed to set dumpable");
  }

  // Essentially pthread_create without CLONE_FILES (see debuggerd_dispatch_pseudothread).
  // Essentially pthread_create without CLONE_FILES, so we still work during file descriptor
  // exhaustion.
  pid_t child_pid =
    clone(debuggerd_dispatch_pseudothread, pseudothread_stack,
          CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,
@@ -484,7 +508,7 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
  // Wait for the child to start...
  futex_wait(&thread_info.pseudothread_tid, -1);

  // and then wait for it to finish.
  // and then wait for it to terminate.
  futex_wait(&thread_info.pseudothread_tid, child_pid);

  // Restore PR_SET_DUMPABLE to its original value.
@@ -492,21 +516,13 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
    fatal_errno("failed to restore dumpable");
  }

  // Signals can either be fatal or nonfatal.
  // For fatal signals, crash_dump will PTRACE_CONT us with the signal we
  // crashed with, so that processes using waitpid on us will see that we
  // exited with the correct exit status (e.g. so that sh will report
  // "Segmentation fault" instead of "Killed"). For this to work, we need
  // to deregister our signal handler for that signal before continuing.
  if (signal_number != DEBUGGER_SIGNAL) {
    signal(signal_number, SIG_DFL);
  }

  resend_signal(info, thread_info.crash_dump_started);
  if (info->si_signo == DEBUGGER_SIGNAL) {
    // If the signal is fatal, don't unlock the mutex to prevent other crashing threads from
    // starting to dump right before our death.
    pthread_mutex_unlock(&crash_mutex);
  } else {
    // Resend the signal, so that either gdb or the parent's waitpid sees it.
    resend_signal(info);
  }
}

Loading