Loading debuggerd/backtrace.cpp +7 −39 Original line number Diff line number Diff line Loading @@ -67,8 +67,7 @@ static void dump_process_footer(log_t* log, pid_t pid) { _LOG(log, logtype::BACKTRACE, "\n----- end %d -----\n", pid); } static void dump_thread( log_t* log, pid_t tid, bool attached, bool* detach_failed, int* total_sleep_time_usec) { static void dump_thread(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid) { char path[PATH_MAX]; char threadnamebuf[1024]; char* threadname = NULL; Loading @@ -88,56 +87,25 @@ static void dump_thread( _LOG(log, logtype::BACKTRACE, "\n\"%s\" sysTid=%d\n", threadname ? threadname : "<unknown>", tid); if (!attached && ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) { _LOG(log, logtype::BACKTRACE, "Could not attach to thread: %s\n", strerror(errno)); return; } if (!attached && wait_for_sigstop(tid, total_sleep_time_usec, detach_failed) == -1) { return; } std::unique_ptr<Backtrace> backtrace(Backtrace::Create(tid, BACKTRACE_CURRENT_THREAD)); std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map)); if (backtrace->Unwind(0)) { dump_backtrace_to_log(backtrace.get(), log, " "); } else { ALOGE("Unwind failed: tid = %d", tid); } if (!attached && ptrace(PTRACE_DETACH, tid, 0, 0) != 0) { ALOGE("ptrace detach from %d failed: %s\n", tid, strerror(errno)); *detach_failed = true; } } void dump_backtrace(int fd, int amfd, pid_t pid, pid_t tid, bool* detach_failed, int* total_sleep_time_usec) { void dump_backtrace(int fd, int amfd, BacktraceMap* map, pid_t pid, pid_t tid, const std::set<pid_t>& siblings) { log_t log; log.tfd = fd; log.amfd = amfd; dump_process_header(&log, pid); dump_thread(&log, tid, true, detach_failed, total_sleep_time_usec); char task_path[64]; snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid); DIR* d = opendir(task_path); if (d != NULL) { struct dirent* de = NULL; while ((de = readdir(d)) != NULL) { if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { continue; } dump_thread(&log, map, pid, tid); char* end; pid_t new_tid = strtoul(de->d_name, &end, 10); if (*end || new_tid == tid) { continue; } dump_thread(&log, new_tid, false, detach_failed, total_sleep_time_usec); } closedir(d); for (pid_t sibling : siblings) { dump_thread(&log, map, pid, sibling); } dump_process_footer(&log, pid); Loading debuggerd/backtrace.h +5 −2 Original line number Diff line number Diff line Loading @@ -19,14 +19,17 @@ #include <sys/types.h> #include <set> #include "utility.h" class Backtrace; class BacktraceMap; // Dumps a backtrace using a format similar to what Dalvik uses so that the result // can be intermixed in a bug report. void dump_backtrace(int fd, int amfd, pid_t pid, pid_t tid, bool* detach_failed, int* total_sleep_time_usec); void dump_backtrace(int fd, int amfd, BacktraceMap* map, pid_t pid, pid_t tid, const std::set<pid_t>& siblings); /* Dumps the backtrace in the backtrace data structure to the log. */ void dump_backtrace_to_log(Backtrace* backtrace, log_t* log, const char* prefix); Loading debuggerd/debuggerd.cpp +162 −97 Original line number Diff line number Diff line Loading @@ -14,14 +14,14 @@ * limitations under the License. */ #include <stdio.h> #include <dirent.h> #include <errno.h> #include <signal.h> #include <fcntl.h> #include <pthread.h> #include <signal.h> #include <stdarg.h> #include <fcntl.h> #include <stdio.h> #include <sys/types.h> #include <dirent.h> #include <time.h> #include <elf.h> Loading @@ -31,6 +31,8 @@ #include <sys/stat.h> #include <sys/wait.h> #include <set> #include <selinux/android.h> #include <log/logger.h> Loading @@ -57,6 +59,8 @@ #define SOCKET_NAME DEBUGGER_SOCKET_NAME #endif extern "C" int tgkill(int tgid, int tid, int sig); struct debugger_request_t { debugger_action_t action; pid_t pid, tid; Loading Loading @@ -335,6 +339,121 @@ static void redirect_to_32(int fd, debugger_request_t* request) { } #endif static void ptrace_siblings(pid_t pid, pid_t main_tid, std::set<pid_t>& tids) { char task_path[64]; snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid); std::unique_ptr<DIR, int (*)(DIR*)> d(opendir(task_path), closedir); // Bail early if the task directory cannot be opened. if (!d) { ALOGE("debuggerd: failed to open /proc/%d/task: %s", pid, strerror(errno)); return; } struct dirent* de; while ((de = readdir(d.get())) != NULL) { // Ignore "." and "..". if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { continue; } char* end; pid_t tid = strtoul(de->d_name, &end, 10); if (*end) { continue; } if (tid == main_tid) { continue; } if (ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) { ALOGE("debuggerd: ptrace attach to %d failed: %s", tid, strerror(errno)); continue; } tids.insert(tid); } } static bool perform_dump(const debugger_request_t& request, int fd, int tombstone_fd, BacktraceMap* backtrace_map, const std::set<pid_t>& siblings) { if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) { ALOGE("debuggerd: failed to respond to client: %s\n", strerror(errno)); return false; } int total_sleep_time_usec = 0; while (true) { int signal = wait_for_signal(request.tid, &total_sleep_time_usec); switch (signal) { case -1: ALOGE("debuggerd: timed out waiting for signal"); return false; case SIGSTOP: if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { ALOGV("debuggerd: stopped -- dumping to tombstone"); engrave_tombstone(tombstone_fd, backtrace_map, request.pid, request.tid, siblings, signal, request.original_si_code, request.abort_msg_address); } else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) { ALOGV("debuggerd: stopped -- dumping to fd"); dump_backtrace(fd, -1, backtrace_map, request.pid, request.tid, siblings); } else { ALOGV("debuggerd: stopped -- continuing"); if (ptrace(PTRACE_CONT, request.tid, 0, 0) != 0) { ALOGE("debuggerd: ptrace continue failed: %s", strerror(errno)); return false; } continue; // loop again } break; case SIGABRT: case SIGBUS: case SIGFPE: case SIGILL: case SIGSEGV: #ifdef SIGSTKFLT case SIGSTKFLT: #endif 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); engrave_tombstone(tombstone_fd, backtrace_map, request.pid, request.tid, siblings, signal, request.original_si_code, request.abort_msg_address); break; default: ALOGE("debuggerd: process stopped due to unexpected signal %d\n", signal); break; } break; } return true; } static bool drop_privileges() { if (setresgid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) { ALOGE("debuggerd: failed to setresgid"); return false; } if (setresuid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) { ALOGE("debuggerd: failed to setresuid"); return false; } return true; } static void handle_request(int fd) { ALOGV("handle_request(%d)\n", fd); Loading Loading @@ -405,117 +524,63 @@ static void handle_request(int fd) { // ensure that it can run as soon as we call PTRACE_CONT below. // See details in bionic/libc/linker/debugger.c, in function // debugger_signal_handler(). if (ptrace(PTRACE_ATTACH, request.tid, 0, 0)) { ALOGE("debuggerd: ptrace attach failed: %s\n", strerror(errno)); exit(1); } // Generate the backtrace map before dropping privileges. std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(request.pid)); // Now that we've done everything that requires privileges, we can drop them. if (setresgid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) { ALOGE("debuggerd: failed to setresgid"); // Attach to the target process. if (ptrace(PTRACE_ATTACH, request.tid, 0, 0) != 0) { ALOGE("debuggerd: ptrace attach failed: %s", strerror(errno)); exit(1); } if (setresuid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) { ALOGE("debuggerd: failed to setresuid"); exit(1); } bool detach_failed = false; bool tid_unresponsive = false; // 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); if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) { ALOGE("debuggerd: failed to respond to client: %s\n", strerror(errno)); exit(1); } int total_sleep_time_usec = 0; while (true) { int signal = wait_for_sigstop(request.tid, &total_sleep_time_usec, &detach_failed); if (signal == -1) { tid_unresponsive = true; break; } switch (signal) { case SIGSTOP: if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { ALOGV("stopped -- dumping to tombstone\n"); engrave_tombstone(tombstone_fd, backtrace_map.get(), request.pid, request.tid, signal, request.original_si_code, request.abort_msg_address, true, &detach_failed, &total_sleep_time_usec); } else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) { ALOGV("stopped -- dumping to fd\n"); dump_backtrace(fd, -1, request.pid, request.tid, &detach_failed, &total_sleep_time_usec); } else { ALOGV("stopped -- continuing\n"); status = ptrace(PTRACE_CONT, request.tid, 0, 0); if (status) { ALOGE("debuggerd: ptrace continue failed: %s\n", strerror(errno)); } continue; // loop again std::set<pid_t> siblings; if (!attach_gdb) { ptrace_siblings(request.pid, request.tid, siblings); } break; case SIGABRT: case SIGBUS: case SIGFPE: case SIGILL: case SIGSEGV: #ifdef SIGSTKFLT case SIGSTKFLT: #endif 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); // don't dump sibling threads when attaching to GDB because it // makes the process less reliable, apparently... engrave_tombstone(tombstone_fd, backtrace_map.get(), request.pid, request.tid, signal, request.original_si_code, request.abort_msg_address, !attach_gdb, &detach_failed, &total_sleep_time_usec); break; // Generate the backtrace map before dropping privileges. std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(request.pid)); default: ALOGE("debuggerd: process stopped due to unexpected signal %d\n", signal); break; } break; } 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 (!tid_unresponsive) { ALOGV("detaching"); if (attach_gdb) { // stop the process so we can debug kill(request.pid, SIGSTOP); // Stop the process so we can debug. tgkill(request.pid, request.tid, SIGSTOP); } } if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) { if (ptrace(PTRACE_DETACH, request.tid, 0, 0) != 0) { ALOGE("debuggerd: ptrace detach from %d failed: %s", request.tid, strerror(errno)); detach_failed = true; } else if (attach_gdb) { // if debug.db.uid is set, its value indicates if we should wait } for (pid_t sibling : siblings) { ptrace(PTRACE_DETACH, sibling, 0, 0); } if (succeeded && attach_gdb) { // if debug.debuggerd.wait_for_gdb is set, its value indicates if we should wait // for user action for the crashing process. // in this case, we log a message and turn the debug LED on // waiting for a gdb connection (for instance) wait_for_user_action(request); } } // Resume the stopped process so it can crash in peace, and exit. // Resume the stopped process. kill(request.pid, SIGCONT); exit(0); exit(!succeeded); } static int do_server() { Loading debuggerd/tombstone.cpp +64 −112 Original line number Diff line number Diff line Loading @@ -328,6 +328,33 @@ static std::string get_addr_string(uintptr_t addr) { return addr_str; } static void dump_abort_message(Backtrace* backtrace, log_t* log, uintptr_t address) { if (address == 0) { return; } address += sizeof(size_t); // Skip the buffer length. char msg[512]; memset(msg, 0, sizeof(msg)); char* p = &msg[0]; while (p < &msg[sizeof(msg)]) { word_t data; size_t len = sizeof(word_t); if (!backtrace->ReadWord(address, &data)) { break; } address += sizeof(word_t); while (len > 0 && (*p++ = (data >> (sizeof(word_t) - len) * 8) & 0xff) != 0) { len--; } } msg[sizeof(msg) - 1] = '\0'; _LOG(log, logtype::HEADER, "Abort message: '%s'\n", msg); } static void dump_all_maps(Backtrace* backtrace, BacktraceMap* map, log_t* log, pid_t tid) { bool print_fault_address_marker = false; uintptr_t addr = 0; Loading Loading @@ -416,67 +443,37 @@ static void dump_backtrace_and_stack(Backtrace* backtrace, log_t* log) { } } // Return true if some thread is not detached cleanly static bool dump_sibling_thread_report( log_t* log, pid_t pid, pid_t tid, int* total_sleep_time_usec, BacktraceMap* map) { char task_path[64]; snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid); DIR* d = opendir(task_path); // Bail early if the task directory cannot be opened if (d == NULL) { ALOGE("Cannot open /proc/%d/task\n", pid); return false; } bool detach_failed = false; struct dirent* de; while ((de = readdir(d)) != NULL) { // Ignore "." and ".." if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { continue; } // The main thread at fault has been handled individually char* end; pid_t new_tid = strtoul(de->d_name, &end, 10); if (*end || new_tid == tid) { continue; static void dump_thread(log_t* log, pid_t pid, pid_t tid, BacktraceMap* map, int signal, int si_code, uintptr_t abort_msg_address, bool primary_thread) { log->current_tid = tid; if (!primary_thread) { _LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); } dump_thread_info(log, pid, tid); // Skip this thread if cannot ptrace it if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) { ALOGE("ptrace attach to %d failed: %s\n", new_tid, strerror(errno)); continue; if (signal) { dump_signal_info(log, tid, signal, si_code); } if (wait_for_sigstop(new_tid, total_sleep_time_usec, &detach_failed) == -1) { continue; std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map)); if (primary_thread) { dump_abort_message(backtrace.get(), log, abort_msg_address); } log->current_tid = new_tid; _LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); dump_thread_info(log, pid, new_tid); dump_registers(log, new_tid); std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, new_tid, map)); dump_registers(log, tid); if (backtrace->Unwind(0)) { dump_backtrace_and_stack(backtrace.get(), log); } else { ALOGE("Unwind of sibling failed: pid = %d, tid = %d", pid, new_tid); ALOGE("Unwind failed: pid = %d, tid = %d", pid, tid); } log->current_tid = log->crashed_tid; if (ptrace(PTRACE_DETACH, new_tid, 0, 0) != 0) { ALOGE("ptrace detach from %d failed: %s\n", new_tid, strerror(errno)); detach_failed = true; if (primary_thread) { dump_memory_and_code(log, backtrace.get()); if (map) { dump_all_maps(backtrace.get(), map, log, tid); } } closedir(d); return detach_failed; log->current_tid = log->crashed_tid; } // Reads the contents of the specified log device, filters out the entries Loading Loading @@ -605,36 +602,10 @@ static void dump_logs(log_t* log, pid_t pid, unsigned int tail) { dump_log_file(log, pid, "main", tail); } static void dump_abort_message(Backtrace* backtrace, log_t* log, uintptr_t address) { if (address == 0) { return; } address += sizeof(size_t); // Skip the buffer length. char msg[512]; memset(msg, 0, sizeof(msg)); char* p = &msg[0]; while (p < &msg[sizeof(msg)]) { word_t data; size_t len = sizeof(word_t); if (!backtrace->ReadWord(address, &data)) { break; } address += sizeof(word_t); while (len > 0 && (*p++ = (data >> (sizeof(word_t) - len) * 8) & 0xff) != 0) len--; } msg[sizeof(msg) - 1] = '\0'; _LOG(log, logtype::HEADER, "Abort message: '%s'\n", msg); } // Dumps all information about the specified pid to the tombstone. static bool dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid, int signal, int si_code, uintptr_t abort_msg_address, bool dump_sibling_threads, int* total_sleep_time_usec) { static void dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid, const std::set<pid_t>& siblings, int signal, int si_code, uintptr_t abort_msg_address) { // don't copy log messages to tombstone unless this is a dev device char value[PROPERTY_VALUE_MAX]; property_get("ro.debuggable", value, "0"); Loading @@ -653,32 +624,15 @@ static bool dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid, int _LOG(log, logtype::HEADER, "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); dump_header_info(log); dump_thread_info(log, pid, tid); if (signal) { dump_signal_info(log, tid, signal, si_code); } std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map)); dump_abort_message(backtrace.get(), log, abort_msg_address); dump_registers(log, tid); if (backtrace->Unwind(0)) { dump_backtrace_and_stack(backtrace.get(), log); } else { ALOGE("Unwind failed: pid = %d, tid = %d", pid, tid); } dump_memory_and_code(log, backtrace.get()); if (map) { dump_all_maps(backtrace.get(), map, log, tid); } dump_thread(log, pid, tid, map, signal, si_code, abort_msg_address, true); if (want_logs) { dump_logs(log, pid, 5); } bool detach_failed = false; if (dump_sibling_threads) { detach_failed = dump_sibling_thread_report(log, pid, tid, total_sleep_time_usec, map); if (!siblings.empty()) { for (pid_t sibling : siblings) { dump_thread(log, pid, sibling, map, 0, 0, 0, false); } } if (want_logs) { Loading @@ -694,7 +648,7 @@ static bool dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid, int TEMP_FAILURE_RETRY( read(log->amfd, &eodMarker, 1) ); } return detach_failed; return; } // open_tombstone - find an available tombstone slot, if any, of the Loading Loading @@ -780,16 +734,15 @@ static int activity_manager_connect() { return amfd; } void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, int signal, int original_si_code, uintptr_t abort_msg_address, bool dump_sibling_threads, bool* detach_failed, int* total_sleep_time_usec) { void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, const std::set<pid_t>& siblings, int signal, int original_si_code, uintptr_t abort_msg_address) { log_t log; log.current_tid = tid; log.crashed_tid = tid; if (tombstone_fd < 0) { ALOGE("debuggerd: skipping tombstone write, nothing to do.\n"); *detach_failed = false; return; } Loading @@ -798,8 +751,7 @@ void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid // being closed. int amfd = activity_manager_connect(); log.amfd = amfd; *detach_failed = dump_crash(&log, map, pid, tid, signal, original_si_code, abort_msg_address, dump_sibling_threads, total_sleep_time_usec); dump_crash(&log, map, pid, tid, siblings, signal, original_si_code, abort_msg_address); // This file descriptor can be -1, any error is ignored. close(amfd); Loading debuggerd/tombstone.h +5 −5 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include <stdbool.h> #include <stddef.h> #include <sys/types.h> #include <set> #include <string> class BacktraceMap; Loading @@ -30,10 +31,9 @@ class BacktraceMap; */ int open_tombstone(std::string* path); /* Creates a tombstone file and writes the crash dump to it. * Returns the path of the tombstone, which must be freed using free(). */ void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, int signal, int original_si_code, uintptr_t abort_msg_address, bool dump_sibling_threads, bool* detach_failed, int* total_sleep_time_usec); /* Creates a tombstone file and writes the crash dump to it. */ void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, const std::set<pid_t>& siblings, int signal, int original_si_code, uintptr_t abort_msg_address); #endif // _DEBUGGERD_TOMBSTONE_H Loading
debuggerd/backtrace.cpp +7 −39 Original line number Diff line number Diff line Loading @@ -67,8 +67,7 @@ static void dump_process_footer(log_t* log, pid_t pid) { _LOG(log, logtype::BACKTRACE, "\n----- end %d -----\n", pid); } static void dump_thread( log_t* log, pid_t tid, bool attached, bool* detach_failed, int* total_sleep_time_usec) { static void dump_thread(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid) { char path[PATH_MAX]; char threadnamebuf[1024]; char* threadname = NULL; Loading @@ -88,56 +87,25 @@ static void dump_thread( _LOG(log, logtype::BACKTRACE, "\n\"%s\" sysTid=%d\n", threadname ? threadname : "<unknown>", tid); if (!attached && ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) { _LOG(log, logtype::BACKTRACE, "Could not attach to thread: %s\n", strerror(errno)); return; } if (!attached && wait_for_sigstop(tid, total_sleep_time_usec, detach_failed) == -1) { return; } std::unique_ptr<Backtrace> backtrace(Backtrace::Create(tid, BACKTRACE_CURRENT_THREAD)); std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map)); if (backtrace->Unwind(0)) { dump_backtrace_to_log(backtrace.get(), log, " "); } else { ALOGE("Unwind failed: tid = %d", tid); } if (!attached && ptrace(PTRACE_DETACH, tid, 0, 0) != 0) { ALOGE("ptrace detach from %d failed: %s\n", tid, strerror(errno)); *detach_failed = true; } } void dump_backtrace(int fd, int amfd, pid_t pid, pid_t tid, bool* detach_failed, int* total_sleep_time_usec) { void dump_backtrace(int fd, int amfd, BacktraceMap* map, pid_t pid, pid_t tid, const std::set<pid_t>& siblings) { log_t log; log.tfd = fd; log.amfd = amfd; dump_process_header(&log, pid); dump_thread(&log, tid, true, detach_failed, total_sleep_time_usec); char task_path[64]; snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid); DIR* d = opendir(task_path); if (d != NULL) { struct dirent* de = NULL; while ((de = readdir(d)) != NULL) { if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { continue; } dump_thread(&log, map, pid, tid); char* end; pid_t new_tid = strtoul(de->d_name, &end, 10); if (*end || new_tid == tid) { continue; } dump_thread(&log, new_tid, false, detach_failed, total_sleep_time_usec); } closedir(d); for (pid_t sibling : siblings) { dump_thread(&log, map, pid, sibling); } dump_process_footer(&log, pid); Loading
debuggerd/backtrace.h +5 −2 Original line number Diff line number Diff line Loading @@ -19,14 +19,17 @@ #include <sys/types.h> #include <set> #include "utility.h" class Backtrace; class BacktraceMap; // Dumps a backtrace using a format similar to what Dalvik uses so that the result // can be intermixed in a bug report. void dump_backtrace(int fd, int amfd, pid_t pid, pid_t tid, bool* detach_failed, int* total_sleep_time_usec); void dump_backtrace(int fd, int amfd, BacktraceMap* map, pid_t pid, pid_t tid, const std::set<pid_t>& siblings); /* Dumps the backtrace in the backtrace data structure to the log. */ void dump_backtrace_to_log(Backtrace* backtrace, log_t* log, const char* prefix); Loading
debuggerd/debuggerd.cpp +162 −97 Original line number Diff line number Diff line Loading @@ -14,14 +14,14 @@ * limitations under the License. */ #include <stdio.h> #include <dirent.h> #include <errno.h> #include <signal.h> #include <fcntl.h> #include <pthread.h> #include <signal.h> #include <stdarg.h> #include <fcntl.h> #include <stdio.h> #include <sys/types.h> #include <dirent.h> #include <time.h> #include <elf.h> Loading @@ -31,6 +31,8 @@ #include <sys/stat.h> #include <sys/wait.h> #include <set> #include <selinux/android.h> #include <log/logger.h> Loading @@ -57,6 +59,8 @@ #define SOCKET_NAME DEBUGGER_SOCKET_NAME #endif extern "C" int tgkill(int tgid, int tid, int sig); struct debugger_request_t { debugger_action_t action; pid_t pid, tid; Loading Loading @@ -335,6 +339,121 @@ static void redirect_to_32(int fd, debugger_request_t* request) { } #endif static void ptrace_siblings(pid_t pid, pid_t main_tid, std::set<pid_t>& tids) { char task_path[64]; snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid); std::unique_ptr<DIR, int (*)(DIR*)> d(opendir(task_path), closedir); // Bail early if the task directory cannot be opened. if (!d) { ALOGE("debuggerd: failed to open /proc/%d/task: %s", pid, strerror(errno)); return; } struct dirent* de; while ((de = readdir(d.get())) != NULL) { // Ignore "." and "..". if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { continue; } char* end; pid_t tid = strtoul(de->d_name, &end, 10); if (*end) { continue; } if (tid == main_tid) { continue; } if (ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) { ALOGE("debuggerd: ptrace attach to %d failed: %s", tid, strerror(errno)); continue; } tids.insert(tid); } } static bool perform_dump(const debugger_request_t& request, int fd, int tombstone_fd, BacktraceMap* backtrace_map, const std::set<pid_t>& siblings) { if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) { ALOGE("debuggerd: failed to respond to client: %s\n", strerror(errno)); return false; } int total_sleep_time_usec = 0; while (true) { int signal = wait_for_signal(request.tid, &total_sleep_time_usec); switch (signal) { case -1: ALOGE("debuggerd: timed out waiting for signal"); return false; case SIGSTOP: if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { ALOGV("debuggerd: stopped -- dumping to tombstone"); engrave_tombstone(tombstone_fd, backtrace_map, request.pid, request.tid, siblings, signal, request.original_si_code, request.abort_msg_address); } else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) { ALOGV("debuggerd: stopped -- dumping to fd"); dump_backtrace(fd, -1, backtrace_map, request.pid, request.tid, siblings); } else { ALOGV("debuggerd: stopped -- continuing"); if (ptrace(PTRACE_CONT, request.tid, 0, 0) != 0) { ALOGE("debuggerd: ptrace continue failed: %s", strerror(errno)); return false; } continue; // loop again } break; case SIGABRT: case SIGBUS: case SIGFPE: case SIGILL: case SIGSEGV: #ifdef SIGSTKFLT case SIGSTKFLT: #endif 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); engrave_tombstone(tombstone_fd, backtrace_map, request.pid, request.tid, siblings, signal, request.original_si_code, request.abort_msg_address); break; default: ALOGE("debuggerd: process stopped due to unexpected signal %d\n", signal); break; } break; } return true; } static bool drop_privileges() { if (setresgid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) { ALOGE("debuggerd: failed to setresgid"); return false; } if (setresuid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) { ALOGE("debuggerd: failed to setresuid"); return false; } return true; } static void handle_request(int fd) { ALOGV("handle_request(%d)\n", fd); Loading Loading @@ -405,117 +524,63 @@ static void handle_request(int fd) { // ensure that it can run as soon as we call PTRACE_CONT below. // See details in bionic/libc/linker/debugger.c, in function // debugger_signal_handler(). if (ptrace(PTRACE_ATTACH, request.tid, 0, 0)) { ALOGE("debuggerd: ptrace attach failed: %s\n", strerror(errno)); exit(1); } // Generate the backtrace map before dropping privileges. std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(request.pid)); // Now that we've done everything that requires privileges, we can drop them. if (setresgid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) { ALOGE("debuggerd: failed to setresgid"); // Attach to the target process. if (ptrace(PTRACE_ATTACH, request.tid, 0, 0) != 0) { ALOGE("debuggerd: ptrace attach failed: %s", strerror(errno)); exit(1); } if (setresuid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) { ALOGE("debuggerd: failed to setresuid"); exit(1); } bool detach_failed = false; bool tid_unresponsive = false; // 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); if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) { ALOGE("debuggerd: failed to respond to client: %s\n", strerror(errno)); exit(1); } int total_sleep_time_usec = 0; while (true) { int signal = wait_for_sigstop(request.tid, &total_sleep_time_usec, &detach_failed); if (signal == -1) { tid_unresponsive = true; break; } switch (signal) { case SIGSTOP: if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { ALOGV("stopped -- dumping to tombstone\n"); engrave_tombstone(tombstone_fd, backtrace_map.get(), request.pid, request.tid, signal, request.original_si_code, request.abort_msg_address, true, &detach_failed, &total_sleep_time_usec); } else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) { ALOGV("stopped -- dumping to fd\n"); dump_backtrace(fd, -1, request.pid, request.tid, &detach_failed, &total_sleep_time_usec); } else { ALOGV("stopped -- continuing\n"); status = ptrace(PTRACE_CONT, request.tid, 0, 0); if (status) { ALOGE("debuggerd: ptrace continue failed: %s\n", strerror(errno)); } continue; // loop again std::set<pid_t> siblings; if (!attach_gdb) { ptrace_siblings(request.pid, request.tid, siblings); } break; case SIGABRT: case SIGBUS: case SIGFPE: case SIGILL: case SIGSEGV: #ifdef SIGSTKFLT case SIGSTKFLT: #endif 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); // don't dump sibling threads when attaching to GDB because it // makes the process less reliable, apparently... engrave_tombstone(tombstone_fd, backtrace_map.get(), request.pid, request.tid, signal, request.original_si_code, request.abort_msg_address, !attach_gdb, &detach_failed, &total_sleep_time_usec); break; // Generate the backtrace map before dropping privileges. std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(request.pid)); default: ALOGE("debuggerd: process stopped due to unexpected signal %d\n", signal); break; } break; } 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 (!tid_unresponsive) { ALOGV("detaching"); if (attach_gdb) { // stop the process so we can debug kill(request.pid, SIGSTOP); // Stop the process so we can debug. tgkill(request.pid, request.tid, SIGSTOP); } } if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) { if (ptrace(PTRACE_DETACH, request.tid, 0, 0) != 0) { ALOGE("debuggerd: ptrace detach from %d failed: %s", request.tid, strerror(errno)); detach_failed = true; } else if (attach_gdb) { // if debug.db.uid is set, its value indicates if we should wait } for (pid_t sibling : siblings) { ptrace(PTRACE_DETACH, sibling, 0, 0); } if (succeeded && attach_gdb) { // if debug.debuggerd.wait_for_gdb is set, its value indicates if we should wait // for user action for the crashing process. // in this case, we log a message and turn the debug LED on // waiting for a gdb connection (for instance) wait_for_user_action(request); } } // Resume the stopped process so it can crash in peace, and exit. // Resume the stopped process. kill(request.pid, SIGCONT); exit(0); exit(!succeeded); } static int do_server() { Loading
debuggerd/tombstone.cpp +64 −112 Original line number Diff line number Diff line Loading @@ -328,6 +328,33 @@ static std::string get_addr_string(uintptr_t addr) { return addr_str; } static void dump_abort_message(Backtrace* backtrace, log_t* log, uintptr_t address) { if (address == 0) { return; } address += sizeof(size_t); // Skip the buffer length. char msg[512]; memset(msg, 0, sizeof(msg)); char* p = &msg[0]; while (p < &msg[sizeof(msg)]) { word_t data; size_t len = sizeof(word_t); if (!backtrace->ReadWord(address, &data)) { break; } address += sizeof(word_t); while (len > 0 && (*p++ = (data >> (sizeof(word_t) - len) * 8) & 0xff) != 0) { len--; } } msg[sizeof(msg) - 1] = '\0'; _LOG(log, logtype::HEADER, "Abort message: '%s'\n", msg); } static void dump_all_maps(Backtrace* backtrace, BacktraceMap* map, log_t* log, pid_t tid) { bool print_fault_address_marker = false; uintptr_t addr = 0; Loading Loading @@ -416,67 +443,37 @@ static void dump_backtrace_and_stack(Backtrace* backtrace, log_t* log) { } } // Return true if some thread is not detached cleanly static bool dump_sibling_thread_report( log_t* log, pid_t pid, pid_t tid, int* total_sleep_time_usec, BacktraceMap* map) { char task_path[64]; snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid); DIR* d = opendir(task_path); // Bail early if the task directory cannot be opened if (d == NULL) { ALOGE("Cannot open /proc/%d/task\n", pid); return false; } bool detach_failed = false; struct dirent* de; while ((de = readdir(d)) != NULL) { // Ignore "." and ".." if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { continue; } // The main thread at fault has been handled individually char* end; pid_t new_tid = strtoul(de->d_name, &end, 10); if (*end || new_tid == tid) { continue; static void dump_thread(log_t* log, pid_t pid, pid_t tid, BacktraceMap* map, int signal, int si_code, uintptr_t abort_msg_address, bool primary_thread) { log->current_tid = tid; if (!primary_thread) { _LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); } dump_thread_info(log, pid, tid); // Skip this thread if cannot ptrace it if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) { ALOGE("ptrace attach to %d failed: %s\n", new_tid, strerror(errno)); continue; if (signal) { dump_signal_info(log, tid, signal, si_code); } if (wait_for_sigstop(new_tid, total_sleep_time_usec, &detach_failed) == -1) { continue; std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map)); if (primary_thread) { dump_abort_message(backtrace.get(), log, abort_msg_address); } log->current_tid = new_tid; _LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); dump_thread_info(log, pid, new_tid); dump_registers(log, new_tid); std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, new_tid, map)); dump_registers(log, tid); if (backtrace->Unwind(0)) { dump_backtrace_and_stack(backtrace.get(), log); } else { ALOGE("Unwind of sibling failed: pid = %d, tid = %d", pid, new_tid); ALOGE("Unwind failed: pid = %d, tid = %d", pid, tid); } log->current_tid = log->crashed_tid; if (ptrace(PTRACE_DETACH, new_tid, 0, 0) != 0) { ALOGE("ptrace detach from %d failed: %s\n", new_tid, strerror(errno)); detach_failed = true; if (primary_thread) { dump_memory_and_code(log, backtrace.get()); if (map) { dump_all_maps(backtrace.get(), map, log, tid); } } closedir(d); return detach_failed; log->current_tid = log->crashed_tid; } // Reads the contents of the specified log device, filters out the entries Loading Loading @@ -605,36 +602,10 @@ static void dump_logs(log_t* log, pid_t pid, unsigned int tail) { dump_log_file(log, pid, "main", tail); } static void dump_abort_message(Backtrace* backtrace, log_t* log, uintptr_t address) { if (address == 0) { return; } address += sizeof(size_t); // Skip the buffer length. char msg[512]; memset(msg, 0, sizeof(msg)); char* p = &msg[0]; while (p < &msg[sizeof(msg)]) { word_t data; size_t len = sizeof(word_t); if (!backtrace->ReadWord(address, &data)) { break; } address += sizeof(word_t); while (len > 0 && (*p++ = (data >> (sizeof(word_t) - len) * 8) & 0xff) != 0) len--; } msg[sizeof(msg) - 1] = '\0'; _LOG(log, logtype::HEADER, "Abort message: '%s'\n", msg); } // Dumps all information about the specified pid to the tombstone. static bool dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid, int signal, int si_code, uintptr_t abort_msg_address, bool dump_sibling_threads, int* total_sleep_time_usec) { static void dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid, const std::set<pid_t>& siblings, int signal, int si_code, uintptr_t abort_msg_address) { // don't copy log messages to tombstone unless this is a dev device char value[PROPERTY_VALUE_MAX]; property_get("ro.debuggable", value, "0"); Loading @@ -653,32 +624,15 @@ static bool dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid, int _LOG(log, logtype::HEADER, "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); dump_header_info(log); dump_thread_info(log, pid, tid); if (signal) { dump_signal_info(log, tid, signal, si_code); } std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map)); dump_abort_message(backtrace.get(), log, abort_msg_address); dump_registers(log, tid); if (backtrace->Unwind(0)) { dump_backtrace_and_stack(backtrace.get(), log); } else { ALOGE("Unwind failed: pid = %d, tid = %d", pid, tid); } dump_memory_and_code(log, backtrace.get()); if (map) { dump_all_maps(backtrace.get(), map, log, tid); } dump_thread(log, pid, tid, map, signal, si_code, abort_msg_address, true); if (want_logs) { dump_logs(log, pid, 5); } bool detach_failed = false; if (dump_sibling_threads) { detach_failed = dump_sibling_thread_report(log, pid, tid, total_sleep_time_usec, map); if (!siblings.empty()) { for (pid_t sibling : siblings) { dump_thread(log, pid, sibling, map, 0, 0, 0, false); } } if (want_logs) { Loading @@ -694,7 +648,7 @@ static bool dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid, int TEMP_FAILURE_RETRY( read(log->amfd, &eodMarker, 1) ); } return detach_failed; return; } // open_tombstone - find an available tombstone slot, if any, of the Loading Loading @@ -780,16 +734,15 @@ static int activity_manager_connect() { return amfd; } void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, int signal, int original_si_code, uintptr_t abort_msg_address, bool dump_sibling_threads, bool* detach_failed, int* total_sleep_time_usec) { void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, const std::set<pid_t>& siblings, int signal, int original_si_code, uintptr_t abort_msg_address) { log_t log; log.current_tid = tid; log.crashed_tid = tid; if (tombstone_fd < 0) { ALOGE("debuggerd: skipping tombstone write, nothing to do.\n"); *detach_failed = false; return; } Loading @@ -798,8 +751,7 @@ void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid // being closed. int amfd = activity_manager_connect(); log.amfd = amfd; *detach_failed = dump_crash(&log, map, pid, tid, signal, original_si_code, abort_msg_address, dump_sibling_threads, total_sleep_time_usec); dump_crash(&log, map, pid, tid, siblings, signal, original_si_code, abort_msg_address); // This file descriptor can be -1, any error is ignored. close(amfd); Loading
debuggerd/tombstone.h +5 −5 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include <stdbool.h> #include <stddef.h> #include <sys/types.h> #include <set> #include <string> class BacktraceMap; Loading @@ -30,10 +31,9 @@ class BacktraceMap; */ int open_tombstone(std::string* path); /* Creates a tombstone file and writes the crash dump to it. * Returns the path of the tombstone, which must be freed using free(). */ void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, int signal, int original_si_code, uintptr_t abort_msg_address, bool dump_sibling_threads, bool* detach_failed, int* total_sleep_time_usec); /* Creates a tombstone file and writes the crash dump to it. */ void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, const std::set<pid_t>& siblings, int signal, int original_si_code, uintptr_t abort_msg_address); #endif // _DEBUGGERD_TOMBSTONE_H