From 053b865412d1982ad1dc0e840898d82527deeb99 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Wed, 6 Jun 2012 16:25:03 -0700 Subject: [PATCH] Enhance native stack dumps. Provides a new mechanism for dumpstate (while running as root) to request that debuggerd dump the stacks of native processes that we care about in bug reports. In this mode, the backtrace is formatted to look similar to a Dalvik backtrace. Moved the tombstone generating code into a separate file to make it easier to maintain. Fixed a bug where sometimes the stack traces would be incomplete because we were not waiting for each thread to stop after issuing PTRACE_ATTACH, only the main thread. So sometimes we were missing traces for some threads. Refactored the logging code to prevent accidentally writing data to logcat when explicitly dumping a tombstone or backtrace from the console. Only root or system server can request to dump backtraces but only root can dump tombstones. Bug: 6615693 Change-Id: Ib3edcc16f9f3a687e414e3f2d250d9500566123b --- debuggerd/Android.mk | 10 +- debuggerd/arm/machine.c | 120 ++++--- debuggerd/backtrace.c | 149 ++++++++ debuggerd/backtrace.h | 31 ++ debuggerd/debuggerd.c | 621 +++++---------------------------- debuggerd/machine.h | 10 +- debuggerd/tombstone.c | 700 ++++++++++++++++++++++++++++++++++++++ debuggerd/tombstone.h | 31 ++ debuggerd/utility.c | 301 +++------------- debuggerd/utility.h | 44 +-- debuggerd/x86/machine.c | 49 +-- include/cutils/debugger.h | 58 ++++ libcutils/Android.mk | 10 +- libcutils/debugger.c | 87 +++++ 14 files changed, 1327 insertions(+), 894 deletions(-) create mode 100644 debuggerd/backtrace.c create mode 100644 debuggerd/backtrace.h create mode 100644 debuggerd/tombstone.c create mode 100644 debuggerd/tombstone.h create mode 100644 include/cutils/debugger.h create mode 100644 libcutils/debugger.c 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..2e92846da --- /dev/null +++ b/debuggerd/tombstone.c @@ -0,0 +1,700 @@ +/* + * 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. + */ + bool found = false; + 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) { + found = true; + 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 2ccf947e6..aabaf7468 100644 --- a/debuggerd/utility.c +++ b/debuggerd/utility.c @@ -15,295 +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. - */ - bool found = false; - 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) { - found = true; - 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; +}