aboutsummaryrefslogtreecommitdiffstats
path: root/debuggerd
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2012-06-08 15:27:01 -0700
committerJeff Brown <jeffbrown@google.com>2012-06-08 15:27:01 -0700
commit536b81afed42ba6be1f824cf90d9a1e17a913c9c (patch)
treeae87781b25cacf61c784d1881e421c9ed8c007b2 /debuggerd
parentd96544ff69601fe2990245e10e054480f0bd341f (diff)
parent053b865412d1982ad1dc0e840898d82527deeb99 (diff)
downloadsystem_core-536b81afed42ba6be1f824cf90d9a1e17a913c9c.tar.gz
system_core-536b81afed42ba6be1f824cf90d9a1e17a913c9c.tar.bz2
system_core-536b81afed42ba6be1f824cf90d9a1e17a913c9c.zip
resolved conflicts for merge of 053b8654 to jb-dev-plus-aosp
Change-Id: Idfef8c26b7a9e1a1a202e21dc5d34022bbaa92cc
Diffstat (limited to 'debuggerd')
-rw-r--r--debuggerd/Android.mk12
-rw-r--r--debuggerd/arm/machine.c120
-rw-r--r--debuggerd/backtrace.c149
-rw-r--r--debuggerd/backtrace.h31
-rw-r--r--debuggerd/debuggerd.c621
-rw-r--r--debuggerd/machine.h10
-rw-r--r--debuggerd/tombstone.c698
-rw-r--r--debuggerd/tombstone.h31
-rw-r--r--debuggerd/utility.c295
-rw-r--r--debuggerd/utility.h44
-rw-r--r--debuggerd/x86/machine.c49
11 files changed, 1170 insertions, 890 deletions
diff --git a/debuggerd/Android.mk b/debuggerd/Android.mk
index f1273637..2a516fbe 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_CFLAGS := -Wall -Werror -std=gnu99
+LOCAL_SRC_FILES:= \
+ backtrace.c \
+ debuggerd.c \
+ getevent.c \
+ tombstone.c \
+ utility.c \
+ $(TARGET_ARCH)/machine.c
+
+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 ca45c9b5..1c2e13ff 100644
--- a/debuggerd/arm/machine.c
+++ b/debuggerd/arm/machine.c
@@ -15,23 +15,17 @@
** limitations under the License.
*/
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
#include <stdio.h>
#include <errno.h>
-#include <signal.h>
-#include <pthread.h>
-#include <fcntl.h>
#include <sys/types.h>
-#include <dirent.h>
-
#include <sys/ptrace.h>
-#include <sys/wait.h>
-#include <sys/exec_elf.h>
-#include <sys/stat.h>
-#include <cutils/sockets.h>
-#include <cutils/properties.h>
+#include <corkscrew/ptrace.h>
-#include <linux/input.h>
#include <linux/user.h>
#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, &regs)) {
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", &REG_NAMES[reg * 2]);
- dump_memory(tfd, tid, addr, at_fault);
+ _LOG(log, false, "\nmemory near %.2s:\n", &REG_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 00000000..2149a673
--- /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 <stddef.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <limits.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/ptrace.h>
+
+#include <corkscrew/backtrace.h>
+
+#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 : "<unknown>", 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 00000000..ec7d20ff
--- /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 <stddef.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include <corkscrew/ptrace.h>
+
+/* 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 662683a6..8009631c 100644
--- a/debuggerd/debuggerd.c
+++ b/debuggerd/debuggerd.c
@@ -23,6 +23,7 @@
#include <fcntl.h>
#include <sys/types.h>
#include <dirent.h>
+#include <time.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
@@ -34,6 +35,7 @@
#include <cutils/logd.h>
#include <cutils/logger.h>
#include <cutils/properties.h>
+#include <cutils/debugger.h>
#include <corkscrew/backtrace.h>
@@ -41,432 +43,16 @@
#include <private/android_filesystem_config.h>
+#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: <priority:1><tag:N>\0<message:N>\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 [<tid>]\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: [<tid>]\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 6049b69d..1619dd3e 100644
--- a/debuggerd/machine.h
+++ b/debuggerd/machine.h
@@ -17,9 +17,15 @@
#ifndef _DEBUGGERD_MACHINE_H
#define _DEBUGGERD_MACHINE_H
-#include <corkscrew/backtrace.h>
+#include <stddef.h>
+#include <stdbool.h>
#include <sys/types.h>
-void dump_thread(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault);
+#include <corkscrew/ptrace.h>
+
+#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 00000000..16b49433
--- /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 <stddef.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <time.h>
+#include <sys/ptrace.h>
+#include <sys/stat.h>
+
+#include <private/android_filesystem_config.h>
+
+#include <cutils/logger.h>
+#include <cutils/properties.h>
+
+#include <corkscrew/demangle.h>
+#include <corkscrew/backtrace.h>
+
+#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: <priority:1><tag:N>\0<message:N>\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 00000000..edcd7b1e
--- /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 <stddef.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include <corkscrew/ptrace.h>
+
+/* 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 8eb52ba2..aabaf746 100644
--- a/debuggerd/utility.c
+++ b/debuggerd/utility.c
@@ -15,293 +15,80 @@
** limitations under the License.
*/
-#include <signal.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdio.h>
#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
#include <cutils/logd.h>
#include <sys/ptrace.h>
-#include <errno.h>
-#include <corkscrew/demangle.h>
+#include <sys/wait.h>
#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;
}
+ va_end(ap);
}
-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);
- }
- } 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 : "");
+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 {
- _LOG(tfd, 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, 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;
+ LOG("unexpected waitpid response: n=%d, status=%08x\n", n, status);
+ return -1;
}
- 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 (*total_sleep_time_usec > max_total_sleep_usec) {
+ LOG("timed out waiting for tid=%d to die\n", tid);
+ return -1;
}
- 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);
+ /* not ready yet */
+ XLOG("not ready yet\n");
+ usleep(sleep_time_usec);
+ *total_sleep_time_usec += sleep_time_usec;
}
}
-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 39f91cba..136f46d8 100644
--- a/debuggerd/utility.h
+++ b/debuggerd/utility.h
@@ -20,53 +20,35 @@
#include <stddef.h>
#include <stdbool.h>
-#include <sys/types.h>
-#include <corkscrew/backtrace.h>
+
+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 2729c7ed..01da5fef 100644
--- a/debuggerd/x86/machine.c
+++ b/debuggerd/x86/machine.c
@@ -15,59 +15,44 @@
** limitations under the License.
*/
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
#include <stdio.h>
#include <errno.h>
-#include <signal.h>
-#include <pthread.h>
-#include <fcntl.h>
#include <sys/types.h>
-#include <dirent.h>
-
#include <sys/ptrace.h>
-#include <sys/wait.h>
-#include <sys/exec_elf.h>
-#include <sys/stat.h>
-
-#include <cutils/sockets.h>
-#include <cutils/properties.h>
-#include <corkscrew/backtrace.h>
#include <corkscrew/ptrace.h>
-#include <linux/input.h>
+#include <linux/user.h>
-#include "../machine.h"
#include "../utility.h"
+#include "../machine.h"
+
+void dump_memory_and_code(const ptrace_context_t* context __attribute((unused)),
+ log_t* log, pid_t tid, bool at_fault) {
+}
-static void dump_registers(const ptrace_context_t* context __attribute((unused)),
- int tfd, 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);
- }
-}
-