diff options
Diffstat (limited to 'debuggerd/handler/debuggerd_fallback.cpp')
-rw-r--r-- | debuggerd/handler/debuggerd_fallback.cpp | 209 |
1 files changed, 196 insertions, 13 deletions
diff --git a/debuggerd/handler/debuggerd_fallback.cpp b/debuggerd/handler/debuggerd_fallback.cpp index 77ad6ac1e..5c6c59c2c 100644 --- a/debuggerd/handler/debuggerd_fallback.cpp +++ b/debuggerd/handler/debuggerd_fallback.cpp @@ -26,23 +26,206 @@ * SUCH DAMAGE. */ +#include <dirent.h> +#include <fcntl.h> +#include <poll.h> +#include <pthread.h> #include <stddef.h> #include <sys/ucontext.h> +#include <syscall.h> #include <unistd.h> +#include <atomic> + +#include <android-base/file.h> +#include <android-base/unique_fd.h> + +#include "debuggerd/handler.h" +#include "debuggerd/tombstoned.h" +#include "debuggerd/util.h" + +#include "backtrace.h" #include "tombstone.h" -extern "C" void __linker_use_fallback_allocator(); - -extern "C" bool debuggerd_fallback(ucontext_t* ucontext, siginfo_t* siginfo, void* abort_message) { - // This is incredibly sketchy to do inside of a signal handler, especially when libbacktrace - // uses the C++ standard library throughout, but this code runs in the linker, so we'll be using - // the linker's malloc instead of the libc one. Switch it out for a replacement, just in case. - // - // This isn't the default method of dumping because it can fail in cases such as memory space - // exhaustion. - __linker_use_fallback_allocator(); - engrave_tombstone_ucontext(-1, getpid(), gettid(), reinterpret_cast<uintptr_t>(abort_message), - siginfo, ucontext); - return true; +#include "private/libc_logging.h" + +using android::base::unique_fd; + +extern "C" void __linker_enable_fallback_allocator(); +extern "C" void __linker_disable_fallback_allocator(); + +// This is incredibly sketchy to do inside of a signal handler, especially when libbacktrace +// uses the C++ standard library throughout, but this code runs in the linker, so we'll be using +// the linker's malloc instead of the libc one. Switch it out for a replacement, just in case. +// +// This isn't the default method of dumping because it can fail in cases such as address space +// exhaustion. +static void debuggerd_fallback_trace(int output_fd, ucontext_t* ucontext) { + __linker_enable_fallback_allocator(); + dump_backtrace_ucontext(output_fd, ucontext); + __linker_disable_fallback_allocator(); +} + +static void debuggerd_fallback_tombstone(int output_fd, ucontext_t* ucontext, siginfo_t* siginfo, + void* abort_message) { + __linker_enable_fallback_allocator(); + engrave_tombstone_ucontext(output_fd, reinterpret_cast<uintptr_t>(abort_message), siginfo, + ucontext); + __linker_disable_fallback_allocator(); +} + +static void iterate_siblings(bool (*callback)(pid_t, int), int output_fd) { + pid_t current_tid = gettid(); + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), "/proc/%d/task", current_tid); + DIR* dir = opendir(buf); + + if (!dir) { + __libc_format_log(ANDROID_LOG_ERROR, "libc", "failed to open %s: %s", buf, strerror(errno)); + return; + } + + struct dirent* ent; + while ((ent = readdir(dir))) { + char* end; + long tid = strtol(ent->d_name, &end, 10); + if (end == ent->d_name || *end != '\0') { + continue; + } + + if (tid != current_tid) { + callback(tid, output_fd); + } + } + closedir(dir); +} + +static bool forward_output(int src_fd, int dst_fd) { + // Make sure the thread actually got the signal. + struct pollfd pfd = { + .fd = src_fd, .events = POLLIN, + }; + + // Wait for up to a second for output to start flowing. + if (poll(&pfd, 1, 1000) != 1) { + return false; + } + + while (true) { + char buf[512]; + ssize_t rc = TEMP_FAILURE_RETRY(read(src_fd, buf, sizeof(buf))); + if (rc == 0) { + return true; + } else if (rc < 0) { + return false; + } + + if (!android::base::WriteFully(dst_fd, buf, rc)) { + // We failed to write to tombstoned, but there's not much we can do. + // Keep reading from src_fd to keep things going. + continue; + } + } +} + +static void trace_handler(siginfo_t* info, ucontext_t* ucontext) { + static std::atomic<int> trace_output_fd(-1); + + if (info->si_value.sival_int == ~0) { + // Asked to dump by the original signal recipient. + debuggerd_fallback_trace(trace_output_fd, ucontext); + + int tmp = trace_output_fd.load(); + trace_output_fd.store(-1); + close(tmp); + return; + } + + // Only allow one thread to perform a trace at a time. + static pthread_mutex_t trace_mutex = PTHREAD_MUTEX_INITIALIZER; + int ret = pthread_mutex_trylock(&trace_mutex); + if (ret != 0) { + __libc_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_try_lock failed: %s", strerror(ret)); + return; + } + + // Fetch output fd from tombstoned. + unique_fd tombstone_socket, output_fd; + if (!tombstoned_connect(getpid(), &tombstone_socket, &output_fd)) { + goto exit; + } + + dump_backtrace_header(output_fd.get()); + + // Dump our own stack. + debuggerd_fallback_trace(output_fd.get(), ucontext); + + // Send a signal to all of our siblings, asking them to dump their stack. + iterate_siblings( + [](pid_t tid, int output_fd) { + // Use a pipe, to be able to detect situations where the thread gracefully exits before + // receiving our signal. + unique_fd pipe_read, pipe_write; + if (!Pipe(&pipe_read, &pipe_write)) { + __libc_format_log(ANDROID_LOG_ERROR, "libc", "failed to create pipe: %s", strerror(errno)); + return false; + } + + trace_output_fd.store(pipe_write.get()); + + siginfo_t siginfo = {}; + siginfo.si_code = SI_QUEUE; + siginfo.si_value.sival_int = ~0; + siginfo.si_pid = getpid(); + siginfo.si_uid = getuid(); + + if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, DEBUGGER_SIGNAL, &siginfo) != 0) { + __libc_format_log(ANDROID_LOG_ERROR, "libc", "failed to send trace signal to %d: %s", tid, + strerror(errno)); + return false; + } + + bool success = forward_output(pipe_read.get(), output_fd); + if (success) { + // The signaled thread has closed trace_output_fd already. + (void)pipe_write.release(); + } else { + trace_output_fd.store(-1); + } + + return true; + }, + output_fd.get()); + + dump_backtrace_footer(output_fd.get()); + tombstoned_notify_completion(tombstone_socket.get()); + +exit: + pthread_mutex_unlock(&trace_mutex); +} + +static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_message) { + // Only allow one thread to handle a crash. + static pthread_mutex_t crash_mutex = PTHREAD_MUTEX_INITIALIZER; + int ret = pthread_mutex_lock(&crash_mutex); + if (ret != 0) { + __libc_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_lock failed: %s", strerror(ret)); + return; + } + + unique_fd tombstone_socket, output_fd; + bool tombstoned_connected = tombstoned_connect(getpid(), &tombstone_socket, &output_fd); + debuggerd_fallback_tombstone(output_fd.get(), ucontext, info, abort_message); + if (tombstoned_connected) { + tombstoned_notify_completion(tombstone_socket.get()); + } +} + +extern "C" void debuggerd_fallback_handler(siginfo_t* info, ucontext_t* ucontext, + void* abort_message) { + if (info->si_signo == DEBUGGER_SIGNAL) { + return trace_handler(info, ucontext); + } else { + return crash_handler(info, ucontext, abort_message); + } } |