/* * Catches fatal signals so we can ask debuggerd to ptrace us before * we crash. */ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void*) { // It's possible somebody cleared the SA_SIGINFO flag, which would mean // our "info" arg holds an undefined value. if (!have_siginfo(signal_number)) { info = NULL; } log_signal_summary(signal_number, info); send_debuggerd_packet(info); // Remove our net so we fault for real when we return. signal(signal_number, SIG_DFL); // These signals are not re-thrown when we resume. This means that // crashing due to (say) SIGPIPE doesn't work the way you'd expect it // to. We work around this by throwing them manually. We don't want // to do this for *all* signals because it'll screw up the si_addr for // faults like SIGSEGV. It does screw up the si_code, which is why we // passed that to debuggerd above. switch (signal_number) { case SIGABRT: case SIGFPE: case SIGPIPE: #if defined(SIGSTKFLT) case SIGSTKFLT: #endif case SIGTRAP: tgkill(getpid(), gettid(), signal_number); break; default: // SIGILL, SIGBUS, SIGSEGV break; } }
/* * Catches fatal signals so we can ask debuggerd to ptrace us before * we crash. */ void debuggerd_signal_handler(int n, siginfo_t* info, void*) { /* * It's possible somebody cleared the SA_SIGINFO flag, which would mean * our "info" arg holds an undefined value. */ if (!have_siginfo(n)) { info = NULL; } log_signal_summary(n, info); pid_t tid = gettid(); int s = socket_abstract_client(DEBUGGER_SOCKET_NAME, SOCK_STREAM); if (s >= 0) { // debuggerd knows our pid from the credentials on the // local socket but we need to tell it the tid of the crashing thread. // debuggerd will be paranoid and verify that we sent a tid // that's actually in our process. debugger_msg_t msg; msg.action = DEBUGGER_ACTION_CRASH; msg.tid = tid; msg.abort_msg_address = reinterpret_cast<uintptr_t>(gAbortMessage); int ret = TEMP_FAILURE_RETRY(write(s, &msg, sizeof(msg))); if (ret == sizeof(msg)) { #ifdef HAVE_AEE_FEATURE int tmppid = getpid(); __libc_format_log(ANDROID_LOG_FATAL, "libc", "Send stop signal to pid:%d in %s", tmppid, __func__); kill(tmppid, SIGSTOP); #endif // if the write failed, there is no point trying to read a response. ret = TEMP_FAILURE_RETRY(read(s, &tid, 1)); int saved_errno = errno; notify_gdb_of_libraries(); errno = saved_errno; } if (ret < 0) { /* read or write failed -- broken connection? */ __libc_format_log(ANDROID_LOG_FATAL, "libc", "Failed while talking to debuggerd: %s", strerror(errno)); } close(s); } else { /* socket failed; maybe process ran out of fds */ __libc_format_log(ANDROID_LOG_FATAL, "libc", "Unable to open connection to debuggerd: %s", strerror(errno)); } /* remove our net so we fault for real when we return */ signal(n, SIG_DFL); /* * These signals are not re-thrown when we resume. This means that * crashing due to (say) SIGPIPE doesn't work the way you'd expect it * to. We work around this by throwing them manually. We don't want * to do this for *all* signals because it'll screw up the address for * faults like SIGSEGV. */ switch (n) { case SIGABRT: case SIGFPE: case SIGPIPE: #ifdef SIGSTKFLT case SIGSTKFLT: #endif (void) tgkill(getpid(), gettid(), n); break; default: // SIGILL, SIGBUS, SIGSEGV break; } }
// Handler that does crash dumping by forking and doing the processing in the child. // Do this by ptracing the relevant thread, and then execing debuggerd to do the actual dump. static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* context) { // Make sure we don't change the value of errno, in case a signal comes in between the process // making a syscall and checking errno. ErrnoRestorer restorer; // It's possible somebody cleared the SA_SIGINFO flag, which would mean // our "info" arg holds an undefined value. if (!have_siginfo(signal_number)) { info = nullptr; } struct siginfo si = {}; if (!info) { memset(&si, 0, sizeof(si)); si.si_signo = signal_number; si.si_code = SI_USER; si.si_pid = __getpid(); si.si_uid = getuid(); info = &si; } else if (info->si_code >= 0 || info->si_code == SI_TKILL) { // rt_tgsigqueueinfo(2)'s documentation appears to be incorrect on kernels // that contain commit 66dd34a (3.9+). The manpage claims to only allow // negative si_code values that are not SI_TKILL, but 66dd34a changed the // check to allow all si_code values in calls coming from inside the house. } void* abort_message = nullptr; if (g_callbacks.get_abort_message) { abort_message = g_callbacks.get_abort_message(); } if (prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1) { // This check might be racy if another thread sets NO_NEW_PRIVS, but this should be unlikely, // 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); return; } // Only allow one thread to handle a signal at a time. int ret = pthread_mutex_lock(&crash_mutex); if (ret != 0) { async_safe_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_lock failed: %s", strerror(ret)); 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; } debugger_thread_info thread_info = { .crash_dump_started = false, .pseudothread_tid = -1, .crashing_tid = __gettid(), .signal_number = signal_number, .info = info }; // Set PR_SET_DUMPABLE to 1, so that crash_dump can ptrace us. int orig_dumpable = prctl(PR_GET_DUMPABLE); if (prctl(PR_SET_DUMPABLE, 1) != 0) { fatal_errno("failed to set dumpable"); } // Essentially pthread_create without CLONE_FILES (see debuggerd_dispatch_pseudothread). pid_t child_pid = clone(debuggerd_dispatch_pseudothread, pseudothread_stack, CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID, &thread_info, nullptr, nullptr, &thread_info.pseudothread_tid); if (child_pid == -1) { fatal_errno("failed to spawn debuggerd dispatch thread"); } // Wait for the child to start... futex_wait(&thread_info.pseudothread_tid, -1); // and then wait for it to finish. futex_wait(&thread_info.pseudothread_tid, child_pid); // Restore PR_SET_DUMPABLE to its original value. if (prctl(PR_SET_DUMPABLE, orig_dumpable) != 0) { 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); } } void debuggerd_init(debuggerd_callbacks_t* callbacks) { if (callbacks) { g_callbacks = *callbacks; } void* thread_stack_allocation = mmap(nullptr, PAGE_SIZE * 3, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (thread_stack_allocation == MAP_FAILED) { fatal_errno("failed to allocate debuggerd thread stack"); } char* stack = static_cast<char*>(thread_stack_allocation) + PAGE_SIZE; if (mprotect(stack, PAGE_SIZE, PROT_READ | PROT_WRITE) != 0) { fatal_errno("failed to mprotect debuggerd thread stack"); } // Stack grows negatively, set it to the last byte in the page... stack = (stack + PAGE_SIZE - 1); // and align it. stack -= 15; pseudothread_stack = stack; struct sigaction action; memset(&action, 0, sizeof(action)); sigfillset(&action.sa_mask); action.sa_sigaction = debuggerd_signal_handler; action.sa_flags = SA_RESTART | SA_SIGINFO; // Use the alternate signal stack if available so we can catch stack overflows. action.sa_flags |= SA_ONSTACK; debuggerd_register_handlers(&action); }