diff --git a/debuggerd/Android.mk b/debuggerd/Android.mk index f1273637b..2a516fbed 100644 --- a/debuggerd/Android.mk +++ b/debuggerd/Android.mk @@ -5,9 +5,15 @@ ifneq ($(filter arm x86,$(TARGET_ARCH)),) LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= debuggerd.c utility.c getevent.c $(TARGET_ARCH)/machine.c +LOCAL_SRC_FILES:= \ + backtrace.c \ + debuggerd.c \ + getevent.c \ + tombstone.c \ + utility.c \ + $(TARGET_ARCH)/machine.c -LOCAL_CFLAGS := -Wall -Werror -std=gnu99 +LOCAL_CFLAGS := -Wall -Wno-unused-parameter -std=gnu99 LOCAL_MODULE := debuggerd ifeq ($(ARCH_ARM_HAVE_VFP),true) diff --git a/debuggerd/arm/machine.c b/debuggerd/arm/machine.c index ca45c9b51..1c2e13ff0 100644 --- a/debuggerd/arm/machine.c +++ b/debuggerd/arm/machine.c @@ -15,23 +15,17 @@ ** 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" @@ -48,11 +42,72 @@ #endif #endif +static void dump_memory(log_t* log, pid_t tid, uintptr_t addr, bool at_fault) { + char code_buffer[64]; /* actual 8+1+((8+1)*4) + 1 == 45 */ + char ascii_buffer[32]; /* actual 16 + 1 == 17 */ + uintptr_t p, end; + + p = addr & ~3; + p -= 32; + if (p > addr) { + /* catch underflow */ + p = 0; + } + end = p + 80; + /* catch overflow; 'end - p' has to be multiples of 16 */ + while (end < p) + end -= 16; + + /* Dump the code around PC as: + * addr contents ascii + * 00008d34 ef000000 e8bd0090 e1b00000 512fff1e ............../Q + * 00008d44 ea00b1f9 e92d0090 e3a070fc ef000000 ......-..p...... + */ + while (p < end) { + char* asc_out = ascii_buffer; + + sprintf(code_buffer, "%08x ", p); + + int i; + for (i = 0; i < 4; i++) { + /* + * If we see (data == -1 && errno != 0), we know that the ptrace + * call failed, probably because we're dumping memory in an + * unmapped or inaccessible page. I don't know if there's + * value in making that explicit in the output -- it likely + * just complicates parsing and clarifies nothing for the + * enlightened reader. + */ + long data = ptrace(PTRACE_PEEKTEXT, tid, (void*)p, NULL); + sprintf(code_buffer + strlen(code_buffer), "%08lx ", data); + + int j; + for (j = 0; j < 4; j++) { + /* + * Our isprint() allows high-ASCII characters that display + * differently (often badly) in different viewers, so we + * just use a simpler test. + */ + char val = (data >> (j*8)) & 0xff; + if (val >= 0x20 && val < 0x7f) { + *asc_out++ = val; + } else { + *asc_out++ = '.'; + } + } + p += 4; + } + *asc_out = '\0'; + _LOG(log, !at_fault, " %s %s\n", code_buffer, ascii_buffer); + } +} + /* * If configured to do so, dump memory around *all* registers * for the crashing thread. */ -static void dump_memory_and_code(int tfd, pid_t tid, bool at_fault) { +void dump_memory_and_code(const ptrace_context_t* context __attribute((unused)), + log_t* log, pid_t tid, bool at_fault) { struct pt_regs regs; if(ptrace(PTRACE_GETREGS, tid, 0, ®s)) { return; @@ -73,38 +128,38 @@ static void dump_memory_and_code(int tfd, pid_t tid, bool at_fault) { continue; } - _LOG(tfd, false, "\nmemory near %.2s:\n", ®_NAMES[reg * 2]); - dump_memory(tfd, tid, addr, at_fault); + _LOG(log, false, "\nmemory near %.2s:\n", ®_NAMES[reg * 2]); + dump_memory(log, tid, addr, at_fault); } } - _LOG(tfd, !at_fault, "\ncode around pc:\n"); - dump_memory(tfd, tid, (uintptr_t)regs.ARM_pc, at_fault); + _LOG(log, !at_fault, "\ncode around pc:\n"); + dump_memory(log, tid, (uintptr_t)regs.ARM_pc, at_fault); if (regs.ARM_pc != regs.ARM_lr) { - _LOG(tfd, !at_fault, "\ncode around lr:\n"); - dump_memory(tfd, tid, (uintptr_t)regs.ARM_lr, at_fault); + _LOG(log, !at_fault, "\ncode around lr:\n"); + dump_memory(log, tid, (uintptr_t)regs.ARM_lr, at_fault); } } void dump_registers(const ptrace_context_t* context __attribute((unused)), - int tfd, pid_t tid, bool at_fault) + log_t* log, pid_t tid, bool at_fault) { struct pt_regs r; bool only_in_tombstone = !at_fault; if(ptrace(PTRACE_GETREGS, tid, 0, &r)) { - _LOG(tfd, only_in_tombstone, "cannot get registers: %s\n", strerror(errno)); + _LOG(log, 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", + _LOG(log, only_in_tombstone, " r0 %08x r1 %08x r2 %08x r3 %08x\n", (uint32_t)r.ARM_r0, (uint32_t)r.ARM_r1, (uint32_t)r.ARM_r2, (uint32_t)r.ARM_r3); - _LOG(tfd, only_in_tombstone, " r4 %08x r5 %08x r6 %08x r7 %08x\n", + _LOG(log, only_in_tombstone, " r4 %08x r5 %08x r6 %08x r7 %08x\n", (uint32_t)r.ARM_r4, (uint32_t)r.ARM_r5, (uint32_t)r.ARM_r6, (uint32_t)r.ARM_r7); - _LOG(tfd, only_in_tombstone, " r8 %08x r9 %08x sl %08x fp %08x\n", + _LOG(log, only_in_tombstone, " r8 %08x r9 %08x sl %08x fp %08x\n", (uint32_t)r.ARM_r8, (uint32_t)r.ARM_r9, (uint32_t)r.ARM_r10, (uint32_t)r.ARM_fp); - _LOG(tfd, only_in_tombstone, " ip %08x sp %08x lr %08x pc %08x cpsr %08x\n", + _LOG(log, only_in_tombstone, " ip %08x sp %08x lr %08x pc %08x cpsr %08x\n", (uint32_t)r.ARM_ip, (uint32_t)r.ARM_sp, (uint32_t)r.ARM_lr, (uint32_t)r.ARM_pc, (uint32_t)r.ARM_cpsr); @@ -113,25 +168,14 @@ void dump_registers(const ptrace_context_t* context __attribute((unused)), int i; if(ptrace(PTRACE_GETVFPREGS, tid, 0, &vfp_regs)) { - _LOG(tfd, only_in_tombstone, "cannot get registers: %s\n", strerror(errno)); + _LOG(log, only_in_tombstone, "cannot get registers: %s\n", strerror(errno)); return; } for (i = 0; i < NUM_VFP_REGS; i += 2) { - _LOG(tfd, only_in_tombstone, " d%-2d %016llx d%-2d %016llx\n", + _LOG(log, only_in_tombstone, " d%-2d %016llx d%-2d %016llx\n", i, vfp_regs.fpregs[i], i+1, vfp_regs.fpregs[i+1]); } - _LOG(tfd, only_in_tombstone, " scr %08lx\n", vfp_regs.fpscr); + _LOG(log, only_in_tombstone, " scr %08lx\n", vfp_regs.fpscr); #endif } - -void dump_thread(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault) { - dump_registers(context, tfd, tid, at_fault); - - dump_backtrace_and_stack(context, tfd, tid, at_fault); - - if (at_fault) { - dump_memory_and_code(tfd, tid, at_fault); - dump_nearby_maps(context, tfd, tid); - } -} diff --git a/debuggerd/backtrace.c b/debuggerd/backtrace.c new file mode 100644 index 000000000..2149a6733 --- /dev/null +++ b/debuggerd/backtrace.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012 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 "tombstone.h" +#include "utility.h" + +#define STACK_DEPTH 32 + +static void dump_process_header(log_t* log, pid_t pid) { + char path[PATH_MAX]; + char procnamebuf[1024]; + char* procname = NULL; + FILE* fp; + + snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); + if ((fp = fopen(path, "r"))) { + procname = fgets(procnamebuf, sizeof(procnamebuf), fp); + fclose(fp); + } + + time_t t = time(NULL); + struct tm tm; + localtime_r(&t, &tm); + char timestr[64]; + strftime(timestr, sizeof(timestr), "%F %T", &tm); + _LOG(log, false, "\n\n----- pid %d at %s -----\n", pid, timestr); + + if (procname) { + _LOG(log, false, "\nCmd line: %s\n", procname); + } +} + +static void dump_process_footer(log_t* log, pid_t pid) { + _LOG(log, false, "\n----- end %d -----\n", pid); +} + +static void dump_thread(log_t* log, pid_t tid, ptrace_context_t* context, bool attached, + bool* detach_failed, int* total_sleep_time_usec) { + char path[PATH_MAX]; + char threadnamebuf[1024]; + char* threadname = NULL; + FILE* fp; + + snprintf(path, sizeof(path), "/proc/%d/comm", tid); + if ((fp = fopen(path, "r"))) { + threadname = fgets(threadnamebuf, sizeof(threadnamebuf), fp); + fclose(fp); + if (threadname) { + size_t len = strlen(threadname); + if (len && threadname[len - 1] == '\n') { + threadname[len - 1] = '\0'; + } + } + } + + _LOG(log, false, "\n\"%s\" sysTid=%d\n", threadname ? threadname : "", tid); + + if (!attached && ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) { + _LOG(log, false, "Could not attach to thread: %s\n", strerror(errno)); + return; + } + + wait_for_stop(tid, total_sleep_time_usec); + + backtrace_frame_t backtrace[STACK_DEPTH]; + ssize_t frames = unwind_backtrace_ptrace(tid, context, backtrace, 0, STACK_DEPTH); + if (frames <= 0) { + _LOG(log, false, "Could not obtain stack trace for thread.\n"); + } else { + backtrace_symbol_t backtrace_symbols[STACK_DEPTH]; + get_backtrace_symbols_ptrace(context, backtrace, frames, backtrace_symbols); + for (size_t i = 0; i < (size_t)frames; i++) { + char line[MAX_BACKTRACE_LINE_LENGTH]; + format_backtrace_line(i, &backtrace[i], &backtrace_symbols[i], + line, MAX_BACKTRACE_LINE_LENGTH); + _LOG(log, false, " %s\n", line); + } + free_backtrace_symbols(backtrace_symbols, frames); + } + + if (!attached && ptrace(PTRACE_DETACH, tid, 0, 0) != 0) { + LOG("ptrace detach from %d failed: %s\n", tid, strerror(errno)); + *detach_failed = true; + } +} + +void dump_backtrace(int fd, pid_t pid, pid_t tid, bool* detach_failed, + int* total_sleep_time_usec) { + log_t log; + log.tfd = fd; + log.quiet = true; + + ptrace_context_t* context = load_ptrace_context(tid); + dump_process_header(&log, pid); + dump_thread(&log, tid, context, 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) { + struct dirent debuf; + struct dirent *de; + while (!readdir_r(d, &debuf, &de) && de) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { + continue; + } + + char* end; + pid_t new_tid = strtoul(de->d_name, &end, 10); + if (*end || new_tid == tid) { + continue; + } + + dump_thread(&log, new_tid, context, false, detach_failed, total_sleep_time_usec); + } + closedir(d); + } + + dump_process_footer(&log, pid); + free_ptrace_context(context); +} diff --git a/debuggerd/backtrace.h b/debuggerd/backtrace.h new file mode 100644 index 000000000..ec7d20ff4 --- /dev/null +++ b/debuggerd/backtrace.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef _DEBUGGERD_BACKTRACE_H +#define _DEBUGGERD_BACKTRACE_H + +#include +#include +#include + +#include + +/* 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, pid_t pid, pid_t tid, bool* detach_failed, + int* total_sleep_time_usec); + +#endif // _DEBUGGERD_BACKTRACE_H diff --git a/debuggerd/debuggerd.c b/debuggerd/debuggerd.c index 662683a63..8009631c8 100644 --- a/debuggerd/debuggerd.c +++ b/debuggerd/debuggerd.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include @@ -41,432 +43,16 @@ #include +#include "backtrace.h" #include "getevent.h" -#include "machine.h" +#include "tombstone.h" #include "utility.h" -#define ANDROID_LOG_INFO 4 - -static 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); -} - -static 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 SIGPIPE: return "SIGPIPE"; - case SIGSTKFLT: return "SIGSTKFLT"; - case SIGSTOP: return "SIGSTOP"; - default: return "?"; - } -} - -static const char *get_sigcode(int signo, int code) -{ - switch (signo) { - case SIGILL: - switch (code) { - case ILL_ILLOPC: return "ILL_ILLOPC"; - case ILL_ILLOPN: return "ILL_ILLOPN"; - case ILL_ILLADR: return "ILL_ILLADR"; - case ILL_ILLTRP: return "ILL_ILLTRP"; - case ILL_PRVOPC: return "ILL_PRVOPC"; - case ILL_PRVREG: return "ILL_PRVREG"; - case ILL_COPROC: return "ILL_COPROC"; - case ILL_BADSTK: return "ILL_BADSTK"; - } - break; - case SIGBUS: - switch (code) { - case BUS_ADRALN: return "BUS_ADRALN"; - case BUS_ADRERR: return "BUS_ADRERR"; - case BUS_OBJERR: return "BUS_OBJERR"; - } - break; - case SIGFPE: - switch (code) { - case FPE_INTDIV: return "FPE_INTDIV"; - case FPE_INTOVF: return "FPE_INTOVF"; - case FPE_FLTDIV: return "FPE_FLTDIV"; - case FPE_FLTOVF: return "FPE_FLTOVF"; - case FPE_FLTUND: return "FPE_FLTUND"; - case FPE_FLTRES: return "FPE_FLTRES"; - case FPE_FLTINV: return "FPE_FLTINV"; - case FPE_FLTSUB: return "FPE_FLTSUB"; - } - break; - case SIGSEGV: - switch (code) { - case SEGV_MAPERR: return "SEGV_MAPERR"; - case SEGV_ACCERR: return "SEGV_ACCERR"; - } - break; - } - return "?"; -} - -static void dump_fault_addr(int tfd, pid_t tid, int sig) -{ - siginfo_t si; - - memset(&si, 0, sizeof(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", - sig, get_signame(sig), - si.si_code, get_sigcode(sig, si.si_code), - (uintptr_t) si.si_addr); - } else { - _LOG(tfd, false, "signal %d (%s), code %d (%s), fault addr --------\n", - sig, get_signame(sig), si.si_code, get_sigcode(sig, si.si_code)); - } -} - -static void dump_crash_banner(int tfd, pid_t pid, pid_t 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); - } -} - -/* Return true if some thread is not detached cleanly */ -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); - - 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) { - pid_t 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; - } - - _LOG(tfd, true, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); - _LOG(tfd, true, "pid: %d, tid: %d\n", pid, new_tid); - - dump_thread(context, tfd, new_tid, false); - - if (ptrace(PTRACE_DETACH, new_tid, 0, 0) != 0) { - LOG("ptrace detach from %d failed: %s\n", new_tid, strerror(errno)); - detach_failed = true; - } - } - - closedir(d); - return detach_failed; -} - -/* - * Reads the contents of the specified log device, filters out the entries - * that don't match the specified pid, and writes them to the tombstone file. - * - * If "tailOnly" is set, we only print the last few lines. - */ -static void dump_log_file(int tfd, pid_t pid, const char* filename, - bool tailOnly) -{ - bool first = true; - - /* circular buffer, for "tailOnly" mode */ - const int kShortLogMaxLines = 5; - const int kShortLogLineLen = 256; - char shortLog[kShortLogMaxLines][kShortLogLineLen]; - int shortLogCount = 0; - int shortLogNext = 0; - - int logfd = open(filename, O_RDONLY | O_NONBLOCK); - if (logfd < 0) { - XLOG("Unable to open %s: %s\n", filename, strerror(errno)); - return; - } - - union { - unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1]; - struct logger_entry entry; - } log_entry; - - while (true) { - ssize_t actual = read(logfd, log_entry.buf, LOGGER_ENTRY_MAX_LEN); - if (actual < 0) { - if (errno == EINTR) { - /* interrupted by signal, retry */ - continue; - } else if (errno == EAGAIN) { - /* non-blocking EOF; we're done */ - break; - } else { - _LOG(tfd, true, "Error while reading log: %s\n", - strerror(errno)); - break; - } - } else if (actual == 0) { - _LOG(tfd, true, "Got zero bytes while reading log: %s\n", - strerror(errno)); - break; - } - - /* - * NOTE: if you XLOG something here, this will spin forever, - * because you will be writing as fast as you're reading. Any - * high-frequency debug diagnostics should just be written to - * the tombstone file. - */ - - struct logger_entry* entry = &log_entry.entry; - - if (entry->pid != (int32_t) pid) { - /* wrong pid, ignore */ - continue; - } - - if (first) { - _LOG(tfd, true, "--------- %slog %s\n", - tailOnly ? "tail end of " : "", filename); - first = false; - } - - /* - * Msg format is: \0\0 - * - * We want to display it in the same format as "logcat -v threadtime" - * (although in this case the pid is redundant). - * - * TODO: scan for line breaks ('\n') and display each text line - * on a separate line, prefixed with the header, like logcat does. - */ - static const char* kPrioChars = "!.VDIWEFS"; - unsigned char prio = entry->msg[0]; - char* tag = entry->msg + 1; - char* msg = tag + strlen(tag) + 1; - - /* consume any trailing newlines */ - char* eatnl = msg + strlen(msg) - 1; - while (eatnl >= msg && *eatnl == '\n') { - *eatnl-- = '\0'; - } - - char prioChar = (prio < strlen(kPrioChars) ? kPrioChars[prio] : '?'); - - char timeBuf[32]; - time_t sec = (time_t) entry->sec; - struct tm tmBuf; - struct tm* ptm; - ptm = localtime_r(&sec, &tmBuf); - strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm); - - if (tailOnly) { - snprintf(shortLog[shortLogNext], kShortLogLineLen, - "%s.%03d %5d %5d %c %-8s: %s", - timeBuf, entry->nsec / 1000000, entry->pid, entry->tid, - prioChar, tag, msg); - shortLogNext = (shortLogNext + 1) % kShortLogMaxLines; - shortLogCount++; - } else { - _LOG(tfd, true, "%s.%03d %5d %5d %c %-8s: %s\n", - timeBuf, entry->nsec / 1000000, entry->pid, entry->tid, - prioChar, tag, msg); - } - } - - if (tailOnly) { - int i; - - /* - * If we filled the buffer, we want to start at "next", which has - * the oldest entry. If we didn't, we want to start at zero. - */ - if (shortLogCount < kShortLogMaxLines) { - shortLogNext = 0; - } else { - shortLogCount = kShortLogMaxLines; /* cap at window size */ - } - - for (i = 0; i < shortLogCount; i++) { - _LOG(tfd, true, "%s\n", shortLog[shortLogNext]); - shortLogNext = (shortLogNext + 1) % kShortLogMaxLines; - } - } - - close(logfd); -} - -/* - * 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, pid_t pid, bool tailOnly) -{ - dump_log_file(tfd, pid, "/dev/log/system", tailOnly); - dump_log_file(tfd, pid, "/dev/log/main", tailOnly); -} - -/* - * 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) -{ - /* 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'); - - dump_crash_banner(tfd, pid, tid, signal); - - ptrace_context_t* context = load_ptrace_context(tid); - - dump_thread(context, tfd, 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(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; - - /* - * 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. - */ - 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); - } - - /* 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 path; -} +typedef struct { + debugger_action_t action; + pid_t pid, tid; + uid_t uid, gid; +} debugger_request_t; static int write_string(const char* file, const char* string) @@ -598,50 +184,7 @@ static int get_process_info(pid_t tid, pid_t* out_pid, uid_t* out_uid, uid_t* ou return fields == 7 ? 0 : -1; } -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; - } - } - - 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, -}; - -typedef struct { - int type; - pid_t pid, tid; - uid_t uid, gid; -} request_t; - -static int read_request(int fd, request_t* out_request) { +static int read_request(int fd, debugger_request_t* out_request) { struct ucred cr; int len = sizeof(cr); int status = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len); @@ -663,20 +206,37 @@ static int read_request(int fd, request_t* out_request) { return -1; } - status = TEMP_FAILURE_RETRY(read(fd, &out_request->tid, sizeof(pid_t))); + debugger_msg_t msg; + status = TEMP_FAILURE_RETRY(read(fd, &msg, sizeof(msg))); if (status < 0) { LOG("read failure? %s\n", strerror(errno)); return -1; } - if (status != sizeof(pid_t)) { + if (status != sizeof(msg)) { 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; + out_request->action = msg.action; + out_request->tid = msg.tid; + out_request->pid = cr.pid; + out_request->uid = cr.uid; + out_request->gid = cr.gid; + + if (msg.action == DEBUGGER_ACTION_CRASH) { + /* Ensure that the tid reported by the crashing process is valid. */ + 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; + } + } else if (cr.uid == 0 + || (cr.uid == AID_SYSTEM && msg.action == DEBUGGER_ACTION_DUMP_BACKTRACE)) { + /* Only root or system can ask us to attach to any process and dump it explicitly. + * However, system is only allowed to collect backtraces but cannot dump tombstones. */ status = get_process_info(out_request->tid, &out_request->pid, &out_request->uid, &out_request->gid); if (status < 0) { @@ -684,28 +244,15 @@ static int read_request(int fd, request_t* out_request) { out_request->tid); return -1; } - return 0; - } - - /* 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; - - 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); + } else { + /* No one else is not allowed to dump arbitrary processes. */ return -1; } return 0; } -static bool should_attach_gdb(request_t* request) { - if (request->type == REQUEST_TYPE_CRASH) { +static bool should_attach_gdb(debugger_request_t* request) { + if (request->action == DEBUGGER_ACTION_CRASH) { char value[PROPERTY_VALUE_MAX]; property_get("debug.db.uid", value, "-1"); int debug_uid = atoi(value); @@ -717,7 +264,7 @@ static bool should_attach_gdb(request_t* request) { static void handle_request(int fd) { XLOG("handle_request(%d)\n", fd); - request_t request; + debugger_request_t request; int status = read_request(fd, &request); if (!status) { XLOG("BOOM: pid=%d uid=%d gid=%d tid=%d\n", @@ -739,13 +286,12 @@ static void handle_request(int fd) { } else { bool detach_failed = false; bool attach_gdb = should_attach_gdb(&request); - char response = 0; - if (TEMP_FAILURE_RETRY(write(fd, &response, 1)) != 1) { + if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) { LOG("failed responding to client: %s\n", strerror(errno)); } else { char* tombstone_path = NULL; - if (request.type != REQUEST_TYPE_DUMP) { + if (request.action == DEBUGGER_ACTION_CRASH) { close(fd); fd = -1; } @@ -759,10 +305,15 @@ static void handle_request(int fd) { switch (signal) { case SIGSTOP: - if (request.type == REQUEST_TYPE_DUMP) { - XLOG("stopped -- dumping\n"); + if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { + XLOG("stopped -- dumping to tombstone\n"); tombstone_path = engrave_tombstone(request.pid, request.tid, - signal, true, &detach_failed); + signal, true, true, &detach_failed, + &total_sleep_time_usec); + } else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) { + XLOG("stopped -- dumping to fd\n"); + dump_backtrace(fd, request.pid, request.tid, &detach_failed, + &total_sleep_time_usec); } else { XLOG("stopped -- continuing\n"); status = ptrace(PTRACE_CONT, request.tid, 0, 0); @@ -791,7 +342,8 @@ static void handle_request(int fd) { /* 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); + signal, !attach_gdb, false, &detach_failed, + &total_sleep_time_usec); break; } @@ -803,7 +355,7 @@ static void handle_request(int fd) { break; } - if (request.type == REQUEST_TYPE_DUMP) { + if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { if (tombstone_path) { write(fd, tombstone_path, strlen(tombstone_path)); } @@ -888,7 +440,7 @@ static int do_server() { act.sa_flags = SA_NOCLDWAIT; sigaction(SIGCHLD, &act, 0); - s = socket_local_server("android:debuggerd", + s = socket_local_server(DEBUGGER_SOCKET_NAME, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); if(s < 0) return 1; fcntl(s, F_SETFD, FD_CLOEXEC); @@ -915,47 +467,56 @@ static int do_server() { return 0; } -static int do_explicit_dump(pid_t tid) { +static int do_explicit_dump(pid_t tid, bool dump_backtrace) { 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); + if (dump_backtrace) { + fflush(stdout); + if (dump_backtrace_to_file(tid, fileno(stdout)) < 0) { + fputs("Error dumping backtrace.\n", stderr); + return 1; + } } 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); + if (dump_tombstone(tid, tombstone_path, sizeof(tombstone_path)) < 0) { + fputs("Error dumping tombstone.\n", stderr); + return 1; } + fprintf(stderr, "Tombstone written to: %s\n", tombstone_path); } - - close(fd); return 0; } +static void usage() { + fputs("Usage: -b []\n" + " -b dump backtrace to console, otherwise dump full tombstone file\n" + "\n" + "If tid specified, sends a request to debuggerd to dump that task.\n" + "Otherwise, starts the debuggerd server.\n", stderr); +} + int main(int argc, char** argv) { - if (argc == 2) { - pid_t tid = atoi(argv[1]); - if (!tid) { - fputs("Usage: []\n" - "\n" - "If tid specified, sends a request to debuggerd to dump that task.\n" - "Otherwise, starts the debuggerd server.\n", stderr); + if (argc == 1) { + return do_server(); + } + + bool dump_backtrace = false; + bool have_tid = false; + pid_t tid = 0; + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-b")) { + dump_backtrace = true; + } else if (!have_tid) { + tid = atoi(argv[i]); + have_tid = true; + } else { + usage(); return 1; } - return do_explicit_dump(tid); } - return do_server(); + if (!have_tid) { + usage(); + return 1; + } + return do_explicit_dump(tid, dump_backtrace); } diff --git a/debuggerd/machine.h b/debuggerd/machine.h index 6049b69d0..1619dd3e4 100644 --- a/debuggerd/machine.h +++ b/debuggerd/machine.h @@ -17,9 +17,15 @@ #ifndef _DEBUGGERD_MACHINE_H #define _DEBUGGERD_MACHINE_H -#include +#include +#include #include -void dump_thread(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault); +#include + +#include "utility.h" + +void dump_memory_and_code(const ptrace_context_t* context, log_t* log, pid_t tid, bool at_fault); +void dump_registers(const ptrace_context_t* context, log_t* log, pid_t tid, bool at_fault); #endif // _DEBUGGERD_MACHINE_H diff --git a/debuggerd/tombstone.c b/debuggerd/tombstone.c new file mode 100644 index 000000000..16b49433b --- /dev/null +++ b/debuggerd/tombstone.c @@ -0,0 +1,698 @@ +/* + * Copyright (C) 2012 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 "machine.h" +#include "tombstone.h" +#include "utility.h" + +#define STACK_DEPTH 32 +#define STACK_WORDS 16 + +#define MAX_TOMBSTONES 10 +#define TOMBSTONE_DIR "/data/tombstones" + +#define typecheck(x,y) { \ + typeof(x) __dummy1; \ + typeof(y) __dummy2; \ + (void)(&__dummy1 == &__dummy2); } + + +static bool signal_has_address(int sig) { + switch (sig) { + case SIGILL: + case SIGFPE: + case SIGSEGV: + case SIGBUS: + return true; + default: + return false; + } +} + +static 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 SIGPIPE: return "SIGPIPE"; + case SIGSTKFLT: return "SIGSTKFLT"; + case SIGSTOP: return "SIGSTOP"; + default: return "?"; + } +} + +static const char *get_sigcode(int signo, int code) +{ + switch (signo) { + case SIGILL: + switch (code) { + case ILL_ILLOPC: return "ILL_ILLOPC"; + case ILL_ILLOPN: return "ILL_ILLOPN"; + case ILL_ILLADR: return "ILL_ILLADR"; + case ILL_ILLTRP: return "ILL_ILLTRP"; + case ILL_PRVOPC: return "ILL_PRVOPC"; + case ILL_PRVREG: return "ILL_PRVREG"; + case ILL_COPROC: return "ILL_COPROC"; + case ILL_BADSTK: return "ILL_BADSTK"; + } + break; + case SIGBUS: + switch (code) { + case BUS_ADRALN: return "BUS_ADRALN"; + case BUS_ADRERR: return "BUS_ADRERR"; + case BUS_OBJERR: return "BUS_OBJERR"; + } + break; + case SIGFPE: + switch (code) { + case FPE_INTDIV: return "FPE_INTDIV"; + case FPE_INTOVF: return "FPE_INTOVF"; + case FPE_FLTDIV: return "FPE_FLTDIV"; + case FPE_FLTOVF: return "FPE_FLTOVF"; + case FPE_FLTUND: return "FPE_FLTUND"; + case FPE_FLTRES: return "FPE_FLTRES"; + case FPE_FLTINV: return "FPE_FLTINV"; + case FPE_FLTSUB: return "FPE_FLTSUB"; + } + break; + case SIGSEGV: + switch (code) { + case SEGV_MAPERR: return "SEGV_MAPERR"; + case SEGV_ACCERR: return "SEGV_ACCERR"; + } + break; + } + return "?"; +} + +static void dump_build_info(log_t* log) +{ + char fingerprint[PROPERTY_VALUE_MAX]; + + property_get("ro.build.fingerprint", fingerprint, "unknown"); + + _LOG(log, false, "Build fingerprint: '%s'\n", fingerprint); +} + +static void dump_fault_addr(log_t* log, pid_t tid, int sig) +{ + siginfo_t si; + + memset(&si, 0, sizeof(si)); + if(ptrace(PTRACE_GETSIGINFO, tid, 0, &si)){ + _LOG(log, false, "cannot get siginfo: %s\n", strerror(errno)); + } else if (signal_has_address(sig)) { + _LOG(log, false, "signal %d (%s), code %d (%s), fault addr %08x\n", + sig, get_signame(sig), + si.si_code, get_sigcode(sig, si.si_code), + (uintptr_t) si.si_addr); + } else { + _LOG(log, false, "signal %d (%s), code %d (%s), fault addr --------\n", + sig, get_signame(sig), si.si_code, get_sigcode(sig, si.si_code)); + } +} + +static void dump_thread_info(log_t* log, pid_t pid, pid_t tid, bool at_fault) { + char path[64]; + char threadnamebuf[1024]; + char* threadname = NULL; + FILE *fp; + + snprintf(path, sizeof(path), "/proc/%d/comm", tid); + if ((fp = fopen(path, "r"))) { + threadname = fgets(threadnamebuf, sizeof(threadnamebuf), fp); + fclose(fp); + if (threadname) { + size_t len = strlen(threadname); + if (len && threadname[len - 1] == '\n') { + threadname[len - 1] = '\0'; + } + } + } + + if (at_fault) { + char procnamebuf[1024]; + char* procname = NULL; + + snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); + if ((fp = fopen(path, "r"))) { + procname = fgets(procnamebuf, sizeof(procnamebuf), fp); + fclose(fp); + } + + _LOG(log, false, "pid: %d, tid: %d, name: %s >>> %s <<<\n", pid, tid, + threadname ? threadname : "UNKNOWN", + procname ? procname : "UNKNOWN"); + } else { + _LOG(log, true, "pid: %d, tid: %d, name: %s\n", pid, tid, + threadname ? threadname : "UNKNOWN"); + } +} + +static void dump_backtrace(const ptrace_context_t* context __attribute((unused)), + log_t* log, pid_t tid __attribute((unused)), bool at_fault, + const backtrace_frame_t* backtrace, size_t frames) { + _LOG(log, !at_fault, "\nbacktrace:\n"); + + backtrace_symbol_t backtrace_symbols[STACK_DEPTH]; + get_backtrace_symbols_ptrace(context, backtrace, frames, backtrace_symbols); + for (size_t i = 0; i < frames; i++) { + char line[MAX_BACKTRACE_LINE_LENGTH]; + format_backtrace_line(i, &backtrace[i], &backtrace_symbols[i], + line, MAX_BACKTRACE_LINE_LENGTH); + _LOG(log, !at_fault, " %s\n", line); + } + free_backtrace_symbols(backtrace_symbols, frames); +} + +static void dump_stack_segment(const ptrace_context_t* context, log_t* log, pid_t tid, + bool only_in_tombstone, uintptr_t* sp, size_t words, int label) { + for (size_t i = 0; i < words; i++) { + uint32_t stack_content; + if (!try_get_word_ptrace(tid, *sp, &stack_content)) { + break; + } + + const map_info_t* mi; + const symbol_t* symbol; + find_symbol_ptrace(context, stack_content, &mi, &symbol); + + if (symbol) { + char* demangled_name = demangle_symbol_name(symbol->name); + const char* symbol_name = demangled_name ? demangled_name : symbol->name; + uint32_t offset = stack_content - (mi->start + symbol->start); + if (!i && label >= 0) { + if (offset) { + _LOG(log, only_in_tombstone, " #%02d %08x %08x %s (%s+%u)\n", + label, *sp, stack_content, mi ? mi->name : "", symbol_name, offset); + } else { + _LOG(log, only_in_tombstone, " #%02d %08x %08x %s (%s)\n", + label, *sp, stack_content, mi ? mi->name : "", symbol_name); + } + } else { + if (offset) { + _LOG(log, only_in_tombstone, " %08x %08x %s (%s+%u)\n", + *sp, stack_content, mi ? mi->name : "", symbol_name, offset); + } else { + _LOG(log, only_in_tombstone, " %08x %08x %s (%s)\n", + *sp, stack_content, mi ? mi->name : "", symbol_name); + } + } + free(demangled_name); + } else { + if (!i && label >= 0) { + _LOG(log, only_in_tombstone, " #%02d %08x %08x %s\n", + label, *sp, stack_content, mi ? mi->name : ""); + } else { + _LOG(log, only_in_tombstone, " %08x %08x %s\n", + *sp, stack_content, mi ? mi->name : ""); + } + } + + *sp += sizeof(uint32_t); + } +} + +static void dump_stack(const ptrace_context_t* context, log_t* log, pid_t tid, bool at_fault, + const backtrace_frame_t* backtrace, size_t frames) { + bool have_first = false; + size_t first, last; + for (size_t i = 0; i < frames; i++) { + if (backtrace[i].stack_top) { + if (!have_first) { + have_first = true; + first = i; + } + last = i; + } + } + if (!have_first) { + return; + } + + _LOG(log, !at_fault, "\nstack:\n"); + + // Dump a few words before the first frame. + bool only_in_tombstone = !at_fault; + uintptr_t sp = backtrace[first].stack_top - STACK_WORDS * sizeof(uint32_t); + dump_stack_segment(context, log, tid, only_in_tombstone, &sp, STACK_WORDS, -1); + + // Dump a few words from all successive frames. + // Only log the first 3 frames, put the rest in the tombstone. + for (size_t i = first; i <= last; i++) { + const backtrace_frame_t* frame = &backtrace[i]; + if (sp != frame->stack_top) { + _LOG(log, only_in_tombstone, " ........ ........\n"); + sp = frame->stack_top; + } + if (i - first == 3) { + only_in_tombstone = true; + } + if (i == last) { + dump_stack_segment(context, log, tid, only_in_tombstone, &sp, STACK_WORDS, i); + if (sp < frame->stack_top + frame->stack_size) { + _LOG(log, only_in_tombstone, " ........ ........\n"); + } + } else { + size_t words = frame->stack_size / sizeof(uint32_t); + if (words == 0) { + words = 1; + } else if (words > STACK_WORDS) { + words = STACK_WORDS; + } + dump_stack_segment(context, log, tid, only_in_tombstone, &sp, words, i); + } + } +} + +static void dump_backtrace_and_stack(const ptrace_context_t* context, log_t* log, pid_t tid, + bool at_fault) { + backtrace_frame_t backtrace[STACK_DEPTH]; + ssize_t frames = unwind_backtrace_ptrace(tid, context, backtrace, 0, STACK_DEPTH); + if (frames > 0) { + dump_backtrace(context, log, tid, at_fault, backtrace, frames); + dump_stack(context, log, tid, at_fault, backtrace, frames); + } +} + +static void dump_nearby_maps(const ptrace_context_t* context, log_t* log, pid_t tid) { + siginfo_t si; + memset(&si, 0, sizeof(si)); + if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si)) { + _LOG(log, false, "cannot get siginfo for %d: %s\n", + tid, strerror(errno)); + return; + } + if (!signal_has_address(si.si_signo)) { + return; + } + + uintptr_t addr = (uintptr_t) si.si_addr; + addr &= ~0xfff; /* round to 4K page boundary */ + if (addr == 0) { /* null-pointer deref */ + return; + } + + _LOG(log, false, "\nmemory map around fault addr %08x:\n", (int)si.si_addr); + + /* + * Search for a match, or for a hole where the match would be. The list + * is backward from the file content, so it starts at high addresses. + */ + map_info_t* map = context->map_info_list; + map_info_t *next = NULL; + map_info_t *prev = NULL; + while (map != NULL) { + if (addr >= map->start && addr < map->end) { + next = map->next; + break; + } else if (addr >= map->end) { + /* map would be between "prev" and this entry */ + next = map; + map = NULL; + break; + } + + prev = map; + map = map->next; + } + + /* + * Show "next" then "match" then "prev" so that the addresses appear in + * ascending order (like /proc/pid/maps). + */ + if (next != NULL) { + _LOG(log, false, " %08x-%08x %s\n", next->start, next->end, next->name); + } else { + _LOG(log, false, " (no map below)\n"); + } + if (map != NULL) { + _LOG(log, false, " %08x-%08x %s\n", map->start, map->end, map->name); + } else { + _LOG(log, false, " (no map for address)\n"); + } + if (prev != NULL) { + _LOG(log, false, " %08x-%08x %s\n", prev->start, prev->end, prev->name); + } else { + _LOG(log, false, " (no map above)\n"); + } +} + +static void dump_thread(const ptrace_context_t* context, log_t* log, pid_t tid, bool at_fault, + int* total_sleep_time_usec) { + wait_for_stop(tid, total_sleep_time_usec); + + dump_registers(context, log, tid, at_fault); + dump_backtrace_and_stack(context, log, tid, at_fault); + if (at_fault) { + dump_memory_and_code(context, log, tid, at_fault); + dump_nearby_maps(context, log, tid); + } +} + +/* Return true if some thread is not detached cleanly */ +static bool dump_sibling_thread_report(const ptrace_context_t* context, + log_t* log, pid_t pid, pid_t tid, int* total_sleep_time_usec) { + char task_path[64]; + snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid); + + 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 debuf; + struct dirent *de; + while (!readdir_r(d, &debuf, &de) && de) { + /* 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; + } + + /* Skip this thread if cannot ptrace it */ + if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) { + continue; + } + + _LOG(log, true, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); + dump_thread_info(log, pid, new_tid, false); + dump_thread(context, log, new_tid, false, total_sleep_time_usec); + + if (ptrace(PTRACE_DETACH, new_tid, 0, 0) != 0) { + LOG("ptrace detach from %d failed: %s\n", new_tid, strerror(errno)); + detach_failed = true; + } + } + + closedir(d); + return detach_failed; +} + +/* + * Reads the contents of the specified log device, filters out the entries + * that don't match the specified pid, and writes them to the tombstone file. + * + * If "tailOnly" is set, we only print the last few lines. + */ +static void dump_log_file(log_t* log, pid_t pid, const char* filename, + bool tailOnly) +{ + bool first = true; + + /* circular buffer, for "tailOnly" mode */ + const int kShortLogMaxLines = 5; + const int kShortLogLineLen = 256; + char shortLog[kShortLogMaxLines][kShortLogLineLen]; + int shortLogCount = 0; + int shortLogNext = 0; + + int logfd = open(filename, O_RDONLY | O_NONBLOCK); + if (logfd < 0) { + XLOG("Unable to open %s: %s\n", filename, strerror(errno)); + return; + } + + union { + unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1]; + struct logger_entry entry; + } log_entry; + + while (true) { + ssize_t actual = read(logfd, log_entry.buf, LOGGER_ENTRY_MAX_LEN); + if (actual < 0) { + if (errno == EINTR) { + /* interrupted by signal, retry */ + continue; + } else if (errno == EAGAIN) { + /* non-blocking EOF; we're done */ + break; + } else { + _LOG(log, true, "Error while reading log: %s\n", + strerror(errno)); + break; + } + } else if (actual == 0) { + _LOG(log, true, "Got zero bytes while reading log: %s\n", + strerror(errno)); + break; + } + + /* + * NOTE: if you XLOG something here, this will spin forever, + * because you will be writing as fast as you're reading. Any + * high-frequency debug diagnostics should just be written to + * the tombstone file. + */ + + struct logger_entry* entry = &log_entry.entry; + + if (entry->pid != (int32_t) pid) { + /* wrong pid, ignore */ + continue; + } + + if (first) { + _LOG(log, true, "--------- %slog %s\n", + tailOnly ? "tail end of " : "", filename); + first = false; + } + + /* + * Msg format is: \0\0 + * + * We want to display it in the same format as "logcat -v threadtime" + * (although in this case the pid is redundant). + * + * TODO: scan for line breaks ('\n') and display each text line + * on a separate line, prefixed with the header, like logcat does. + */ + static const char* kPrioChars = "!.VDIWEFS"; + unsigned char prio = entry->msg[0]; + char* tag = entry->msg + 1; + char* msg = tag + strlen(tag) + 1; + + /* consume any trailing newlines */ + char* eatnl = msg + strlen(msg) - 1; + while (eatnl >= msg && *eatnl == '\n') { + *eatnl-- = '\0'; + } + + char prioChar = (prio < strlen(kPrioChars) ? kPrioChars[prio] : '?'); + + char timeBuf[32]; + time_t sec = (time_t) entry->sec; + struct tm tmBuf; + struct tm* ptm; + ptm = localtime_r(&sec, &tmBuf); + strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm); + + if (tailOnly) { + snprintf(shortLog[shortLogNext], kShortLogLineLen, + "%s.%03d %5d %5d %c %-8s: %s", + timeBuf, entry->nsec / 1000000, entry->pid, entry->tid, + prioChar, tag, msg); + shortLogNext = (shortLogNext + 1) % kShortLogMaxLines; + shortLogCount++; + } else { + _LOG(log, true, "%s.%03d %5d %5d %c %-8s: %s\n", + timeBuf, entry->nsec / 1000000, entry->pid, entry->tid, + prioChar, tag, msg); + } + } + + if (tailOnly) { + int i; + + /* + * If we filled the buffer, we want to start at "next", which has + * the oldest entry. If we didn't, we want to start at zero. + */ + if (shortLogCount < kShortLogMaxLines) { + shortLogNext = 0; + } else { + shortLogCount = kShortLogMaxLines; /* cap at window size */ + } + + for (i = 0; i < shortLogCount; i++) { + _LOG(log, true, "%s\n", shortLog[shortLogNext]); + shortLogNext = (shortLogNext + 1) % kShortLogMaxLines; + } + } + + close(logfd); +} + +/* + * 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(log_t* log, pid_t pid, bool tailOnly) +{ + dump_log_file(log, pid, "/dev/log/system", tailOnly); + dump_log_file(log, pid, "/dev/log/main", tailOnly); +} + +/* + * Dumps all information about the specified pid to the tombstone. + */ +static bool dump_crash(log_t* log, pid_t pid, pid_t tid, int signal, + bool dump_sibling_threads, int* total_sleep_time_usec) +{ + /* 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 want_logs = (value[0] == '1'); + + _LOG(log, false, + "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); + dump_build_info(log); + dump_thread_info(log, pid, tid, true); + if(signal) { + dump_fault_addr(log, tid, signal); + } + + ptrace_context_t* context = load_ptrace_context(tid); + dump_thread(context, log, tid, true, total_sleep_time_usec); + + if (want_logs) { + dump_logs(log, pid, true); + } + + bool detach_failed = false; + if (dump_sibling_threads) { + detach_failed = dump_sibling_thread_report(context, log, pid, tid, total_sleep_time_usec); + } + + free_ptrace_context(context); + + if (want_logs) { + dump_logs(log, pid, false); + } + return detach_failed; +} + +/* + * 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; + + /* + * 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. + */ + 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); + } + + /* 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); +} + +char* engrave_tombstone(pid_t pid, pid_t tid, int signal, + bool dump_sibling_threads, bool quiet, bool* detach_failed, + int* total_sleep_time_usec) { + 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; + } + + log_t log; + log.tfd = fd; + log.quiet = quiet; + *detach_failed = dump_crash(&log, pid, tid, signal, dump_sibling_threads, + total_sleep_time_usec); + + close(fd); + return path; +} diff --git a/debuggerd/tombstone.h b/debuggerd/tombstone.h new file mode 100644 index 000000000..edcd7b1ea --- /dev/null +++ b/debuggerd/tombstone.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef _DEBUGGERD_TOMBSTONE_H +#define _DEBUGGERD_TOMBSTONE_H + +#include +#include +#include + +#include + +/* Creates a tombstone file and writes the crash dump to it. + * Returns the path of the tombstone, which must be freed using free(). */ +char* engrave_tombstone(pid_t pid, pid_t tid, int signal, + bool dump_sibling_threads, bool quiet, bool* detach_failed, int* total_sleep_time_usec); + +#endif // _DEBUGGERD_TOMBSTONE_H diff --git a/debuggerd/utility.c b/debuggerd/utility.c index 8eb52ba26..aabaf7468 100644 --- a/debuggerd/utility.c +++ b/debuggerd/utility.c @@ -15,293 +15,80 @@ ** limitations under the License. */ -#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include +#include #include "utility.h" -#define STACK_DEPTH 32 -#define STACK_WORDS 16 +const int sleep_time_usec = 50000; /* 0.05 seconds */ +const int max_total_sleep_usec = 10000000; /* 10 seconds */ -void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...) { +void _LOG(log_t* log, bool in_tombstone_only, const char *fmt, ...) { char buf[512]; va_list ap; va_start(ap, fmt); - if (tfd >= 0) { + if (log && log->tfd >= 0) { int len; vsnprintf(buf, sizeof(buf), fmt, ap); len = strlen(buf); - if(tfd >= 0) write(tfd, buf, len); + write(log->tfd, buf, len); } - if (!in_tombstone_only) + if (!in_tombstone_only && (!log || !log->quiet)) { __android_log_vprint(ANDROID_LOG_INFO, "DEBUG", fmt, ap); + } va_end(ap); } -bool signal_has_address(int sig) { - switch (sig) { - case SIGILL: - case SIGFPE: - case SIGSEGV: - case SIGBUS: - return true; - default: - return false; - } -} - -static void dump_backtrace(const ptrace_context_t* context __attribute((unused)), - int tfd, pid_t tid __attribute((unused)), bool at_fault, - const backtrace_frame_t* backtrace, size_t frames) { - _LOG(tfd, !at_fault, "\nbacktrace:\n"); - - backtrace_symbol_t backtrace_symbols[STACK_DEPTH]; - get_backtrace_symbols_ptrace(context, backtrace, frames, backtrace_symbols); - for (size_t i = 0; i < frames; i++) { - char line[MAX_BACKTRACE_LINE_LENGTH]; - format_backtrace_line(i, &backtrace[i], &backtrace_symbols[i], - line, MAX_BACKTRACE_LINE_LENGTH); - _LOG(tfd, !at_fault, " %s\n", line); - } - free_backtrace_symbols(backtrace_symbols, frames); -} - -static void dump_stack_segment(const ptrace_context_t* context, int tfd, pid_t tid, - bool only_in_tombstone, uintptr_t* sp, size_t words, int label) { - for (size_t i = 0; i < words; i++) { - uint32_t stack_content; - if (!try_get_word_ptrace(tid, *sp, &stack_content)) { - break; - } - - const map_info_t* mi; - const symbol_t* symbol; - find_symbol_ptrace(context, stack_content, &mi, &symbol); - - if (symbol) { - char* demangled_name = demangle_symbol_name(symbol->name); - const char* symbol_name = demangled_name ? demangled_name : symbol->name; - uint32_t offset = stack_content - (mi->start + symbol->start); - if (!i && label >= 0) { - if (offset) { - _LOG(tfd, only_in_tombstone, " #%02d %08x %08x %s (%s+%u)\n", - label, *sp, stack_content, mi ? mi->name : "", symbol_name, offset); - } else { - _LOG(tfd, only_in_tombstone, " #%02d %08x %08x %s (%s)\n", - label, *sp, stack_content, mi ? mi->name : "", symbol_name); - } +int wait_for_signal(pid_t tid, int* total_sleep_time_usec) { + 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 { - if (offset) { - _LOG(tfd, only_in_tombstone, " %08x %08x %s (%s+%u)\n", - *sp, stack_content, mi ? mi->name : "", symbol_name, offset); - } else { - _LOG(tfd, only_in_tombstone, " %08x %08x %s (%s)\n", - *sp, stack_content, mi ? mi->name : "", symbol_name); - } - } - free(demangled_name); - } else { - if (!i && label >= 0) { - _LOG(tfd, only_in_tombstone, " #%02d %08x %08x %s\n", - label, *sp, stack_content, mi ? mi->name : ""); - } else { - _LOG(tfd, only_in_tombstone, " %08x %08x %s\n", - *sp, stack_content, mi ? mi->name : ""); + LOG("unexpected waitpid response: n=%d, status=%08x\n", n, status); + return -1; } } - *sp += sizeof(uint32_t); + 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; } } -static void dump_stack(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault, - const backtrace_frame_t* backtrace, size_t frames) { - bool have_first = false; - size_t first, last; - for (size_t i = 0; i < frames; i++) { - if (backtrace[i].stack_top) { - if (!have_first) { - have_first = true; - first = i; - } - last = i; - } - } - if (!have_first) { - return; - } - - _LOG(tfd, !at_fault, "\nstack:\n"); - - // Dump a few words before the first frame. - bool only_in_tombstone = !at_fault; - uintptr_t sp = backtrace[first].stack_top - STACK_WORDS * sizeof(uint32_t); - dump_stack_segment(context, tfd, tid, only_in_tombstone, &sp, STACK_WORDS, -1); - - // Dump a few words from all successive frames. - // Only log the first 3 frames, put the rest in the tombstone. - for (size_t i = first; i <= last; i++) { - const backtrace_frame_t* frame = &backtrace[i]; - if (sp != frame->stack_top) { - _LOG(tfd, only_in_tombstone, " ........ ........\n"); - sp = frame->stack_top; - } - if (i - first == 3) { - only_in_tombstone = true; - } - if (i == last) { - dump_stack_segment(context, tfd, tid, only_in_tombstone, &sp, STACK_WORDS, i); - if (sp < frame->stack_top + frame->stack_size) { - _LOG(tfd, only_in_tombstone, " ........ ........\n"); - } - } else { - size_t words = frame->stack_size / sizeof(uint32_t); - if (words == 0) { - words = 1; - } else if (words > STACK_WORDS) { - words = STACK_WORDS; - } - dump_stack_segment(context, tfd, tid, only_in_tombstone, &sp, words, i); - } - } -} - -void dump_backtrace_and_stack(const ptrace_context_t* context, int tfd, pid_t tid, - bool at_fault) { - backtrace_frame_t backtrace[STACK_DEPTH]; - ssize_t frames = unwind_backtrace_ptrace(tid, context, backtrace, 0, STACK_DEPTH); - if (frames > 0) { - dump_backtrace(context, tfd, tid, at_fault, backtrace, frames); - dump_stack(context, tfd, tid, at_fault, backtrace, frames); - } -} - -void dump_memory(int tfd, pid_t tid, uintptr_t addr, bool at_fault) { - char code_buffer[64]; /* actual 8+1+((8+1)*4) + 1 == 45 */ - char ascii_buffer[32]; /* actual 16 + 1 == 17 */ - uintptr_t p, end; - - p = addr & ~3; - p -= 32; - if (p > addr) { - /* catch underflow */ - p = 0; - } - end = p + 80; - /* catch overflow; 'end - p' has to be multiples of 16 */ - while (end < p) - end -= 16; - - /* Dump the code around PC as: - * addr contents ascii - * 00008d34 ef000000 e8bd0090 e1b00000 512fff1e ............../Q - * 00008d44 ea00b1f9 e92d0090 e3a070fc ef000000 ......-..p...... - */ - while (p < end) { - char* asc_out = ascii_buffer; - - sprintf(code_buffer, "%08x ", p); - - int i; - for (i = 0; i < 4; i++) { - /* - * If we see (data == -1 && errno != 0), we know that the ptrace - * call failed, probably because we're dumping memory in an - * unmapped or inaccessible page. I don't know if there's - * value in making that explicit in the output -- it likely - * just complicates parsing and clarifies nothing for the - * enlightened reader. - */ - long data = ptrace(PTRACE_PEEKTEXT, tid, (void*)p, NULL); - sprintf(code_buffer + strlen(code_buffer), "%08lx ", data); - - int j; - for (j = 0; j < 4; j++) { - /* - * Our isprint() allows high-ASCII characters that display - * differently (often badly) in different viewers, so we - * just use a simpler test. - */ - char val = (data >> (j*8)) & 0xff; - if (val >= 0x20 && val < 0x7f) { - *asc_out++ = val; - } else { - *asc_out++ = '.'; - } - } - p += 4; - } - *asc_out = '\0'; - _LOG(tfd, !at_fault, " %s %s\n", code_buffer, ascii_buffer); - } -} - -void dump_nearby_maps(const ptrace_context_t* context, int tfd, pid_t tid) { +void wait_for_stop(pid_t tid, int* total_sleep_time_usec) { siginfo_t si; - memset(&si, 0, sizeof(si)); - if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si)) { - _LOG(tfd, false, "cannot get siginfo for %d: %s\n", - tid, strerror(errno)); - return; - } - if (!signal_has_address(si.si_signo)) { - return; - } - - uintptr_t addr = (uintptr_t) si.si_addr; - addr &= ~0xfff; /* round to 4K page boundary */ - if (addr == 0) { /* null-pointer deref */ - return; - } - - _LOG(tfd, false, "\nmemory map around fault addr %08x:\n", (int)si.si_addr); - - /* - * Search for a match, or for a hole where the match would be. The list - * is backward from the file content, so it starts at high addresses. - */ - map_info_t* map = context->map_info_list; - map_info_t *next = NULL; - map_info_t *prev = NULL; - while (map != NULL) { - if (addr >= map->start && addr < map->end) { - next = map->next; - break; - } else if (addr >= map->end) { - /* map would be between "prev" and this entry */ - next = map; - map = NULL; + while (TEMP_FAILURE_RETRY(ptrace(PTRACE_GETSIGINFO, tid, 0, &si)) < 0 && errno == ESRCH) { + if (*total_sleep_time_usec > max_total_sleep_usec) { + LOG("timed out waiting for tid=%d to stop\n", tid); break; } - prev = map; - map = map->next; - } - - /* - * Show "next" then "match" then "prev" so that the addresses appear in - * ascending order (like /proc/pid/maps). - */ - if (next != NULL) { - _LOG(tfd, false, " %08x-%08x %s\n", next->start, next->end, next->name); - } else { - _LOG(tfd, false, " (no map below)\n"); - } - if (map != NULL) { - _LOG(tfd, false, " %08x-%08x %s\n", map->start, map->end, map->name); - } else { - _LOG(tfd, false, " (no map for address)\n"); - } - if (prev != NULL) { - _LOG(tfd, false, " %08x-%08x %s\n", prev->start, prev->end, prev->name); - } else { - _LOG(tfd, false, " (no map above)\n"); + usleep(sleep_time_usec); + *total_sleep_time_usec += sleep_time_usec; } } diff --git a/debuggerd/utility.h b/debuggerd/utility.h index 39f91cba4..136f46d8d 100644 --- a/debuggerd/utility.h +++ b/debuggerd/utility.h @@ -20,53 +20,35 @@ #include #include -#include -#include + +typedef struct { + /* tombstone file descriptor */ + int tfd; + /* if true, does not log anything to the Android logcat */ + bool quiet; +} log_t; /* Log information onto the tombstone. */ -void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...) +void _LOG(log_t* log, bool in_tombstone_only, const char *fmt, ...) __attribute__ ((format(printf, 3, 4))); -#define LOG(fmt...) _LOG(-1, 0, fmt) +#define LOG(fmt...) _LOG(NULL, 0, fmt) /* Set to 1 for normal debug traces */ #if 0 -#define XLOG(fmt...) _LOG(-1, 0, fmt) +#define XLOG(fmt...) _LOG(NULL, 0, fmt) #else #define XLOG(fmt...) do {} while(0) #endif /* Set to 1 for chatty debug traces. Includes all resolved dynamic symbols */ #if 0 -#define XLOG2(fmt...) _LOG(-1, 0, fmt) +#define XLOG2(fmt...) _LOG(NULL, 0, fmt) #else #define XLOG2(fmt...) do {} while(0) #endif -/* - * Returns true if the specified signal has an associated address. - * (i.e. it sets siginfo_t.si_addr). - */ -bool signal_has_address(int sig); - -/* - * Dumps the backtrace and contents of the stack. - */ -void dump_backtrace_and_stack(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault); - -/* - * Dumps a few bytes of memory, starting a bit before and ending a bit - * after the specified address. - */ -void dump_memory(int tfd, pid_t tid, uintptr_t addr, bool at_fault); - -/* - * If this isn't clearly a null pointer dereference, dump the - * /proc/maps entries near the fault address. - * - * This only makes sense to do on the thread that crashed. - */ -void dump_nearby_maps(const ptrace_context_t* context, int tfd, pid_t tid); - +int wait_for_signal(pid_t tid, int* total_sleep_time_usec); +void wait_for_stop(pid_t tid, int* total_sleep_time_usec); #endif // _DEBUGGERD_UTILITY_H diff --git a/debuggerd/x86/machine.c b/debuggerd/x86/machine.c index 2729c7edc..01da5fef0 100644 --- a/debuggerd/x86/machine.c +++ b/debuggerd/x86/machine.c @@ -15,59 +15,44 @@ ** 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 -#include "../machine.h" #include "../utility.h" +#include "../machine.h" -static void dump_registers(const ptrace_context_t* context __attribute((unused)), - int tfd, pid_t tid, bool at_fault) { +void dump_memory_and_code(const ptrace_context_t* context __attribute((unused)), + log_t* log, pid_t tid, bool at_fault) { +} + +void dump_registers(const ptrace_context_t* context __attribute((unused)), + log_t* log, pid_t tid, bool at_fault) { struct pt_regs_x86 r; bool only_in_tombstone = !at_fault; if(ptrace(PTRACE_GETREGS, tid, 0, &r)) { - _LOG(tfd, only_in_tombstone, "cannot get registers: %s\n", strerror(errno)); + _LOG(log, only_in_tombstone, "cannot get registers: %s\n", strerror(errno)); return; } //if there is no stack, no print just like arm if(!r.ebp) return; - _LOG(tfd, only_in_tombstone, " eax %08x ebx %08x ecx %08x edx %08x\n", + _LOG(log, only_in_tombstone, " eax %08x ebx %08x ecx %08x edx %08x\n", r.eax, r.ebx, r.ecx, r.edx); - _LOG(tfd, only_in_tombstone, " esi %08x edi %08x\n", + _LOG(log, only_in_tombstone, " esi %08x edi %08x\n", r.esi, r.edi); - _LOG(tfd, only_in_tombstone, " xcs %08x xds %08x xes %08x xfs %08x xss %08x\n", + _LOG(log, only_in_tombstone, " xcs %08x xds %08x xes %08x xfs %08x xss %08x\n", r.xcs, r.xds, r.xes, r.xfs, r.xss); - _LOG(tfd, only_in_tombstone, " eip %08x ebp %08x esp %08x flags %08x\n", + _LOG(log, only_in_tombstone, " eip %08x ebp %08x esp %08x flags %08x\n", r.eip, r.ebp, r.esp, r.eflags); } - -void dump_thread(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault) { - dump_registers(context, tfd, tid, at_fault); - - dump_backtrace_and_stack(context, tfd, tid, at_fault); - - if (at_fault) { - dump_nearby_maps(context, tfd, tid); - } -} - diff --git a/include/cutils/debugger.h b/include/cutils/debugger.h new file mode 100644 index 000000000..5a8e796a2 --- /dev/null +++ b/include/cutils/debugger.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef __CUTILS_DEBUGGER_H +#define __CUTILS_DEBUGGER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEBUGGER_SOCKET_NAME "android:debuggerd" + +typedef enum { + // dump a crash + DEBUGGER_ACTION_CRASH, + // dump a tombstone file + DEBUGGER_ACTION_DUMP_TOMBSTONE, + // dump a backtrace only back to the socket + DEBUGGER_ACTION_DUMP_BACKTRACE, +} debugger_action_t; + +/* message sent over the socket */ +typedef struct { + debugger_action_t action; + pid_t tid; +} debugger_msg_t; + +/* Dumps a process backtrace, registers, and stack to a tombstone file (requires root). + * Stores the tombstone path in the provided buffer. + * Returns 0 on success, -1 on error. + */ +int dump_tombstone(pid_t tid, char* pathbuf, size_t pathlen); + +/* Dumps a process backtrace only to the specified file (requires root). + * Returns 0 on success, -1 on error. + */ +int dump_backtrace_to_file(pid_t tid, int fd); + +#ifdef __cplusplus +} +#endif + +#endif /* __CUTILS_DEBUGGER_H */ diff --git a/libcutils/Android.mk b/libcutils/Android.mk index 0d4916534..5c227b6d7 100644 --- a/libcutils/Android.mk +++ b/libcutils/Android.mk @@ -116,7 +116,15 @@ libcutils_c_includes := bionic/libc/private include $(CLEAR_VARS) LOCAL_MODULE := libcutils -LOCAL_SRC_FILES := $(commonSources) ashmem-dev.c mq.c android_reboot.c partition_utils.c uevent.c qtaguid.c klog.c +LOCAL_SRC_FILES := $(commonSources) \ + android_reboot.c \ + ashmem-dev.c \ + debugger.c \ + klog.c \ + mq.c \ + partition_utils.c \ + qtaguid.c \ + uevent.c ifeq ($(TARGET_ARCH),arm) LOCAL_SRC_FILES += arch-arm/memset32.S diff --git a/libcutils/debugger.c b/libcutils/debugger.c new file mode 100644 index 000000000..942500604 --- /dev/null +++ b/libcutils/debugger.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012 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 + +int dump_tombstone(pid_t tid, char* pathbuf, size_t pathlen) { + int s = socket_local_client(DEBUGGER_SOCKET_NAME, + ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); + if (s < 0) { + return -1; + } + + debugger_msg_t msg; + msg.tid = tid; + msg.action = DEBUGGER_ACTION_DUMP_TOMBSTONE; + + int result = 0; + if (TEMP_FAILURE_RETRY(write(s, &msg, sizeof(msg))) != sizeof(msg)) { + result = -1; + } else { + char ack; + if (TEMP_FAILURE_RETRY(read(s, &ack, 1)) != 1) { + result = -1; + } else { + if (pathbuf && pathlen) { + ssize_t n = TEMP_FAILURE_RETRY(read(s, pathbuf, pathlen - 1)); + if (n <= 0) { + result = -1; + } else { + pathbuf[n] = '\0'; + } + } + } + } + TEMP_FAILURE_RETRY(close(s)); + return result; +} + +int dump_backtrace_to_file(pid_t tid, int fd) { + int s = socket_local_client(DEBUGGER_SOCKET_NAME, + ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); + if (s < 0) { + return -1; + } + + debugger_msg_t msg; + msg.tid = tid; + msg.action = DEBUGGER_ACTION_DUMP_BACKTRACE; + + int result = 0; + if (TEMP_FAILURE_RETRY(write(s, &msg, sizeof(msg))) != sizeof(msg)) { + result = -1; + } else { + char ack; + if (TEMP_FAILURE_RETRY(read(s, &ack, 1)) != 1) { + result = -1; + } else { + char buffer[4096]; + ssize_t n; + while ((n = TEMP_FAILURE_RETRY(read(s, buffer, sizeof(buffer)))) > 0) { + if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) { + result = -1; + break; + } + } + } + } + TEMP_FAILURE_RETRY(close(s)); + return result; +}