/* system/debuggerd/debuggerd.c ** ** Copyright 2006, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utility.h" /* Main entry point to get the backtrace from the crashing process */ extern int unwind_backtrace_with_ptrace(int tfd, pid_t pid, mapinfo *map, unsigned int sp_list[], int *frame0_pc_sane, bool at_fault); static char **process_name_ptr; static int logsocket = -1; #define ANDROID_LOG_INFO 4 /* Log information onto the tombstone */ void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...) { char buf[128]; 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); } #define LOG(fmt...) _LOG(-1, 0, fmt) #if 0 #define XLOG(fmt...) _LOG(-1, 0, fmt) #else #define XLOG(fmt...) do {} while(0) #endif // 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; line[--len] = 0; if(len < 50) return 0; if(line[20] != 'x') return 0; mi = malloc(sizeof(mapinfo) + (len - 47)); if(mi == 0) return 0; mi->start = strtoul(line, 0, 16); mi->end = strtoul(line + 9, 0, 16); /* To be filled in parse_exidx_info if the mapped section starts with * elf_header */ mi->exidx_start = mi->exidx_end = 0; mi->next = 0; strcpy(mi->name, line + 49); return mi; } void dump_build_info(int tfd) { char fingerprint[PROPERTY_VALUE_MAX]; property_get("ro.build.fingerprint", fingerprint, "unknown"); _LOG(tfd, false, "Build fingerprint: '%s'\n", fingerprint); } void dump_stack_and_code(int tfd, int pid, mapinfo *map, int unwind_depth, unsigned int sp_list[], int frame0_pc_sane, bool at_fault) { unsigned int sp, pc, p, end, data; struct pt_regs r; int sp_depth; bool only_in_tombstone = !at_fault; if(ptrace(PTRACE_GETREGS, pid, 0, &r)) return; sp = r.ARM_sp; pc = r.ARM_pc; /* Died because calling the weeds - dump * the code around the PC in the next frame instead. */ if (frame0_pc_sane == 0) { pc = r.ARM_lr; } _LOG(tfd, true, "code%s:\n", frame0_pc_sane ? "" : " (around frame #01)"); end = p = pc & ~3; p -= 16; /* Dump the code as: * PC contents * 00008d34 fffffcd0 4c0eb530 b0934a0e 1c05447c * 00008d44 f7ff18a0 490ced94 68035860 d0012b00 */ while (p <= end) { int i; _LOG(tfd, true, " %08x ", p); for (i = 0; i < 4; i++) { data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL); _LOG(tfd, true, " %08x", data); p += 4; } _LOG(tfd, true, "\n", p); } p = sp - 64; p &= ~3; if (unwind_depth != 0) { if (unwind_depth < STACK_CONTENT_DEPTH) { end = sp_list[unwind_depth-1]; } else { end = sp_list[STACK_CONTENT_DEPTH-1]; } } else { end = sp | 0x000000ff; end += 0xff; } _LOG(tfd, only_in_tombstone, "stack:\n"); /* If the crash is due to PC == 0, there will be two frames that * have identical SP value. */ if (sp_list[0] == sp_list[1]) { sp_depth = 1; } else { sp_depth = 0; } while (p <= end) { char *prompt; char level[16]; data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL); if (p == sp_list[sp_depth]) { sprintf(level, "#%02d", sp_depth++); prompt = level; } else { prompt = " "; } /* Print the stack content in the log for the first 3 frames. For the * rest only print them in the tombstone file. */ _LOG(tfd, (sp_depth > 2) || only_in_tombstone, "%s %08x %08x %s\n", prompt, p, data, map_to_name(map, data, "")); p += 4; } /* print another 64-byte of stack data after the last frame */ end = p+64; while (p <= end) { data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL); _LOG(tfd, (sp_depth > 2) || only_in_tombstone, " %08x %08x %s\n", p, data, map_to_name(map, data, "")); p += 4; } } void dump_pc_and_lr(int tfd, int pid, mapinfo *map, int unwound_level, bool at_fault) { struct pt_regs r; if(ptrace(PTRACE_GETREGS, pid, 0, &r)) { _LOG(tfd, !at_fault, "tid %d not responding!\n", pid); return; } if (unwound_level == 0) { _LOG(tfd, !at_fault, " #%02d pc %08x %s\n", 0, r.ARM_pc, map_to_name(map, r.ARM_pc, "")); } _LOG(tfd, !at_fault, " #%02d lr %08x %s\n", 1, r.ARM_lr, map_to_name(map, r.ARM_lr, "")); } void dump_registers(int tfd, int pid, bool at_fault) { struct pt_regs r; bool only_in_tombstone = !at_fault; if(ptrace(PTRACE_GETREGS, pid, 0, &r)) { _LOG(tfd, only_in_tombstone, "cannot get registers: %s\n", strerror(errno)); return; } _LOG(tfd, only_in_tombstone, " r0 %08x r1 %08x r2 %08x r3 %08x\n", r.ARM_r0, r.ARM_r1, r.ARM_r2, r.ARM_r3); _LOG(tfd, only_in_tombstone, " r4 %08x r5 %08x r6 %08x r7 %08x\n", r.ARM_r4, r.ARM_r5, r.ARM_r6, r.ARM_r7); _LOG(tfd, only_in_tombstone, " r8 %08x r9 %08x 10 %08x fp %08x\n", r.ARM_r8, r.ARM_r9, r.ARM_r10, r.ARM_fp); _LOG(tfd, only_in_tombstone, " ip %08x sp %08x lr %08x pc %08x cpsr %08x\n", r.ARM_ip, r.ARM_sp, r.ARM_lr, r.ARM_pc, r.ARM_cpsr); } const char *get_signame(int sig) { switch(sig) { case SIGILL: return "SIGILL"; case SIGABRT: return "SIGABRT"; case SIGBUS: return "SIGBUS"; case SIGFPE: return "SIGFPE"; case SIGSEGV: return "SIGSEGV"; case SIGSTKFLT: return "SIGSTKFLT"; default: return "?"; } } void dump_fault_addr(int tfd, int pid, int sig) { siginfo_t si; memset(&si, 0, sizeof(si)); if(ptrace(PTRACE_GETSIGINFO, pid, 0, &si)){ _LOG(tfd, false, "cannot get siginfo: %s\n", strerror(errno)); } else { _LOG(tfd, false, "signal %d (%s), fault addr %08x\n", sig, get_signame(sig), si.si_addr); } } void dump_crash_banner(int tfd, unsigned pid, unsigned tid, int sig) { char data[1024]; char *x = 0; FILE *fp; sprintf(data, "/proc/%d/cmdline", pid); fp = fopen(data, "r"); if(fp) { x = fgets(data, 1024, fp); fclose(fp); } _LOG(tfd, false, "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\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_exidx_info(mapinfo *milist, pid_t pid) { mapinfo *mi; for (mi = milist; mi != NULL; mi = mi->next) { 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, (void *) ptr+i, &phdr, sizeof(Elf32_Phdr)); /* 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; } } } } } 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; int frame0_pc_sane = 1; 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_exidx_info(milist, tid); /* 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, frame0_pc_sane, at_fault); while(milist) { mapinfo *next = milist->next; free(milist); milist = next; } } /* FIXME: unused: use it or lose it*/ #if 0 static void start_gdbserver_vs(int pid, int port) { pid_t p; char *args[5]; char commspec[16]; char pidspec[16]; p = fork(); if(p < 0) { LOG("could not fork()\n"); return; } if(p == 0) { sprintf(commspec, ":%d", port); sprintf(pidspec, "%d", pid); args[0] = "/system/bin/gdbserver"; args[1] = commspec; args[2] = "--attach"; args[3] = pidspec; args[4] = 0; exit(execv(args[0], args)); } else { LOG("gdbserver pid=%d port=%d targetpid=%d\n", p, port, pid); sleep(5); } } #endif #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; } /* 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; 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; } while ((de = readdir(d)) != NULL) { unsigned new_tid; /* Ignore "." and ".." */ 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) continue; /* Skip this thread if cannot ptrace it */ if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) continue; dump_crash_report(tfd, pid, new_tid, false); need_cleanup |= ptrace(PTRACE_DETACH, new_tid, 0, 0); } closedir(d); return need_cleanup != 0; } /* Return true if some thread is not detached cleanly */ static bool engrave_tombstone(unsigned pid, unsigned tid, int debug_uid, int signal) { int fd; bool need_cleanup = false; mkdir(TOMBSTONE_DIR, 0755); chown(TOMBSTONE_DIR, AID_SYSTEM, AID_SYSTEM); fd = find_and_open_tombstone(); if (fd < 0) return need_cleanup; dump_crash_banner(fd, pid, tid, signal); dump_crash_report(fd, pid, tid, true); /* * 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. */ if ((signed)pid > debug_uid) { need_cleanup = dump_sibling_thread_report(fd, pid, tid); } close(fd); return need_cleanup; } static int write_string(const char* file, const char* string) { int len; int fd; ssize_t amt; fd = open(file, O_RDWR); len = strlen(string); if (fd < 0) return -errno; amt = write(fd, string, len); close(fd); return amt >= 0 ? 0 : -errno; } static void init_debug_led(void) { // trout leds write_string("/sys/class/leds/red/brightness", "0"); write_string("/sys/class/leds/green/brightness", "0"); write_string("/sys/class/leds/blue/brightness", "0"); write_string("/sys/class/leds/red/device/blink", "0"); // sardine leds write_string("/sys/class/leds/left/cadence", "0,0"); } static void enable_debug_led(void) { // trout leds write_string("/sys/class/leds/red/brightness", "255"); // sardine leds write_string("/sys/class/leds/left/cadence", "1,0"); } static void disable_debug_led(void) { // trout leds write_string("/sys/class/leds/red/brightness", "0"); // sardine leds 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; /* First log a helpful message */ LOG( "********************************************************\n" "* process %d crashed. debuggerd waiting for gdbserver \n" "* \n" "* adb shell gdbserver :port --attach %d & \n" "* \n" "* and press the HOME key. \n" "********************************************************\n", cr->pid, cr->pid); /* wait for HOME key */ if (init_getevent() == 0) { int ms = 1200 / 10; int dit = 1; int dah = 3*dit; int _ = -dit; int ___ = 3*_; int _______ = 7*_; const signed char codes[] = { dit,_,dit,_,dit,___,dah,_,dah,_,dah,___,dit,_,dit,_,dit,_______ }; size_t s = 0; struct input_event e; int home = 0; 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; } else if (res == 1) { if (++s >= sizeof(codes)/sizeof(*codes)) s = 0; if (codes[s] > 0) { enable_debug_led(); } else { disable_debug_led(); } } } while (!home); uninit_getevent(); } /* don't forget to turn debug led off */ disable_debug_led(); /* close filedescriptor */ LOG("debuggerd resuming process %d", cr->pid); } 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; char value[PROPERTY_VALUE_MAX]; property_get("debug.db.uid", value, "-1"); int debug_uid = atoi(value); XLOG("handle_crashing_process(%d)\n", fd); len = sizeof(cr); n = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len); if(n != 0) { LOG("cannot get credentials\n"); goto done; } 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; } sprintf(buf,"/proc/%d/task/%d", cr.pid, tid); if(stat(buf, &s)) { LOG("tid %d does not exist in pid %d. ignorning debug request\n", tid, cr.pid); close(fd); return; } XLOG("BOOM: pid=%d uid=%d gid=%d tid=%d\n", cr.pid, cr.uid, cr.gid, tid); tid_attach_status = ptrace(PTRACE_ATTACH, tid, 0, 0); if(tid_attach_status < 0) { LOG("ptrace attach failed: %s\n", strerror(errno)); goto done; } close(fd); fd = -1; for(;;) { n = waitpid(tid, &status, __WALL); if(n < 0) { if(errno == EAGAIN) continue; LOG("waitpid failed: %s\n", strerror(errno)); goto done; } 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; } 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; } default: XLOG("stopped -- unexpected signal\n"); goto done; } } else { XLOG("unexpected waitpid response\n"); goto done; } } done: XLOG("detaching\n"); /* stop the process so we can debug */ kill(cr.pid, SIGSTOP); /* * 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); } /* * 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 ((signed)cr.uid <= debug_uid) { wait_for_user_action(tid, &cr); } /* resume stopped process (so it can crash in peace) */ kill(cr.pid, SIGCONT); if (need_cleanup) { LOG("debuggerd committing suicide to free the zombie!\n"); kill(getpid(), SIGKILL); } if(fd != -1) close(fd); } int main(int argc, char **argv) { int s; struct sigaction act; process_name_ptr = argv; logsocket = socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_DGRAM); if(logsocket < 0) { logsocket = -1; } else { fcntl(logsocket, F_SETFD, FD_CLOEXEC); } act.sa_handler = SIG_DFL; sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask,SIGCHLD); act.sa_flags = SA_NOCLDWAIT; sigaction(SIGCHLD, &act, 0); s = socket_local_server("android:debuggerd", ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); if(s < 0) return -1; fcntl(s, F_SETFD, FD_CLOEXEC); LOG("debuggerd: " __DATE__ " " __TIME__ "\n"); for(;;) { struct sockaddr addr; socklen_t alen; int fd; alen = sizeof(addr); fd = accept(s, &addr, &alen); if(fd < 0) continue; fcntl(fd, F_SETFD, FD_CLOEXEC); handle_crashing_process(fd); } return 0; }