diff options
Diffstat (limited to 'debuggerd/debuggerd.c')
-rw-r--r-- | debuggerd/debuggerd.c | 900 |
1 files changed, 447 insertions, 453 deletions
diff --git a/debuggerd/debuggerd.c b/debuggerd/debuggerd.c index 2acf26d..0f05bfd 100644 --- a/debuggerd/debuggerd.c +++ b/debuggerd/debuggerd.c @@ -28,83 +28,26 @@ #include <sys/wait.h> #include <sys/exec_elf.h> #include <sys/stat.h> +#include <sys/poll.h> #include <cutils/sockets.h> #include <cutils/logd.h> #include <cutils/logger.h> #include <cutils/properties.h> +#include <corkscrew/backtrace.h> + #include <linux/input.h> #include <private/android_filesystem_config.h> -#include "debuggerd.h" +#include "getevent.h" +#include "machine.h" #include "utility.h" #define ANDROID_LOG_INFO 4 -void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...) - __attribute__ ((format(printf, 3, 4))); - -/* Log information onto the tombstone */ -void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...) -{ - char buf[512]; - - va_list ap; - va_start(ap, fmt); - - if (tfd >= 0) { - int len; - vsnprintf(buf, sizeof(buf), fmt, ap); - len = strlen(buf); - if(tfd >= 0) write(tfd, buf, len); - } - - if (!in_tombstone_only) - __android_log_vprint(ANDROID_LOG_INFO, "DEBUG", fmt, ap); - va_end(ap); -} - -// 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so -// 012345678901234567890123456789012345678901234567890123456789 -// 0 1 2 3 4 5 - -mapinfo *parse_maps_line(char *line) -{ - mapinfo *mi; - int len = strlen(line); - - if (len < 1) return 0; /* not expected */ - line[--len] = 0; - - if (len < 50) { - mi = malloc(sizeof(mapinfo) + 1); - } else { - mi = malloc(sizeof(mapinfo) + (len - 47)); - } - if (mi == 0) return 0; - - mi->isExecutable = (line[20] == 'x'); - - mi->start = strtoul(line, 0, 16); - mi->end = strtoul(line + 9, 0, 16); - /* To be filled in parse_elf_info if the mapped section starts with - * elf_header - */ - mi->exidx_start = mi->exidx_end = 0; - mi->symbols = 0; - mi->next = 0; - if (len < 50) { - mi->name[0] = '\0'; - } else { - strcpy(mi->name, line + 49); - } - - return mi; -} - -void dump_build_info(int tfd) +static void dump_build_info(int tfd) { char fingerprint[PROPERTY_VALUE_MAX]; @@ -113,7 +56,7 @@ void dump_build_info(int tfd) _LOG(tfd, false, "Build fingerprint: '%s'\n", fingerprint); } -const char *get_signame(int sig) +static const char *get_signame(int sig) { switch(sig) { case SIGILL: return "SIGILL"; @@ -122,11 +65,12 @@ const char *get_signame(int sig) case SIGFPE: return "SIGFPE"; case SIGSEGV: return "SIGSEGV"; case SIGSTKFLT: return "SIGSTKFLT"; + case SIGSTOP: return "SIGSTOP"; default: return "?"; } } -const char *get_sigcode(int signo, int code) +static const char *get_sigcode(int signo, int code) { switch (signo) { case SIGILL: @@ -170,12 +114,12 @@ const char *get_sigcode(int signo, int code) return "?"; } -void dump_fault_addr(int tfd, int pid, int sig) +static void dump_fault_addr(int tfd, pid_t tid, int sig) { siginfo_t si; memset(&si, 0, sizeof(si)); - if(ptrace(PTRACE_GETSIGINFO, pid, 0, &si)){ + if(ptrace(PTRACE_GETSIGINFO, tid, 0, &si)){ _LOG(tfd, false, "cannot get siginfo: %s\n", strerror(errno)); } else if (signal_has_address(sig)) { _LOG(tfd, false, "signal %d (%s), code %d (%s), fault addr %08x\n", @@ -188,7 +132,7 @@ void dump_fault_addr(int tfd, int pid, int sig) } } -void dump_crash_banner(int tfd, unsigned pid, unsigned tid, int sig) +static void dump_crash_banner(int tfd, pid_t pid, pid_t tid, int sig) { char data[1024]; char *x = 0; @@ -202,225 +146,62 @@ void dump_crash_banner(int tfd, unsigned pid, unsigned tid, int sig) } _LOG(tfd, false, - "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); + "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); dump_build_info(tfd); _LOG(tfd, false, "pid: %d, tid: %d >>> %s <<<\n", pid, tid, x ? x : "UNKNOWN"); - if(sig) dump_fault_addr(tfd, tid, sig); -} - -static void parse_elf_info(mapinfo *milist, pid_t pid) -{ - mapinfo *mi; - for (mi = milist; mi != NULL; mi = mi->next) { - if (!mi->isExecutable) - continue; - - Elf32_Ehdr ehdr; - - memset(&ehdr, 0, sizeof(Elf32_Ehdr)); - /* Read in sizeof(Elf32_Ehdr) worth of data from the beginning of - * mapped section. - */ - get_remote_struct(pid, (void *) (mi->start), &ehdr, - sizeof(Elf32_Ehdr)); - /* Check if it has the matching magic words */ - if (IS_ELF(ehdr)) { - Elf32_Phdr phdr; - Elf32_Phdr *ptr; - int i; - - ptr = (Elf32_Phdr *) (mi->start + ehdr.e_phoff); - for (i = 0; i < ehdr.e_phnum; i++) { - /* Parse the program header */ - get_remote_struct(pid, (char *) (ptr+i), &phdr, - sizeof(Elf32_Phdr)); -#ifdef __arm__ - /* Found a EXIDX segment? */ - if (phdr.p_type == PT_ARM_EXIDX) { - mi->exidx_start = mi->start + phdr.p_offset; - mi->exidx_end = mi->exidx_start + phdr.p_filesz; - break; - } -#endif - } - - /* Try to load symbols from this file */ - mi->symbols = symbol_table_create(mi->name); - } - } -} - -void dump_crash_report(int tfd, unsigned pid, unsigned tid, bool at_fault) -{ - char data[1024]; - FILE *fp; - mapinfo *milist = 0; - unsigned int sp_list[STACK_CONTENT_DEPTH]; - int stack_depth; -#ifdef __arm__ - int frame0_pc_sane = 1; -#endif - - if (!at_fault) { - _LOG(tfd, true, - "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); - _LOG(tfd, true, "pid: %d, tid: %d\n", pid, tid); - } - - dump_registers(tfd, tid, at_fault); - - /* Clear stack pointer records */ - memset(sp_list, 0, sizeof(sp_list)); - - sprintf(data, "/proc/%d/maps", pid); - fp = fopen(data, "r"); - if(fp) { - while(fgets(data, 1024, fp)) { - mapinfo *mi = parse_maps_line(data); - if(mi) { - mi->next = milist; - milist = mi; - } - } - fclose(fp); - } - - parse_elf_info(milist, tid); - -#if __arm__ - /* If stack unwinder fails, use the default solution to dump the stack - * content. - */ - stack_depth = unwind_backtrace_with_ptrace(tfd, tid, milist, sp_list, - &frame0_pc_sane, at_fault); - - /* The stack unwinder should at least unwind two levels of stack. If less - * level is seen we make sure at lease pc and lr are dumped. - */ - if (stack_depth < 2) { - dump_pc_and_lr(tfd, tid, milist, stack_depth, at_fault); - } - - dump_stack_and_code(tfd, tid, milist, stack_depth, sp_list, at_fault); -#elif __i386__ - /* If stack unwinder fails, use the default solution to dump the stack - * content. - */ - stack_depth = unwind_backtrace_with_ptrace_x86(tfd, tid, milist,at_fault); -#else -#error "Unsupported architecture" -#endif - - while(milist) { - mapinfo *next = milist->next; - symbol_table_free(milist->symbols); - free(milist); - milist = next; - } -} - -#define MAX_TOMBSTONES 10 - -#define typecheck(x,y) { \ - typeof(x) __dummy1; \ - typeof(y) __dummy2; \ - (void)(&__dummy1 == &__dummy2); } - -#define TOMBSTONE_DIR "/data/tombstones" - -/* - * find_and_open_tombstone - find an available tombstone slot, if any, of the - * form tombstone_XX where XX is 00 to MAX_TOMBSTONES-1, inclusive. If no - * file is available, we reuse the least-recently-modified file. - */ -static int find_and_open_tombstone(void) -{ - unsigned long mtime = ULONG_MAX; - struct stat sb; - char path[128]; - int fd, i, oldest = 0; - - /* - * XXX: Our stat.st_mtime isn't time_t. If it changes, as it probably ought - * to, our logic breaks. This check will generate a warning if that happens. - */ - typecheck(mtime, sb.st_mtime); - - /* - * In a single wolf-like pass, find an available slot and, in case none - * exist, find and record the least-recently-modified file. - */ - for (i = 0; i < MAX_TOMBSTONES; i++) { - snprintf(path, sizeof(path), TOMBSTONE_DIR"/tombstone_%02d", i); - - if (!stat(path, &sb)) { - if (sb.st_mtime < mtime) { - oldest = i; - mtime = sb.st_mtime; - } - continue; - } - if (errno != ENOENT) - continue; - - fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0600); - if (fd < 0) - continue; /* raced ? */ - - fchown(fd, AID_SYSTEM, AID_SYSTEM); - return fd; + if(sig) { + dump_fault_addr(tfd, tid, sig); } - - /* we didn't find an available file, so we clobber the oldest one */ - snprintf(path, sizeof(path), TOMBSTONE_DIR"/tombstone_%02d", oldest); - fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); - fchown(fd, AID_SYSTEM, AID_SYSTEM); - - return fd; } /* Return true if some thread is not detached cleanly */ -static bool dump_sibling_thread_report(int tfd, unsigned pid, unsigned tid) -{ - char task_path[1024]; - - sprintf(task_path, "/proc/%d/task", pid); - DIR *d; - struct dirent *de; - int need_cleanup = 0; +static bool dump_sibling_thread_report(const ptrace_context_t* context, + int tfd, pid_t pid, pid_t tid) { + char task_path[64]; + snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid); - d = opendir(task_path); + DIR* d = opendir(task_path); /* Bail early if cannot open the task directory */ if (d == NULL) { XLOG("Cannot open /proc/%d/task\n", pid); return false; } + + bool detach_failed = false; + struct dirent *de; while ((de = readdir(d)) != NULL) { - unsigned new_tid; + pid_t new_tid; /* Ignore "." and ".." */ - if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { continue; + } + new_tid = atoi(de->d_name); /* The main thread at fault has been handled individually */ - if (new_tid == tid) + if (new_tid == tid) { continue; + } /* Skip this thread if cannot ptrace it */ - if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) + if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) { continue; + } + + _LOG(tfd, true, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); + _LOG(tfd, true, "pid: %d, tid: %d\n", pid, new_tid); - dump_crash_report(tfd, pid, new_tid, false); + dump_thread(context, tfd, new_tid, false); if (ptrace(PTRACE_DETACH, new_tid, 0, 0) != 0) { - XLOG("detach of tid %d failed: %s\n", new_tid, strerror(errno)); - need_cleanup = 1; + LOG("ptrace detach from %d failed: %s\n", new_tid, strerror(errno)); + detach_failed = true; } } - closedir(d); - return need_cleanup != 0; + closedir(d); + return detach_failed; } /* @@ -429,7 +210,7 @@ static bool dump_sibling_thread_report(int tfd, unsigned pid, unsigned tid) * * If "tailOnly" is set, we only print the last few lines. */ -static void dump_log_file(int tfd, unsigned pid, const char* filename, +static void dump_log_file(int tfd, pid_t pid, const char* filename, bool tailOnly) { bool first = true; @@ -561,52 +342,129 @@ static void dump_log_file(int tfd, unsigned pid, const char* filename, * Dumps the logs generated by the specified pid to the tombstone, from both * "system" and "main" log devices. Ideally we'd interleave the output. */ -static void dump_logs(int tfd, unsigned pid, bool tailOnly) +static void dump_logs(int tfd, pid_t pid, bool tailOnly) { dump_log_file(tfd, pid, "/dev/log/system", tailOnly); dump_log_file(tfd, pid, "/dev/log/main", tailOnly); } -/* Return true if some thread is not detached cleanly */ -static bool engrave_tombstone(unsigned pid, unsigned tid, int debug_uid, - int signal) +/* + * Dumps all information about the specified pid to the tombstone. + */ +static bool dump_crash(int tfd, pid_t pid, pid_t tid, int signal, + bool dump_sibling_threads) { - int fd; - bool need_cleanup = false; - /* don't copy log messages to tombstone unless this is a dev device */ char value[PROPERTY_VALUE_MAX]; property_get("ro.debuggable", value, "0"); bool wantLogs = (value[0] == '1'); - mkdir(TOMBSTONE_DIR, 0755); - chown(TOMBSTONE_DIR, AID_SYSTEM, AID_SYSTEM); + dump_crash_banner(tfd, pid, tid, signal); - fd = find_and_open_tombstone(); - if (fd < 0) - return need_cleanup; + ptrace_context_t* context = load_ptrace_context(tid); + + dump_thread(context, tfd, tid, true); - dump_crash_banner(fd, pid, tid, signal); - dump_crash_report(fd, pid, tid, true); + if (wantLogs) { + dump_logs(tfd, pid, true); + } + + bool detach_failed = false; + if (dump_sibling_threads) { + detach_failed = dump_sibling_thread_report(context, tfd, pid, tid); + } + + free_ptrace_context(context); if (wantLogs) { - dump_logs(fd, pid, true); + dump_logs(tfd, pid, false); } + return detach_failed; +} + +#define MAX_TOMBSTONES 10 + +#define typecheck(x,y) { \ + typeof(x) __dummy1; \ + typeof(y) __dummy2; \ + (void)(&__dummy1 == &__dummy2); } + +#define TOMBSTONE_DIR "/data/tombstones" + +/* + * find_and_open_tombstone - find an available tombstone slot, if any, of the + * form tombstone_XX where XX is 00 to MAX_TOMBSTONES-1, inclusive. If no + * file is available, we reuse the least-recently-modified file. + * + * Returns the path of the tombstone file, allocated using malloc(). Caller must free() it. + */ +static char* find_and_open_tombstone(int* fd) +{ + unsigned long mtime = ULONG_MAX; + struct stat sb; /* - * If the user has requested to attach gdb, don't collect the per-thread - * information as it increases the chance to lose track of the process. + * XXX: Our stat.st_mtime isn't time_t. If it changes, as it probably ought + * to, our logic breaks. This check will generate a warning if that happens. */ - if ((signed)pid > debug_uid) { - need_cleanup = dump_sibling_thread_report(fd, pid, tid); + typecheck(mtime, sb.st_mtime); + + /* + * In a single wolf-like pass, find an available slot and, in case none + * exist, find and record the least-recently-modified file. + */ + char path[128]; + int oldest = 0; + for (int i = 0; i < MAX_TOMBSTONES; i++) { + snprintf(path, sizeof(path), TOMBSTONE_DIR"/tombstone_%02d", i); + + if (!stat(path, &sb)) { + if (sb.st_mtime < mtime) { + oldest = i; + mtime = sb.st_mtime; + } + continue; + } + if (errno != ENOENT) + continue; + + *fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0600); + if (*fd < 0) + continue; /* raced ? */ + + fchown(*fd, AID_SYSTEM, AID_SYSTEM); + return strdup(path); } - if (wantLogs) { - dump_logs(fd, pid, false); + /* we didn't find an available file, so we clobber the oldest one */ + snprintf(path, sizeof(path), TOMBSTONE_DIR"/tombstone_%02d", oldest); + *fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); + if (*fd < 0) { + LOG("failed to open tombstone file '%s': %s\n", path, strerror(errno)); + return NULL; } + fchown(*fd, AID_SYSTEM, AID_SYSTEM); + return strdup(path); +} + +/* Return true if some thread is not detached cleanly */ +static char* engrave_tombstone(pid_t pid, pid_t tid, int signal, bool dump_sibling_threads, + bool* detach_failed) +{ + mkdir(TOMBSTONE_DIR, 0755); + chown(TOMBSTONE_DIR, AID_SYSTEM, AID_SYSTEM); + + int fd; + char* path = find_and_open_tombstone(&fd); + if (!path) { + *detach_failed = false; + return NULL; + } + + *detach_failed = dump_crash(fd, pid, tid, signal, dump_sibling_threads); close(fd); - return need_cleanup; + return path; } static int @@ -654,25 +512,21 @@ void disable_debug_led(void) write_string("/sys/class/leds/left/cadence", "0,0"); } -extern int init_getevent(); -extern void uninit_getevent(); -extern int get_event(struct input_event* event, int timeout); - -static void wait_for_user_action(unsigned tid, struct ucred* cr) -{ - (void)tid; +static void wait_for_user_action(pid_t pid) { /* First log a helpful message */ LOG( "********************************************************\n" "* Process %d has been suspended while crashing. To\n" - "* attach gdbserver for a gdb connection on port 5039:\n" + "* attach gdbserver for a gdb connection on port 5039\n" + "* and start gdbclient:\n" "*\n" - "* adb shell gdbserver :5039 --attach %d &\n" + "* gdbclient app_process :5039 %d\n" "*\n" - "* Press HOME key to let the process continue crashing.\n" + "* Wait for gdb to start, then press HOME or VOLUME DOWN key\n" + "* to let the process continue crashing.\n" "********************************************************\n", - cr->pid, cr->pid); + pid, pid); - /* wait for HOME key (TODO: something useful for devices w/o HOME key) */ + /* wait for HOME or VOLUME DOWN key */ if (init_getevent() == 0) { int ms = 1200 / 10; int dit = 1; @@ -685,15 +539,18 @@ static void wait_for_user_action(unsigned tid, struct ucred* cr) }; size_t s = 0; struct input_event e; - int home = 0; + bool done = false; init_debug_led(); enable_debug_led(); do { int timeout = abs((int)(codes[s])) * ms; int res = get_event(&e, timeout); if (res == 0) { - if (e.type==EV_KEY && e.code==KEY_HOME && e.value==0) - home = 1; + if (e.type == EV_KEY + && (e.code == KEY_HOME || e.code == KEY_VOLUMEDOWN) + && e.value == 0) { + done = true; + } } else if (res == 1) { if (++s >= sizeof(codes)/sizeof(*codes)) s = 0; @@ -703,202 +560,294 @@ static void wait_for_user_action(unsigned tid, struct ucred* cr) disable_debug_led(); } } - } while (!home); + } while (!done); uninit_getevent(); } /* don't forget to turn debug led off */ disable_debug_led(); + LOG("debuggerd resuming process %d", pid); +} - /* close filedescriptor */ - LOG("debuggerd resuming process %d", cr->pid); - } +static int get_process_info(pid_t tid, pid_t* out_pid, uid_t* out_uid, uid_t* out_gid) { + char path[64]; + snprintf(path, sizeof(path), "/proc/%d/status", tid); + + FILE* fp = fopen(path, "r"); + if (!fp) { + return -1; + } + + int fields = 0; + char line[1024]; + while (fgets(line, sizeof(line), fp)) { + size_t len = strlen(line); + if (len > 6 && !memcmp(line, "Tgid:\t", 6)) { + *out_pid = atoi(line + 6); + fields |= 1; + } else if (len > 5 && !memcmp(line, "Uid:\t", 5)) { + *out_uid = atoi(line + 5); + fields |= 2; + } else if (len > 5 && !memcmp(line, "Gid:\t", 5)) { + *out_gid = atoi(line + 5); + fields |= 4; + } + } + fclose(fp); + return fields == 7 ? 0 : -1; +} -static void handle_crashing_process(int fd) -{ - char buf[64]; - struct stat s; - unsigned tid; - struct ucred cr; - int n, len, status; - int tid_attach_status = -1; - unsigned retry = 30; - bool need_cleanup = false; +static int wait_for_signal(pid_t tid, int* total_sleep_time_usec) { + const int sleep_time_usec = 200000; /* 0.2 seconds */ + const int max_total_sleep_usec = 3000000; /* 3 seconds */ + for (;;) { + int status; + pid_t n = waitpid(tid, &status, __WALL | WNOHANG); + if (n < 0) { + if(errno == EAGAIN) continue; + LOG("waitpid failed: %s\n", strerror(errno)); + return -1; + } else if (n > 0) { + XLOG("waitpid: n=%d status=%08x\n", n, status); + if (WIFSTOPPED(status)) { + return WSTOPSIG(status); + } else { + LOG("unexpected waitpid response: n=%d, status=%08x\n", n, status); + return -1; + } + } - char value[PROPERTY_VALUE_MAX]; - property_get("debug.db.uid", value, "-1"); - int debug_uid = atoi(value); + if (*total_sleep_time_usec > max_total_sleep_usec) { + LOG("timed out waiting for tid=%d to die\n", tid); + return -1; + } + + /* not ready yet */ + XLOG("not ready yet\n"); + usleep(sleep_time_usec); + *total_sleep_time_usec += sleep_time_usec; + } +} + +enum { + REQUEST_TYPE_CRASH, + REQUEST_TYPE_DUMP, +}; - XLOG("handle_crashing_process(%d)\n", fd); +typedef struct { + int type; + pid_t pid, tid; + uid_t uid, gid; +} request_t; - len = sizeof(cr); - n = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len); - if(n != 0) { +static int read_request(int fd, request_t* out_request) { + struct ucred cr; + int len = sizeof(cr); + int status = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len); + if (status != 0) { LOG("cannot get credentials\n"); - goto done; + return -1; } XLOG("reading tid\n"); fcntl(fd, F_SETFL, O_NONBLOCK); - while((n = read(fd, &tid, sizeof(unsigned))) != sizeof(unsigned)) { - if(errno == EINTR) continue; - if(errno == EWOULDBLOCK) { - if(retry-- > 0) { - usleep(100 * 1000); - continue; - } - LOG("timed out reading tid\n"); - goto done; - } - LOG("read failure? %s\n", strerror(errno)); - goto done; - } - snprintf(buf, sizeof buf, "/proc/%d/task/%d", cr.pid, tid); - if(stat(buf, &s)) { - LOG("tid %d does not exist in pid %d. ignoring debug request\n", - tid, cr.pid); - close(fd); - return; + struct pollfd pollfds[1]; + pollfds[0].fd = fd; + pollfds[0].events = POLLIN; + pollfds[0].revents = 0; + status = TEMP_FAILURE_RETRY(poll(pollfds, 1, 3000)); + if (status != 1) { + LOG("timed out reading tid\n"); + return -1; } - XLOG("BOOM: pid=%d uid=%d gid=%d tid=%d\n", cr.pid, cr.uid, cr.gid, tid); + status = TEMP_FAILURE_RETRY(read(fd, &out_request->tid, sizeof(pid_t))); + if (status < 0) { + LOG("read failure? %s\n", strerror(errno)); + return -1; + } + if (status != sizeof(pid_t)) { + LOG("invalid crash request of size %d\n", status); + return -1; + } + + if (out_request->tid < 0 && cr.uid == 0) { + /* Root can ask us to attach to any process and dump it explicitly. */ + out_request->type = REQUEST_TYPE_DUMP; + out_request->tid = -out_request->tid; + status = get_process_info(out_request->tid, &out_request->pid, + &out_request->uid, &out_request->gid); + if (status < 0) { + LOG("tid %d does not exist. ignoring explicit dump request\n", + out_request->tid); + return -1; + } + return 0; + } - /* Note that at this point, the target thread's signal handler - * is blocked in a read() call. This gives us the time to PTRACE_ATTACH - * to it before it has a chance to really fault. - * - * The PTRACE_ATTACH sends a SIGSTOP to the target process, but it - * won't necessarily have stopped by the time ptrace() returns. (We - * currently assume it does.) We write to the file descriptor to - * 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(). - */ - tid_attach_status = ptrace(PTRACE_ATTACH, tid, 0, 0); - int ptrace_error = errno; + /* Ensure that the tid reported by the crashing process is valid. */ + out_request->type = REQUEST_TYPE_CRASH; + out_request->pid = cr.pid; + out_request->uid = cr.uid; + out_request->gid = cr.gid; - if (TEMP_FAILURE_RETRY(write(fd, &tid, 1)) != 1) { - XLOG("failed responding to client: %s\n", - strerror(errno)); - goto done; + char buf[64]; + struct stat s; + snprintf(buf, sizeof buf, "/proc/%d/task/%d", out_request->pid, out_request->tid); + if(stat(buf, &s)) { + LOG("tid %d does not exist in pid %d. ignoring debug request\n", + out_request->tid, out_request->pid); + return -1; } + return 0; +} - if(tid_attach_status < 0) { - LOG("ptrace attach failed: %s\n", strerror(ptrace_error)); - goto done; +static bool should_attach_gdb(request_t* request) { + if (request->type == REQUEST_TYPE_CRASH) { + char value[PROPERTY_VALUE_MAX]; + property_get("debug.db.uid", value, "-1"); + int debug_uid = atoi(value); + return debug_uid >= 0 && request->uid <= (uid_t)debug_uid; } + return false; +} - close(fd); - fd = -1; +static void handle_request(int fd) { + XLOG("handle_request(%d)\n", fd); - const int sleep_time_usec = 200000; /* 0.2 seconds */ - const int max_total_sleep_usec = 3000000; /* 3 seconds */ - int loop_limit = max_total_sleep_usec / sleep_time_usec; - for(;;) { - if (loop_limit-- == 0) { - LOG("timed out waiting for pid=%d tid=%d uid=%d to die\n", - cr.pid, tid, cr.uid); - goto done; - } - n = waitpid(tid, &status, __WALL | WNOHANG); + request_t request; + int status = read_request(fd, &request); + if (!status) { + XLOG("BOOM: pid=%d uid=%d gid=%d tid=%d\n", pid, uid, gid, tid); - if (n == 0) { - /* not ready yet */ - XLOG("not ready yet\n"); - usleep(sleep_time_usec); - continue; - } + /* At this point, the thread that made the request is blocked in + * a read() call. If the thread has crashed, then this gives us + * time to PTRACE_ATTACH to it before it has a chance to really fault. + * + * The PTRACE_ATTACH sends a SIGSTOP to the target process, but it + * won't necessarily have stopped by the time ptrace() returns. (We + * currently assume it does.) We write to the file descriptor to + * 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)) { + LOG("ptrace attach failed: %s\n", strerror(errno)); + } else { + bool detach_failed = false; + bool attach_gdb = should_attach_gdb(&request); + char response = 0; + if (TEMP_FAILURE_RETRY(write(fd, &response, 1)) != 1) { + LOG("failed responding to client: %s\n", strerror(errno)); + } else { + char* tombstone_path = NULL; - if(n < 0) { - if(errno == EAGAIN) continue; - LOG("waitpid failed: %s\n", strerror(errno)); - goto done; - } + if (request.type != REQUEST_TYPE_DUMP) { + close(fd); + fd = -1; + } - XLOG("waitpid: n=%d status=%08x\n", n, status); - - if(WIFSTOPPED(status)){ - n = WSTOPSIG(status); - switch(n) { - case SIGSTOP: - XLOG("stopped -- continuing\n"); - n = ptrace(PTRACE_CONT, tid, 0, 0); - if(n) { - LOG("ptrace failed: %s\n", strerror(errno)); - goto done; + int total_sleep_time_usec = 0; + for (;;) { + int signal = wait_for_signal(request.tid, &total_sleep_time_usec); + if (signal < 0) { + break; + } + + switch (signal) { + case SIGSTOP: + if (request.type == REQUEST_TYPE_DUMP) { + XLOG("stopped -- dumping\n"); + tombstone_path = engrave_tombstone(request.pid, request.tid, + signal, true, &detach_failed); + } else { + XLOG("stopped -- continuing\n"); + status = ptrace(PTRACE_CONT, request.tid, 0, 0); + if (status) { + LOG("ptrace continue failed: %s\n", strerror(errno)); + } + continue; /* loop again */ + } + break; + + case SIGILL: + case SIGABRT: + case SIGBUS: + case SIGFPE: + case SIGSEGV: + case SIGSTKFLT: { + XLOG("stopped -- fatal signal\n"); + /* don't dump sibling threads when attaching to GDB because it + * makes the process less reliable, apparently... */ + tombstone_path = engrave_tombstone(request.pid, request.tid, + signal, !attach_gdb, &detach_failed); + break; + } + + default: + XLOG("stopped -- unexpected signal\n"); + LOG("process stopped due to unexpected signal %d\n", signal); + break; + } + break; } - continue; - case SIGILL: - case SIGABRT: - case SIGBUS: - case SIGFPE: - case SIGSEGV: - case SIGSTKFLT: { - XLOG("stopped -- fatal signal\n"); - need_cleanup = engrave_tombstone(cr.pid, tid, debug_uid, n); - kill(tid, SIGSTOP); - goto done; + if (request.type == REQUEST_TYPE_DUMP) { + if (tombstone_path) { + write(fd, tombstone_path, strlen(tombstone_path)); + } + close(fd); + fd = -1; + } + free(tombstone_path); } - default: - XLOG("stopped -- unexpected signal\n"); - goto done; - } - } else { - XLOG("unexpected waitpid response\n"); - goto done; - } - } + XLOG("detaching\n"); + if (attach_gdb) { + /* stop the process so we can debug */ + kill(request.pid, SIGSTOP); -done: - XLOG("detaching\n"); + /* detach so we can attach gdbserver */ + if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) { + LOG("ptrace detach from %d failed: %s\n", request.tid, strerror(errno)); + detach_failed = true; + } - /* stop the process so we can debug */ - kill(cr.pid, SIGSTOP); + /* + * if debug.db.uid 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.pid); + } else { + /* just detach */ + if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) { + LOG("ptrace detach from %d failed: %s\n", request.tid, strerror(errno)); + detach_failed = true; + } + } - /* - * If a thread has been attached by ptrace, make sure it is detached - * successfully otherwise we will get a zombie. - */ - if (tid_attach_status == 0) { - int detach_status; - /* detach so we can attach gdbserver */ - detach_status = ptrace(PTRACE_DETACH, tid, 0, 0); - need_cleanup |= (detach_status != 0); - } + /* resume stopped process (so it can crash in peace). */ + kill(request.pid, SIGCONT); - /* - * if debug.db.uid 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) - */ + /* If we didn't successfully detach, we're still the parent, and the + * actual parent won't receive a death notification via wait(2). At this point + * there's not much we can do about that. */ + if (detach_failed) { + LOG("debuggerd committing suicide to free the zombie!\n"); + kill(getpid(), SIGKILL); + } + } - if ((signed)cr.uid <= debug_uid) { - wait_for_user_action(tid, &cr); } - - /* - * Resume stopped process (so it can crash in peace). If we didn't - * successfully detach, we're still the parent, and the actual parent - * won't receive a death notification via wait(2). At this point - * there's not much we can do about that. - */ - kill(cr.pid, SIGCONT); - - if (need_cleanup) { - LOG("debuggerd committing suicide to free the zombie!\n"); - kill(getpid(), SIGKILL); + if (fd >= 0) { + close(fd); } - - if(fd != -1) close(fd); } - -int main() -{ +static int do_server() { int s; struct sigaction act; int logsocket = -1; @@ -931,7 +880,7 @@ int main() s = socket_local_server("android:debuggerd", ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); - if(s < 0) return -1; + if(s < 0) return 1; fcntl(s, F_SETFD, FD_CLOEXEC); LOG("debuggerd: " __DATE__ " " __TIME__ "\n"); @@ -951,7 +900,52 @@ int main() fcntl(fd, F_SETFD, FD_CLOEXEC); - handle_crashing_process(fd); + handle_request(fd); + } + return 0; +} + +static int do_explicit_dump(pid_t tid) { + fprintf(stdout, "Sending request to dump task %d.\n", tid); + + int fd = socket_local_client("android:debuggerd", + ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); + if (fd < 0) { + fputs("Error opening local socket to debuggerd.\n", stderr); + return 1; + } + + pid_t request = -tid; + write(fd, &request, sizeof(pid_t)); + if (read(fd, &request, 1) != 1) { + /* did not get expected reply, debuggerd must have closed the socket */ + fputs("Error sending request. Did not receive reply from debuggerd.\n", stderr); + } else { + char tombstone_path[PATH_MAX]; + ssize_t n = read(fd, &tombstone_path, sizeof(tombstone_path) - 1); + if (n <= 0) { + fputs("Error dumping process. Check log for details.\n", stderr); + } else { + tombstone_path[n] = '\0'; + fprintf(stderr, "Tombstone written to: %s\n", tombstone_path); + } } + + close(fd); return 0; } + +int main(int argc, char** argv) { + if (argc == 2) { + pid_t tid = atoi(argv[1]); + if (!tid) { + fputs("Usage: [<tid>]\n" + "\n" + "If tid specified, sends a request to debuggerd to dump that task.\n" + "Otherwise, starts the debuggerd server.\n", stderr); + return 1; + } + return do_explicit_dump(tid); + } + return do_server(); +} |