diff options
Diffstat (limited to 'debuggerd/debuggerd.cpp')
-rw-r--r-- | debuggerd/debuggerd.cpp | 332 |
1 files changed, 173 insertions, 159 deletions
diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp index 8efbacc99..12f507a79 100644 --- a/debuggerd/debuggerd.cpp +++ b/debuggerd/debuggerd.cpp @@ -15,21 +15,20 @@ */ #include <dirent.h> +#include <elf.h> #include <errno.h> #include <fcntl.h> #include <pthread.h> #include <signal.h> #include <stdarg.h> #include <stdio.h> -#include <sys/types.h> -#include <time.h> - -#include <elf.h> #include <sys/poll.h> #include <sys/prctl.h> #include <sys/ptrace.h> #include <sys/stat.h> +#include <sys/types.h> #include <sys/wait.h> +#include <time.h> #include <set> @@ -48,6 +47,7 @@ #include "backtrace.h" #include "getevent.h" +#include "signal_sender.h" #include "tombstone.h" #include "utility.h" @@ -248,8 +248,8 @@ static int read_request(int fd, debugger_request_t* out_request) { return 0; } -static bool should_attach_gdb(debugger_request_t* request) { - if (request->action == DEBUGGER_ACTION_CRASH) { +static bool should_attach_gdb(const debugger_request_t& request) { + if (request.action == DEBUGGER_ACTION_CRASH) { return property_get_bool("debug.debuggerd.wait_for_gdb", false); } return false; @@ -374,7 +374,8 @@ static void ptrace_siblings(pid_t pid, pid_t main_tid, std::set<pid_t>& tids) { } static bool perform_dump(const debugger_request_t& request, int fd, int tombstone_fd, - BacktraceMap* backtrace_map, const std::set<pid_t>& siblings) { + BacktraceMap* backtrace_map, const std::set<pid_t>& siblings, + int* crash_signal) { if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) { ALOGE("debuggerd: failed to respond to client: %s\n", strerror(errno)); return false; @@ -414,13 +415,10 @@ static bool perform_dump(const debugger_request_t& request, int fd, int tombston #ifdef SIGSTKFLT case SIGSTKFLT: #endif + case SIGSYS: case SIGTRAP: ALOGV("stopped -- fatal signal\n"); - // Send a SIGSTOP to the process to make all of - // the non-signaled threads stop moving. Without - // this we get a lot of "ptrace detach failed: - // No such process". - kill(request.pid, SIGSTOP); + *crash_signal = signal; engrave_tombstone(tombstone_fd, backtrace_map, request.pid, request.tid, siblings, signal, request.original_si_code, request.abort_msg_address); break; @@ -449,104 +447,7 @@ static bool drop_privileges() { return true; } -static bool fork_signal_sender(int* in_fd, int* out_fd, pid_t* sender_pid, pid_t target_pid) { - int input_pipe[2]; - int output_pipe[2]; - if (pipe(input_pipe) != 0) { - ALOGE("debuggerd: failed to create input pipe for signal sender: %s", strerror(errno)); - return false; - } - - if (pipe(output_pipe) != 0) { - close(input_pipe[0]); - close(input_pipe[1]); - ALOGE("debuggerd: failed to create output pipe for signal sender: %s", strerror(errno)); - return false; - } - - pid_t fork_pid = fork(); - if (fork_pid == -1) { - ALOGE("debuggerd: failed to initialize signal sender: fork failed: %s", strerror(errno)); - return false; - } else if (fork_pid == 0) { - close(input_pipe[1]); - close(output_pipe[0]); - auto wait = [=]() { - char buf[1]; - if (TEMP_FAILURE_RETRY(read(input_pipe[0], buf, 1)) != 1) { - ALOGE("debuggerd: signal sender failed to read from pipe"); - exit(1); - } - }; - auto notify_done = [=]() { - if (TEMP_FAILURE_RETRY(write(output_pipe[1], "", 1)) != 1) { - ALOGE("debuggerd: signal sender failed to write to pipe"); - exit(1); - } - }; - - wait(); - if (kill(target_pid, SIGSTOP) != 0) { - ALOGE("debuggerd: failed to stop target '%d': %s", target_pid, strerror(errno)); - } - notify_done(); - - wait(); - if (kill(target_pid, SIGCONT) != 0) { - ALOGE("debuggerd: failed to resume target '%d': %s", target_pid, strerror(errno)); - } - notify_done(); - - exit(0); - } else { - close(input_pipe[0]); - close(output_pipe[1]); - *in_fd = input_pipe[1]; - *out_fd = output_pipe[0]; - *sender_pid = fork_pid; - return true; - } -} - -static void handle_request(int fd) { - ALOGV("handle_request(%d)\n", fd); - - ScopedFd closer(fd); - debugger_request_t request; - memset(&request, 0, sizeof(request)); - int status = read_request(fd, &request); - if (status != 0) { - return; - } - - ALOGV("BOOM: pid=%d uid=%d gid=%d tid=%d\n", request.pid, request.uid, request.gid, request.tid); - -#if defined(__LP64__) - // On 64 bit systems, requests to dump 32 bit and 64 bit tids come - // to the 64 bit debuggerd. If the process is a 32 bit executable, - // redirect the request to the 32 bit debuggerd. - if (is32bit(request.tid)) { - // Only dump backtrace and dump tombstone requests can be redirected. - if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE || - request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { - redirect_to_32(fd, &request); - } else { - ALOGE("debuggerd: Not allowed to redirect action %d to 32 bit debuggerd\n", request.action); - } - return; - } -#endif - - // Fork a child to handle the rest of the request. - pid_t fork_pid = fork(); - if (fork_pid == -1) { - ALOGE("debuggerd: failed to fork: %s\n", strerror(errno)); - return; - } else if (fork_pid != 0) { - waitpid(fork_pid, nullptr, 0); - return; - } - +static void worker_process(int fd, debugger_request_t& request) { // Open the tombstone file if we need it. std::string tombstone_path; int tombstone_fd = -1; @@ -587,10 +488,7 @@ static void handle_request(int fd) { // Don't attach to the sibling threads if we want to attach gdb. // Supposedly, it makes the process less reliable. - bool attach_gdb = should_attach_gdb(&request); - int signal_in_fd = -1; - int signal_out_fd = -1; - pid_t signal_pid = 0; + bool attach_gdb = should_attach_gdb(request); if (attach_gdb) { // Open all of the input devices we need to listen for VOLUMEDOWN before dropping privileges. if (init_getevent() != 0) { @@ -598,21 +496,8 @@ static void handle_request(int fd) { attach_gdb = false; } - // Fork a process that stays root, and listens on a pipe to pause and resume the target. - if (!fork_signal_sender(&signal_in_fd, &signal_out_fd, &signal_pid, request.pid)) { - attach_gdb = false; - } } - auto notify_signal_sender = [=]() { - char buf[1]; - if (TEMP_FAILURE_RETRY(write(signal_in_fd, "", 1)) != 1) { - ALOGE("debuggerd: failed to notify signal process: %s", strerror(errno)); - } else if (TEMP_FAILURE_RETRY(read(signal_out_fd, buf, 1)) != 1) { - ALOGE("debuggerd: failed to read response from signal process: %s", strerror(errno)); - } - }; - std::set<pid_t> siblings; if (!attach_gdb) { ptrace_siblings(request.pid, request.tid, siblings); @@ -624,19 +509,26 @@ static void handle_request(int fd) { bool succeeded = false; // Now that we've done everything that requires privileges, we can drop them. - if (drop_privileges()) { - succeeded = perform_dump(request, fd, tombstone_fd, backtrace_map.get(), siblings); - if (succeeded) { - if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { - if (!tombstone_path.empty()) { - write(fd, tombstone_path.c_str(), tombstone_path.length()); - } + if (!drop_privileges()) { + ALOGE("debuggerd: failed to drop privileges, exiting"); + _exit(1); + } + + int crash_signal = SIGKILL; + succeeded = perform_dump(request, fd, tombstone_fd, backtrace_map.get(), siblings, &crash_signal); + if (succeeded) { + if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { + if (!tombstone_path.empty()) { + write(fd, tombstone_path.c_str(), tombstone_path.length()); } } + } - if (attach_gdb) { - // Tell the signal process to send SIGSTOP to the target. - notify_signal_sender(); + if (attach_gdb) { + // Tell the signal process to send SIGSTOP to the target. + if (!send_signal(request.pid, 0, SIGSTOP)) { + ALOGE("debuggerd: failed to stop process for gdb attach: %s", strerror(errno)); + attach_gdb = false; } } @@ -648,20 +540,147 @@ static void handle_request(int fd) { ptrace(PTRACE_DETACH, sibling, 0, 0); } + // Send the signal back to the process if it crashed and we're not waiting for gdb. + if (!attach_gdb && request.action == DEBUGGER_ACTION_CRASH) { + if (!send_signal(request.pid, request.tid, crash_signal)) { + ALOGE("debuggerd: failed to kill process %d: %s", request.pid, strerror(errno)); + } + } + // Wait for gdb, if requested. if (attach_gdb && succeeded) { wait_for_user_action(request); // Tell the signal process to send SIGCONT to the target. - notify_signal_sender(); + if (!send_signal(request.pid, 0, SIGCONT)) { + ALOGE("debuggerd: failed to resume process %d: %s", request.pid, strerror(errno)); + } uninit_getevent(); - waitpid(signal_pid, nullptr, 0); } exit(!succeeded); } +static void monitor_worker_process(int child_pid, const debugger_request_t& request) { + struct timespec timeout = {.tv_sec = 10, .tv_nsec = 0 }; + if (should_attach_gdb(request)) { + // If wait_for_gdb is enabled, set the timeout to something large. + timeout.tv_sec = INT_MAX; + } + + sigset_t signal_set; + sigemptyset(&signal_set); + sigaddset(&signal_set, SIGCHLD); + + bool kill_worker = false; + bool kill_target = false; + bool kill_self = false; + + int status; + siginfo_t siginfo; + int signal = TEMP_FAILURE_RETRY(sigtimedwait(&signal_set, &siginfo, &timeout)); + if (signal == SIGCHLD) { + pid_t rc = waitpid(-1, &status, WNOHANG | WUNTRACED); + if (rc != child_pid) { + ALOGE("debuggerd: waitpid returned unexpected pid (%d), committing murder-suicide", rc); + + if (WIFEXITED(status)) { + ALOGW("debuggerd: pid %d exited with status %d", rc, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + ALOGW("debuggerd: pid %d received signal %d", rc, WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + ALOGW("debuggerd: pid %d stopped by signal %d", rc, WSTOPSIG(status)); + } else if (WIFCONTINUED(status)) { + ALOGW("debuggerd: pid %d continued", rc); + } + + kill_worker = true; + kill_target = true; + kill_self = true; + } else if (WIFSIGNALED(status)) { + ALOGE("debuggerd: worker process %d terminated due to signal %d", child_pid, WTERMSIG(status)); + kill_worker = false; + kill_target = true; + } else if (WIFSTOPPED(status)) { + ALOGE("debuggerd: worker process %d stopped due to signal %d", child_pid, WSTOPSIG(status)); + kill_worker = true; + kill_target = true; + } + } else { + ALOGE("debuggerd: worker process %d timed out", child_pid); + kill_worker = true; + kill_target = true; + } + + if (kill_worker) { + // Something bad happened, kill the worker. + if (kill(child_pid, SIGKILL) != 0) { + ALOGE("debuggerd: failed to kill worker process %d: %s", child_pid, strerror(errno)); + } else { + waitpid(child_pid, &status, 0); + } + } + + int exit_signal = SIGCONT; + if (kill_target && request.action == DEBUGGER_ACTION_CRASH) { + ALOGE("debuggerd: killing target %d", request.pid); + exit_signal = SIGKILL; + } else { + ALOGW("debuggerd: resuming target %d", request.pid); + } + + if (kill(request.pid, exit_signal) != 0) { + ALOGE("debuggerd: failed to send signal %d to target: %s", exit_signal, strerror(errno)); + } + + if (kill_self) { + stop_signal_sender(); + _exit(1); + } +} + +static void handle_request(int fd) { + ALOGV("handle_request(%d)\n", fd); + + ScopedFd closer(fd); + debugger_request_t request; + memset(&request, 0, sizeof(request)); + int status = read_request(fd, &request); + if (status != 0) { + return; + } + + ALOGW("debuggerd: handling request: pid=%d uid=%d gid=%d tid=%d\n", request.pid, request.uid, + request.gid, request.tid); + +#if defined(__LP64__) + // On 64 bit systems, requests to dump 32 bit and 64 bit tids come + // to the 64 bit debuggerd. If the process is a 32 bit executable, + // redirect the request to the 32 bit debuggerd. + if (is32bit(request.tid)) { + // Only dump backtrace and dump tombstone requests can be redirected. + if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE || + request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { + redirect_to_32(fd, &request); + } else { + ALOGE("debuggerd: Not allowed to redirect action %d to 32 bit debuggerd\n", request.action); + } + return; + } +#endif + + // Fork a child to handle the rest of the request. + pid_t fork_pid = fork(); + if (fork_pid == -1) { + ALOGE("debuggerd: failed to fork: %s\n", strerror(errno)); + } else if (fork_pid == 0) { + worker_process(fd, request); + } else { + monitor_worker_process(fork_pid, request); + } +} + static int do_server() { // debuggerd crashes can't be reported to debuggerd. // Reset all of the crash handlers. @@ -678,24 +697,21 @@ static int do_server() { // Ignore failed writes to closed sockets signal(SIGPIPE, SIG_IGN); - int logsocket = socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_DGRAM); - if (logsocket < 0) { - logsocket = -1; - } else { - fcntl(logsocket, F_SETFD, FD_CLOEXEC); - } + // Block SIGCHLD so we can sigtimedwait for it. + sigset_t sigchld; + sigemptyset(&sigchld); + sigaddset(&sigchld, SIGCHLD); + sigprocmask(SIG_SETMASK, &sigchld, nullptr); - struct sigaction act; - act.sa_handler = SIG_DFL; - sigemptyset(&act.sa_mask); - sigaddset(&act.sa_mask,SIGCHLD); - act.sa_flags = SA_NOCLDWAIT; - sigaction(SIGCHLD, &act, 0); + int s = socket_local_server(SOCKET_NAME, ANDROID_SOCKET_NAMESPACE_ABSTRACT, + SOCK_STREAM | SOCK_CLOEXEC); + if (s == -1) return 1; - int s = socket_local_server(SOCKET_NAME, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); - if (s < 0) + // Fork a process that stays root, and listens on a pipe to pause and resume the target. + if (!start_signal_sender()) { + ALOGE("debuggerd: failed to fork signal sender"); return 1; - fcntl(s, F_SETFD, FD_CLOEXEC); + } ALOGI("debuggerd: starting\n"); @@ -705,14 +721,12 @@ static int do_server() { socklen_t alen = sizeof(ss); ALOGV("waiting for connection\n"); - int fd = accept(s, addrp, &alen); - if (fd < 0) { - ALOGV("accept failed: %s\n", strerror(errno)); + int fd = accept4(s, addrp, &alen, SOCK_CLOEXEC); + if (fd == -1) { + ALOGE("accept failed: %s\n", strerror(errno)); continue; } - fcntl(fd, F_SETFD, FD_CLOEXEC); - handle_request(fd); } return 0; |