diff options
Diffstat (limited to 'debuggerd')
-rw-r--r-- | debuggerd/Android.mk | 32 | ||||
-rw-r--r-- | debuggerd/arm/machine.c | 188 | ||||
-rw-r--r-- | debuggerd/arm/machine.cpp | 172 | ||||
-rw-r--r-- | debuggerd/backtrace.c | 150 | ||||
-rw-r--r-- | debuggerd/backtrace.cpp | 141 | ||||
-rw-r--r-- | debuggerd/backtrace.h | 16 | ||||
-rw-r--r-- | debuggerd/debuggerd.c | 539 | ||||
-rw-r--r-- | debuggerd/debuggerd.cpp | 514 | ||||
-rw-r--r-- | debuggerd/getevent.c | 219 | ||||
-rw-r--r-- | debuggerd/getevent.cpp | 222 | ||||
-rw-r--r-- | debuggerd/machine.h | 8 | ||||
-rw-r--r-- | debuggerd/mips/machine.c | 179 | ||||
-rw-r--r-- | debuggerd/mips/machine.cpp | 162 | ||||
-rw-r--r-- | debuggerd/tombstone.c | 838 | ||||
-rw-r--r-- | debuggerd/tombstone.cpp | 774 | ||||
-rw-r--r-- | debuggerd/tombstone.h | 2 | ||||
-rw-r--r-- | debuggerd/utility.c | 130 | ||||
-rw-r--r-- | debuggerd/utility.cpp | 129 | ||||
-rw-r--r-- | debuggerd/x86/machine.c | 58 | ||||
-rw-r--r-- | debuggerd/x86/machine.cpp | 45 |
20 files changed, 2191 insertions, 2327 deletions
diff --git a/debuggerd/Android.mk b/debuggerd/Android.mk index 8621e9c..422a86a 100644 --- a/debuggerd/Android.mk +++ b/debuggerd/Android.mk @@ -6,14 +6,20 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) 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 + backtrace.cpp \ + debuggerd.cpp \ + getevent.cpp \ + tombstone.cpp \ + utility.cpp \ + $(TARGET_ARCH)/machine.cpp \ + +LOCAL_CONLYFLAGS := -std=gnu99 +LOCAL_CPPFLAGS := -std=gnu++11 +LOCAL_CFLAGS := \ + -Wall \ + -Wno-array-bounds \ + -Werror \ + LOCAL_MODULE := debuggerd ifeq ($(ARCH_ARM_HAVE_VFP),true) @@ -24,11 +30,13 @@ LOCAL_CFLAGS += -DWITH_VFP_D32 endif # ARCH_ARM_HAVE_VFP_D32 LOCAL_SHARED_LIBRARIES := \ + libbacktrace \ + libc \ libcutils \ liblog \ - libc \ - libcorkscrew \ - libselinux + libselinux \ + +include external/stlport/libstlport.mk include $(BUILD_EXECUTABLE) @@ -38,7 +46,7 @@ LOCAL_SRC_FILES += $(TARGET_ARCH)/crashglue.S LOCAL_MODULE := crasher LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) LOCAL_MODULE_TAGS := optional -LOCAL_CFLAGS += -fstack-protector-all +LOCAL_CFLAGS += -fstack-protector-all -Wno-unused-parameter -Wno-free-nonheap-object #LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_SHARED_LIBRARIES := libcutils liblog libc include $(BUILD_EXECUTABLE) diff --git a/debuggerd/arm/machine.c b/debuggerd/arm/machine.c deleted file mode 100644 index 67e3028..0000000 --- a/debuggerd/arm/machine.c +++ /dev/null @@ -1,188 +0,0 @@ -/* system/debuggerd/debuggerd.c -** -** Copyright 2006, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#include <stddef.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <errno.h> -#include <sys/types.h> -#include <sys/ptrace.h> - -#include <corkscrew/ptrace.h> - -#include <linux/user.h> - -#include "../utility.h" -#include "../machine.h" - -/* enable to dump memory pointed to by every register */ -#define DUMP_MEMORY_FOR_ALL_REGISTERS 1 - -#ifdef WITH_VFP -#ifdef WITH_VFP_D32 -#define NUM_VFP_REGS 32 -#else -#define NUM_VFP_REGS 16 -#endif -#endif - -static void dump_memory(log_t* log, pid_t tid, uintptr_t addr, int scopeFlags) { - 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; - } - /* Dump more memory content for the crashing thread. */ - end = p + 256; - /* 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); - - /* Enable the following code blob to dump ASCII values */ -#if 0 - 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++ = '.'; - } - } -#endif - p += 4; - } - *asc_out = '\0'; - _LOG(log, scopeFlags, " %s %s\n", code_buffer, ascii_buffer); - } -} - -/* - * If configured to do so, dump memory around *all* registers - * for the crashing thread. - */ -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; - } - - int scopeFlags = at_fault ? SCOPE_AT_FAULT : 0; - - if (at_fault && DUMP_MEMORY_FOR_ALL_REGISTERS) { - static const char REG_NAMES[] = "r0r1r2r3r4r5r6r7r8r9slfpipsp"; - - for (int reg = 0; reg < 14; reg++) { - /* this may not be a valid way to access, but it'll do for now */ - uintptr_t addr = regs.uregs[reg]; - - /* - * Don't bother if it looks like a small int or ~= null, or if - * it's in the kernel area. - */ - if (addr < 4096 || addr >= 0xc0000000) { - continue; - } - - _LOG(log, scopeFlags | SCOPE_SENSITIVE, "\nmemory near %.2s:\n", ®_NAMES[reg * 2]); - dump_memory(log, tid, addr, scopeFlags | SCOPE_SENSITIVE); - } - } - - /* explicitly allow upload of code dump logging */ - _LOG(log, scopeFlags, "\ncode around pc:\n"); - dump_memory(log, tid, (uintptr_t)regs.ARM_pc, scopeFlags); - - if (regs.ARM_pc != regs.ARM_lr) { - _LOG(log, scopeFlags, "\ncode around lr:\n"); - dump_memory(log, tid, (uintptr_t)regs.ARM_lr, scopeFlags); - } -} - -void dump_registers(const ptrace_context_t* context __attribute((unused)), - log_t* log, pid_t tid, bool at_fault) -{ - struct pt_regs r; - int scopeFlags = at_fault ? SCOPE_AT_FAULT : 0; - - if(ptrace(PTRACE_GETREGS, tid, 0, &r)) { - _LOG(log, scopeFlags, "cannot get registers: %s\n", strerror(errno)); - return; - } - - _LOG(log, scopeFlags, " 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(log, scopeFlags, " 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(log, scopeFlags, " 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(log, scopeFlags, " 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); - -#ifdef WITH_VFP - struct user_vfp vfp_regs; - int i; - - if(ptrace(PTRACE_GETVFPREGS, tid, 0, &vfp_regs)) { - _LOG(log, scopeFlags, "cannot get registers: %s\n", strerror(errno)); - return; - } - - for (i = 0; i < NUM_VFP_REGS; i += 2) { - _LOG(log, scopeFlags, " d%-2d %016llx d%-2d %016llx\n", - i, vfp_regs.fpregs[i], i+1, vfp_regs.fpregs[i+1]); - } - _LOG(log, scopeFlags, " scr %08lx\n", vfp_regs.fpscr); -#endif -} diff --git a/debuggerd/arm/machine.cpp b/debuggerd/arm/machine.cpp new file mode 100644 index 0000000..3fba6db --- /dev/null +++ b/debuggerd/arm/machine.cpp @@ -0,0 +1,172 @@ +/* + * + * Copyright 2006, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ptrace.h> +#include <sys/types.h> +#include <sys/user.h> + +#include "../utility.h" +#include "../machine.h" + +// enable to dump memory pointed to by every register +#define DUMP_MEMORY_FOR_ALL_REGISTERS 1 + +#ifdef WITH_VFP +#ifdef WITH_VFP_D32 +#define NUM_VFP_REGS 32 +#else +#define NUM_VFP_REGS 16 +#endif +#endif + +static void dump_memory(log_t* log, pid_t tid, uintptr_t addr, int scope_flags) { + 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; + } + // Dump more memory content for the crashing thread. + end = p + 256; + // 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, reinterpret_cast<void*>(p), NULL); + sprintf(code_buffer + strlen(code_buffer), "%08lx ", data); + + // Enable the following code blob to dump ASCII values +#if 0 + 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++ = '.'; + } + } +#endif + p += 4; + } + *asc_out = '\0'; + _LOG(log, scope_flags, " %s %s\n", code_buffer, ascii_buffer); + } +} + +// If configured to do so, dump memory around *all* registers +// for the crashing thread. +void dump_memory_and_code(log_t* log, pid_t tid, int scope_flags) { + struct pt_regs regs; + if (ptrace(PTRACE_GETREGS, tid, 0, ®s)) { + return; + } + + if (IS_AT_FAULT(scope_flags) && DUMP_MEMORY_FOR_ALL_REGISTERS) { + static const char REG_NAMES[] = "r0r1r2r3r4r5r6r7r8r9slfpipsp"; + + for (int reg = 0; reg < 14; reg++) { + // this may not be a valid way to access, but it'll do for now + uintptr_t addr = regs.uregs[reg]; + + // Don't bother if it looks like a small int or ~= null, or if + // it's in the kernel area. + if (addr < 4096 || addr >= 0xc0000000) { + continue; + } + + _LOG(log, scope_flags | SCOPE_SENSITIVE, "\nmemory near %.2s:\n", ®_NAMES[reg * 2]); + dump_memory(log, tid, addr, scope_flags | SCOPE_SENSITIVE); + } + } + + // explicitly allow upload of code dump logging + _LOG(log, scope_flags, "\ncode around pc:\n"); + dump_memory(log, tid, static_cast<uintptr_t>(regs.ARM_pc), scope_flags); + + if (regs.ARM_pc != regs.ARM_lr) { + _LOG(log, scope_flags, "\ncode around lr:\n"); + dump_memory(log, tid, static_cast<uintptr_t>(regs.ARM_lr), scope_flags); + } +} + +void dump_registers(log_t* log, pid_t tid, int scope_flags) { + struct pt_regs r; + if (ptrace(PTRACE_GETREGS, tid, 0, &r)) { + _LOG(log, scope_flags, "cannot get registers: %s\n", strerror(errno)); + return; + } + + _LOG(log, scope_flags, " r0 %08x r1 %08x r2 %08x r3 %08x\n", + static_cast<uint32_t>(r.ARM_r0), static_cast<uint32_t>(r.ARM_r1), + static_cast<uint32_t>(r.ARM_r2), static_cast<uint32_t>(r.ARM_r3)); + _LOG(log, scope_flags, " r4 %08x r5 %08x r6 %08x r7 %08x\n", + static_cast<uint32_t>(r.ARM_r4), static_cast<uint32_t>(r.ARM_r5), + static_cast<uint32_t>(r.ARM_r6), static_cast<uint32_t>(r.ARM_r7)); + _LOG(log, scope_flags, " r8 %08x r9 %08x sl %08x fp %08x\n", + static_cast<uint32_t>(r.ARM_r8), static_cast<uint32_t>(r.ARM_r9), + static_cast<uint32_t>(r.ARM_r10), static_cast<uint32_t>(r.ARM_fp)); + _LOG(log, scope_flags, " ip %08x sp %08x lr %08x pc %08x cpsr %08x\n", + static_cast<uint32_t>(r.ARM_ip), static_cast<uint32_t>(r.ARM_sp), + static_cast<uint32_t>(r.ARM_lr), static_cast<uint32_t>(r.ARM_pc), + static_cast<uint32_t>(r.ARM_cpsr)); + +#ifdef WITH_VFP + struct user_vfp vfp_regs; + int i; + + if (ptrace(PTRACE_GETVFPREGS, tid, 0, &vfp_regs)) { + _LOG(log, scope_flags, "cannot get registers: %s\n", strerror(errno)); + return; + } + + for (i = 0; i < NUM_VFP_REGS; i += 2) { + _LOG(log, scope_flags, " d%-2d %016llx d%-2d %016llx\n", + i, vfp_regs.fpregs[i], i+1, vfp_regs.fpregs[i+1]); + } + _LOG(log, scope_flags, " scr %08lx\n", vfp_regs.fpscr); +#endif +} diff --git a/debuggerd/backtrace.c b/debuggerd/backtrace.c deleted file mode 100644 index f42f24c..0000000 --- a/debuggerd/backtrace.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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, SCOPE_AT_FAULT, "\n\n----- pid %d at %s -----\n", pid, timestr); - - if (procname) { - _LOG(log, SCOPE_AT_FAULT, "Cmd line: %s\n", procname); - } -} - -static void dump_process_footer(log_t* log, pid_t pid) { - _LOG(log, SCOPE_AT_FAULT, "\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, SCOPE_AT_FAULT, "\n\"%s\" sysTid=%d\n", - threadname ? threadname : "<unknown>", tid); - - if (!attached && ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) { - _LOG(log, SCOPE_AT_FAULT, "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, SCOPE_AT_FAULT, "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, SCOPE_AT_FAULT, " %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, int amfd, pid_t pid, pid_t tid, bool* detach_failed, - int* total_sleep_time_usec) { - log_t log; - log.tfd = fd; - log.amfd = amfd; - 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 != NULL) { - struct dirent* de = NULL; - while ((de = readdir(d)) != NULL) { - 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.cpp b/debuggerd/backtrace.cpp new file mode 100644 index 0000000..d388348 --- /dev/null +++ b/debuggerd/backtrace.cpp @@ -0,0 +1,141 @@ +/* + * 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 <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 <backtrace/Backtrace.h> +#include <UniquePtr.h> + +#include "backtrace.h" +#include "utility.h" + +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, SCOPE_AT_FAULT, "\n\n----- pid %d at %s -----\n", pid, timestr); + + if (procname) { + _LOG(log, SCOPE_AT_FAULT, "Cmd line: %s\n", procname); + } +} + +static void dump_process_footer(log_t* log, pid_t pid) { + _LOG(log, SCOPE_AT_FAULT, "\n----- end %d -----\n", pid); +} + +static void dump_thread( + log_t* log, pid_t tid, 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, SCOPE_AT_FAULT, "\n\"%s\" sysTid=%d\n", threadname ? threadname : "<unknown>", tid); + + if (!attached && ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) { + _LOG(log, SCOPE_AT_FAULT, "Could not attach to thread: %s\n", strerror(errno)); + return; + } + + wait_for_stop(tid, total_sleep_time_usec); + + UniquePtr<Backtrace> backtrace(Backtrace::Create(tid, BACKTRACE_CURRENT_THREAD)); + if (backtrace->Unwind(0)) { + dump_backtrace_to_log(backtrace.get(), log, SCOPE_AT_FAULT, " "); + } + + 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, int amfd, pid_t pid, pid_t tid, bool* detach_failed, + int* total_sleep_time_usec) { + log_t log; + log.tfd = fd; + log.amfd = amfd; + log.quiet = true; + + dump_process_header(&log, pid); + dump_thread(&log, tid, 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 != NULL) { + struct dirent* de = NULL; + while ((de = readdir(d)) != NULL) { + 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, false, detach_failed, total_sleep_time_usec); + } + closedir(d); + } + + dump_process_footer(&log, pid); +} + +void dump_backtrace_to_log(Backtrace* backtrace, log_t* log, + int scope_flags, const char* prefix) { + for (size_t i = 0; i < backtrace->NumFrames(); i++) { + _LOG(log, scope_flags, "%s%s\n", prefix, backtrace->FormatFrameData(i).c_str()); + } +} diff --git a/debuggerd/backtrace.h b/debuggerd/backtrace.h index c5c786a..2ec8afb 100644 --- a/debuggerd/backtrace.h +++ b/debuggerd/backtrace.h @@ -17,15 +17,19 @@ #ifndef _DEBUGGERD_BACKTRACE_H #define _DEBUGGERD_BACKTRACE_H -#include <stddef.h> -#include <stdbool.h> #include <sys/types.h> -#include <corkscrew/ptrace.h> +#include "utility.h" -/* Dumps a backtrace using a format similar to what Dalvik uses so that the result - * can be intermixed in a bug report. */ +class Backtrace; + +// 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, int amfd, pid_t pid, pid_t tid, bool* detach_failed, - int* total_sleep_time_usec); + int* total_sleep_time_usec); + +/* Dumps the backtrace in the backtrace data structure to the log. */ +void dump_backtrace_to_log(Backtrace* backtrace, log_t* log, + int scope_flags, const char* prefix); #endif // _DEBUGGERD_BACKTRACE_H diff --git a/debuggerd/debuggerd.c b/debuggerd/debuggerd.c deleted file mode 100644 index 756f7bb..0000000 --- a/debuggerd/debuggerd.c +++ /dev/null @@ -1,539 +0,0 @@ -/* system/debuggerd/debuggerd.c -** -** Copyright 2006, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#include <stdio.h> -#include <errno.h> -#include <signal.h> -#include <pthread.h> -#include <stdarg.h> -#include <fcntl.h> -#include <sys/types.h> -#include <dirent.h> -#include <time.h> - -#include <sys/ptrace.h> -#include <sys/wait.h> -#include <sys/exec_elf.h> -#include <sys/stat.h> -#include <sys/poll.h> - -#include <log/logd.h> -#include <log/logger.h> - -#include <cutils/sockets.h> -#include <cutils/properties.h> -#include <cutils/debugger.h> - -#include <corkscrew/backtrace.h> - -#include <linux/input.h> - -#include <private/android_filesystem_config.h> - -#include "backtrace.h" -#include "getevent.h" -#include "tombstone.h" -#include "utility.h" - -typedef struct { - debugger_action_t action; - pid_t pid, tid; - uid_t uid, gid; - uintptr_t abort_msg_address; -} debugger_request_t; - -static int -write_string(const char* file, const char* string) -{ - int len; - int fd; - ssize_t amt; - fd = open(file, O_RDWR); - len = strlen(string); - if (fd < 0) - return -errno; - amt = write(fd, string, len); - close(fd); - return amt >= 0 ? 0 : -errno; -} - -static -void init_debug_led(void) -{ - // trout leds - write_string("/sys/class/leds/red/brightness", "0"); - write_string("/sys/class/leds/green/brightness", "0"); - write_string("/sys/class/leds/blue/brightness", "0"); - write_string("/sys/class/leds/red/device/blink", "0"); - // sardine leds - write_string("/sys/class/leds/left/cadence", "0,0"); -} - -static -void enable_debug_led(void) -{ - // trout leds - write_string("/sys/class/leds/red/brightness", "255"); - // sardine leds - write_string("/sys/class/leds/left/cadence", "1,0"); -} - -static -void disable_debug_led(void) -{ - // trout leds - write_string("/sys/class/leds/red/brightness", "0"); - // sardine leds - write_string("/sys/class/leds/left/cadence", "0,0"); -} - -static void wait_for_user_action(pid_t pid) { - /* First log a helpful message */ - LOG( "********************************************************\n" - "* Process %d has been suspended while crashing. To\n" - "* attach gdbserver for a gdb connection on port 5039\n" - "* and start gdbclient:\n" - "*\n" - "* gdbclient app_process :5039 %d\n" - "*\n" - "* Wait for gdb to start, then press HOME or VOLUME DOWN key\n" - "* to let the process continue crashing.\n" - "********************************************************\n", - pid, pid); - - /* wait for HOME or VOLUME DOWN key */ - if (init_getevent() == 0) { - int ms = 1200 / 10; - int dit = 1; - int dah = 3*dit; - int _ = -dit; - int ___ = 3*_; - int _______ = 7*_; - const signed char codes[] = { - dit,_,dit,_,dit,___,dah,_,dah,_,dah,___,dit,_,dit,_,dit,_______ - }; - size_t s = 0; - struct input_event e; - bool done = false; - init_debug_led(); - enable_debug_led(); - do { - int timeout = abs((int)(codes[s])) * ms; - int res = get_event(&e, timeout); - if (res == 0) { - if (e.type == EV_KEY - && (e.code == KEY_HOME || e.code == KEY_VOLUMEDOWN) - && e.value == 0) { - done = true; - } - } else if (res == 1) { - if (++s >= sizeof(codes)/sizeof(*codes)) - s = 0; - if (codes[s] > 0) { - enable_debug_led(); - } else { - disable_debug_led(); - } - } - } while (!done); - uninit_getevent(); - } - - /* don't forget to turn debug led off */ - disable_debug_led(); - LOG("debuggerd resuming process %d", pid); -} - -static int get_process_info(pid_t tid, pid_t* out_pid, uid_t* out_uid, uid_t* out_gid) { - char path[64]; - snprintf(path, sizeof(path), "/proc/%d/status", tid); - - FILE* fp = fopen(path, "r"); - if (!fp) { - return -1; - } - - int fields = 0; - char line[1024]; - while (fgets(line, sizeof(line), fp)) { - size_t len = strlen(line); - if (len > 6 && !memcmp(line, "Tgid:\t", 6)) { - *out_pid = atoi(line + 6); - fields |= 1; - } else if (len > 5 && !memcmp(line, "Uid:\t", 5)) { - *out_uid = atoi(line + 5); - fields |= 2; - } else if (len > 5 && !memcmp(line, "Gid:\t", 5)) { - *out_gid = atoi(line + 5); - fields |= 4; - } - } - fclose(fp); - return fields == 7 ? 0 : -1; -} - -static 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); - if (status != 0) { - LOG("cannot get credentials\n"); - return -1; - } - - XLOG("reading tid\n"); - fcntl(fd, F_SETFL, O_NONBLOCK); - - struct pollfd pollfds[1]; - pollfds[0].fd = fd; - pollfds[0].events = POLLIN; - pollfds[0].revents = 0; - status = TEMP_FAILURE_RETRY(poll(pollfds, 1, 3000)); - if (status != 1) { - LOG("timed out reading tid (from pid=%d uid=%d)\n", cr.pid, cr.uid); - return -1; - } - - debugger_msg_t msg; - memset(&msg, 0, sizeof(msg)); - status = TEMP_FAILURE_RETRY(read(fd, &msg, sizeof(msg))); - if (status < 0) { - LOG("read failure? %s (pid=%d uid=%d)\n", - strerror(errno), cr.pid, cr.uid); - return -1; - } - if (status == sizeof(debugger_msg_t)) { - XLOG("crash request of size %d abort_msg_address=%#08x\n", status, msg.abort_msg_address); - } else { - LOG("invalid crash request of size %d (from pid=%d uid=%d)\n", - status, cr.pid, cr.uid); - return -1; - } - - 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; - out_request->abort_msg_address = msg.abort_msg_address; - - 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) { - LOG("tid %d does not exist. ignoring explicit dump request\n", - out_request->tid); - return -1; - } - } else { - /* No one else is allowed to dump arbitrary processes. */ - return -1; - } - return 0; -} - -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); - return debug_uid >= 0 && request->uid <= (uid_t)debug_uid; - } - return false; -} - -static void handle_request(int fd) { - XLOG("handle_request(%d)\n", fd); - - debugger_request_t request; - memset(&request, 0, sizeof(request)); - int status = read_request(fd, &request); - if (!status) { - XLOG("BOOM: pid=%d uid=%d gid=%d tid=%d\n", - request.pid, request.uid, request.gid, request.tid); - - /* At this point, the thread that made the request is blocked in - * a read() call. If the thread has crashed, then this gives us - * time to PTRACE_ATTACH to it before it has a chance to really fault. - * - * The PTRACE_ATTACH sends a SIGSTOP to the target process, but it - * won't necessarily have stopped by the time ptrace() returns. (We - * currently assume it does.) We write to the file descriptor to - * ensure that it can run as soon as we call PTRACE_CONT below. - * See details in bionic/libc/linker/debugger.c, in function - * debugger_signal_handler(). - */ - if (ptrace(PTRACE_ATTACH, request.tid, 0, 0)) { - LOG("ptrace attach failed: %s\n", strerror(errno)); - } else { - bool detach_failed = false; - bool attach_gdb = should_attach_gdb(&request); - 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.action == DEBUGGER_ACTION_CRASH) { - close(fd); - fd = -1; - } - - int total_sleep_time_usec = 0; - for (;;) { - int signal = wait_for_signal(request.tid, &total_sleep_time_usec); - if (signal < 0) { - break; - } - - switch (signal) { - case SIGSTOP: - if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { - XLOG("stopped -- dumping to tombstone\n"); - tombstone_path = engrave_tombstone(request.pid, request.tid, - signal, request.abort_msg_address, 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, -1, - request.pid, request.tid, &detach_failed, - &total_sleep_time_usec); - } else { - XLOG("stopped -- continuing\n"); - status = ptrace(PTRACE_CONT, request.tid, 0, 0); - if (status) { - LOG("ptrace continue failed: %s\n", strerror(errno)); - } - continue; /* loop again */ - } - break; - - case SIGILL: - case SIGABRT: - case SIGBUS: - case SIGFPE: - case SIGSEGV: - case SIGPIPE: -#ifdef SIGSTKFLT - case SIGSTKFLT: -#endif - { - XLOG("stopped -- fatal signal\n"); - /* - * Send a SIGSTOP to the process to make all of - * the non-signaled threads stop moving. Without - * this we get a lot of "ptrace detach failed: - * No such process". - */ - kill(request.pid, SIGSTOP); - /* 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, request.abort_msg_address, !attach_gdb, false, - &detach_failed, &total_sleep_time_usec); - break; - } - - default: - XLOG("stopped -- unexpected signal\n"); - LOG("process stopped due to unexpected signal %d\n", signal); - break; - } - break; - } - - if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { - if (tombstone_path) { - write(fd, tombstone_path, strlen(tombstone_path)); - } - close(fd); - fd = -1; - } - free(tombstone_path); - } - - XLOG("detaching\n"); - if (attach_gdb) { - /* stop the process so we can debug */ - kill(request.pid, SIGSTOP); - - /* detach so we can attach gdbserver */ - if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) { - LOG("ptrace detach from %d failed: %s\n", request.tid, strerror(errno)); - detach_failed = true; - } - - /* - * if debug.db.uid is set, its value indicates if we should wait - * for user action for the crashing process. - * in this case, we log a message and turn the debug LED on - * waiting for a gdb connection (for instance) - */ - wait_for_user_action(request.pid); - } else { - /* just detach */ - if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) { - LOG("ptrace detach from %d failed: %s\n", request.tid, strerror(errno)); - detach_failed = true; - } - } - - /* resume stopped process (so it can crash in peace). */ - kill(request.pid, SIGCONT); - - /* If we didn't successfully detach, we're still the parent, and the - * actual parent won't receive a death notification via wait(2). At this point - * there's not much we can do about that. */ - if (detach_failed) { - LOG("debuggerd committing suicide to free the zombie!\n"); - kill(getpid(), SIGKILL); - } - } - - } - if (fd >= 0) { - close(fd); - } -} - -static int do_server() { - int s; - struct sigaction act; - int logsocket = -1; - - /* - * debuggerd crashes can't be reported to debuggerd. Reset all of the - * crash handlers. - */ - signal(SIGILL, SIG_DFL); - signal(SIGABRT, SIG_DFL); - signal(SIGBUS, SIG_DFL); - signal(SIGFPE, SIG_DFL); - signal(SIGSEGV, SIG_DFL); -#ifdef SIGSTKFLT - signal(SIGSTKFLT, SIG_DFL); -#endif - - // Ignore failed writes to closed sockets - signal(SIGPIPE, SIG_IGN); - - logsocket = socket_local_client("logd", - ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_DGRAM); - if(logsocket < 0) { - logsocket = -1; - } else { - fcntl(logsocket, F_SETFD, FD_CLOEXEC); - } - - act.sa_handler = SIG_DFL; - sigemptyset(&act.sa_mask); - sigaddset(&act.sa_mask,SIGCHLD); - act.sa_flags = SA_NOCLDWAIT; - sigaction(SIGCHLD, &act, 0); - - s = socket_local_server(DEBUGGER_SOCKET_NAME, - ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); - if(s < 0) return 1; - fcntl(s, F_SETFD, FD_CLOEXEC); - - LOG("debuggerd: " __DATE__ " " __TIME__ "\n"); - - for(;;) { - struct sockaddr addr; - socklen_t alen; - int fd; - - alen = sizeof(addr); - XLOG("waiting for connection\n"); - fd = accept(s, &addr, &alen); - if(fd < 0) { - XLOG("accept failed: %s\n", strerror(errno)); - continue; - } - - fcntl(fd, F_SETFD, FD_CLOEXEC); - - handle_request(fd); - } - return 0; -} - -static int do_explicit_dump(pid_t tid, bool dump_backtrace) { - fprintf(stdout, "Sending request to dump task %d.\n", tid); - - 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]; - 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); - } - 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 == 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; - } - } - if (!have_tid) { - usage(); - return 1; - } - return do_explicit_dump(tid, dump_backtrace); -} diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp new file mode 100644 index 0000000..de8ba9d --- /dev/null +++ b/debuggerd/debuggerd.cpp @@ -0,0 +1,514 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <errno.h> +#include <signal.h> +#include <pthread.h> +#include <stdarg.h> +#include <fcntl.h> +#include <sys/types.h> +#include <dirent.h> +#include <time.h> + +#include <sys/ptrace.h> +#include <sys/wait.h> +#include <elf.h> +#include <sys/stat.h> +#include <sys/poll.h> + +#include <log/logd.h> +#include <log/logger.h> + +#include <cutils/sockets.h> +#include <cutils/properties.h> +#include <cutils/debugger.h> + +#include <linux/input.h> + +#include <private/android_filesystem_config.h> + +#include "backtrace.h" +#include "getevent.h" +#include "tombstone.h" +#include "utility.h" + +typedef struct { + debugger_action_t action; + pid_t pid, tid; + uid_t uid, gid; + uintptr_t abort_msg_address; +} debugger_request_t; + +static int write_string(const char* file, const char* string) { + int len; + int fd; + ssize_t amt; + fd = open(file, O_RDWR); + len = strlen(string); + if (fd < 0) + return -errno; + amt = write(fd, string, len); + close(fd); + return amt >= 0 ? 0 : -errno; +} + +static void init_debug_led() { + // trout leds + write_string("/sys/class/leds/red/brightness", "0"); + write_string("/sys/class/leds/green/brightness", "0"); + write_string("/sys/class/leds/blue/brightness", "0"); + write_string("/sys/class/leds/red/device/blink", "0"); + // sardine leds + write_string("/sys/class/leds/left/cadence", "0,0"); +} + +static void enable_debug_led() { + // trout leds + write_string("/sys/class/leds/red/brightness", "255"); + // sardine leds + write_string("/sys/class/leds/left/cadence", "1,0"); +} + +static void disable_debug_led() { + // trout leds + write_string("/sys/class/leds/red/brightness", "0"); + // sardine leds + write_string("/sys/class/leds/left/cadence", "0,0"); +} + +static void wait_for_user_action(pid_t pid) { + // First log a helpful message + LOG( "********************************************************\n" + "* Process %d has been suspended while crashing. To\n" + "* attach gdbserver for a gdb connection on port 5039\n" + "* and start gdbclient:\n" + "*\n" + "* gdbclient app_process :5039 %d\n" + "*\n" + "* Wait for gdb to start, then press HOME or VOLUME DOWN key\n" + "* to let the process continue crashing.\n" + "********************************************************\n", + pid, pid); + + // wait for HOME or VOLUME DOWN key + if (init_getevent() == 0) { + int ms = 1200 / 10; + int dit = 1; + int dah = 3*dit; + int _ = -dit; + int ___ = 3*_; + int _______ = 7*_; + const int codes[] = { + dit,_,dit,_,dit,___,dah,_,dah,_,dah,___,dit,_,dit,_,dit,_______ + }; + size_t s = 0; + struct input_event e; + bool done = false; + init_debug_led(); + enable_debug_led(); + do { + int timeout = abs(codes[s]) * ms; + int res = get_event(&e, timeout); + if (res == 0) { + if (e.type == EV_KEY + && (e.code == KEY_HOME || e.code == KEY_VOLUMEDOWN) + && e.value == 0) { + done = true; + } + } else if (res == 1) { + if (++s >= sizeof(codes)/sizeof(*codes)) + s = 0; + if (codes[s] > 0) { + enable_debug_led(); + } else { + disable_debug_led(); + } + } + } while (!done); + uninit_getevent(); + } + + // don't forget to turn debug led off + disable_debug_led(); + LOG("debuggerd resuming process %d", pid); +} + +static int get_process_info(pid_t tid, pid_t* out_pid, uid_t* out_uid, uid_t* out_gid) { + char path[64]; + snprintf(path, sizeof(path), "/proc/%d/status", tid); + + FILE* fp = fopen(path, "r"); + if (!fp) { + return -1; + } + + int fields = 0; + char line[1024]; + while (fgets(line, sizeof(line), fp)) { + size_t len = strlen(line); + if (len > 6 && !memcmp(line, "Tgid:\t", 6)) { + *out_pid = atoi(line + 6); + fields |= 1; + } else if (len > 5 && !memcmp(line, "Uid:\t", 5)) { + *out_uid = atoi(line + 5); + fields |= 2; + } else if (len > 5 && !memcmp(line, "Gid:\t", 5)) { + *out_gid = atoi(line + 5); + fields |= 4; + } + } + fclose(fp); + return fields == 7 ? 0 : -1; +} + +static 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); + if (status != 0) { + LOG("cannot get credentials\n"); + return -1; + } + + XLOG("reading tid\n"); + fcntl(fd, F_SETFL, O_NONBLOCK); + + struct pollfd pollfds[1]; + pollfds[0].fd = fd; + pollfds[0].events = POLLIN; + pollfds[0].revents = 0; + status = TEMP_FAILURE_RETRY(poll(pollfds, 1, 3000)); + if (status != 1) { + LOG("timed out reading tid (from pid=%d uid=%d)\n", cr.pid, cr.uid); + return -1; + } + + debugger_msg_t msg; + memset(&msg, 0, sizeof(msg)); + status = TEMP_FAILURE_RETRY(read(fd, &msg, sizeof(msg))); + if (status < 0) { + LOG("read failure? %s (pid=%d uid=%d)\n", strerror(errno), cr.pid, cr.uid); + return -1; + } + if (status == sizeof(debugger_msg_t)) { + XLOG("crash request of size %d abort_msg_address=%#08x\n", status, msg.abort_msg_address); + } else { + LOG("invalid crash request of size %d (from pid=%d uid=%d)\n", status, cr.pid, cr.uid); + return -1; + } + + 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; + out_request->abort_msg_address = msg.abort_msg_address; + + 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) { + LOG("tid %d does not exist. ignoring explicit dump request\n", out_request->tid); + return -1; + } + } else { + // No one else is allowed to dump arbitrary processes. + return -1; + } + return 0; +} + +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); + return debug_uid >= 0 && request->uid <= (uid_t)debug_uid; + } + return false; +} + +static void handle_request(int fd) { + XLOG("handle_request(%d)\n", fd); + + debugger_request_t request; + memset(&request, 0, sizeof(request)); + int status = read_request(fd, &request); + if (!status) { + XLOG("BOOM: pid=%d uid=%d gid=%d tid=%d\n", + request.pid, request.uid, request.gid, request.tid); + + // At this point, the thread that made the request is blocked in + // a read() call. If the thread has crashed, then this gives us + // time to PTRACE_ATTACH to it before it has a chance to really fault. + // + // The PTRACE_ATTACH sends a SIGSTOP to the target process, but it + // won't necessarily have stopped by the time ptrace() returns. (We + // currently assume it does.) We write to the file descriptor to + // ensure that it can run as soon as we call PTRACE_CONT below. + // See details in bionic/libc/linker/debugger.c, in function + // debugger_signal_handler(). + if (ptrace(PTRACE_ATTACH, request.tid, 0, 0)) { + LOG("ptrace attach failed: %s\n", strerror(errno)); + } else { + bool detach_failed = false; + bool attach_gdb = should_attach_gdb(&request); + 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.action == DEBUGGER_ACTION_CRASH) { + close(fd); + fd = -1; + } + + int total_sleep_time_usec = 0; + for (;;) { + int signal = wait_for_signal(request.tid, &total_sleep_time_usec); + if (signal < 0) { + break; + } + + switch (signal) { + case SIGSTOP: + if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { + XLOG("stopped -- dumping to tombstone\n"); + tombstone_path = engrave_tombstone( + request.pid, request.tid, signal, request.abort_msg_address, 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, -1, request.pid, request.tid, &detach_failed, + &total_sleep_time_usec); + } else { + XLOG("stopped -- continuing\n"); + status = ptrace(PTRACE_CONT, request.tid, 0, 0); + if (status) { + LOG("ptrace continue failed: %s\n", strerror(errno)); + } + continue; // loop again + } + break; + + case SIGILL: + case SIGABRT: + case SIGBUS: + case SIGFPE: + case SIGSEGV: + case SIGPIPE: +#ifdef SIGSTKFLT + case SIGSTKFLT: +#endif + XLOG("stopped -- fatal signal\n"); + // Send a SIGSTOP to the process to make all of + // the non-signaled threads stop moving. Without + // this we get a lot of "ptrace detach failed: + // No such process". + kill(request.pid, SIGSTOP); + // 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, request.abort_msg_address, !attach_gdb, + false, &detach_failed, &total_sleep_time_usec); + break; + + default: + XLOG("stopped -- unexpected signal\n"); + LOG("process stopped due to unexpected signal %d\n", signal); + break; + } + break; + } + + if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { + if (tombstone_path) { + write(fd, tombstone_path, strlen(tombstone_path)); + } + close(fd); + fd = -1; + } + free(tombstone_path); + } + + XLOG("detaching\n"); + if (attach_gdb) { + // stop the process so we can debug + kill(request.pid, SIGSTOP); + + // detach so we can attach gdbserver + if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) { + LOG("ptrace detach from %d failed: %s\n", request.tid, strerror(errno)); + detach_failed = true; + } + + // if debug.db.uid is set, its value indicates if we should wait + // for user action for the crashing process. + // in this case, we log a message and turn the debug LED on + // waiting for a gdb connection (for instance) + wait_for_user_action(request.pid); + } else { + // just detach + if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) { + LOG("ptrace detach from %d failed: %s\n", request.tid, strerror(errno)); + detach_failed = true; + } + } + + // resume stopped process (so it can crash in peace). + kill(request.pid, SIGCONT); + + // If we didn't successfully detach, we're still the parent, and the + // actual parent won't receive a death notification via wait(2). At this point + // there's not much we can do about that. + if (detach_failed) { + LOG("debuggerd committing suicide to free the zombie!\n"); + kill(getpid(), SIGKILL); + } + } + + } + if (fd >= 0) { + close(fd); + } +} + +static int do_server() { + int s; + struct sigaction act; + int logsocket = -1; + + // debuggerd crashes can't be reported to debuggerd. Reset all of the + // crash handlers. + signal(SIGILL, SIG_DFL); + signal(SIGABRT, SIG_DFL); + signal(SIGBUS, SIG_DFL); + signal(SIGFPE, SIG_DFL); + signal(SIGSEGV, SIG_DFL); +#ifdef SIGSTKFLT + signal(SIGSTKFLT, SIG_DFL); +#endif + + // Ignore failed writes to closed sockets + signal(SIGPIPE, SIG_IGN); + + logsocket = socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_DGRAM); + if (logsocket < 0) { + logsocket = -1; + } else { + fcntl(logsocket, F_SETFD, FD_CLOEXEC); + } + + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask,SIGCHLD); + act.sa_flags = SA_NOCLDWAIT; + sigaction(SIGCHLD, &act, 0); + + s = socket_local_server(DEBUGGER_SOCKET_NAME, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); + if (s < 0) + return 1; + fcntl(s, F_SETFD, FD_CLOEXEC); + + LOG("debuggerd: " __DATE__ " " __TIME__ "\n"); + + for (;;) { + struct sockaddr addr; + socklen_t alen; + int fd; + + alen = sizeof(addr); + XLOG("waiting for connection\n"); + fd = accept(s, &addr, &alen); + if (fd < 0) { + XLOG("accept failed: %s\n", strerror(errno)); + continue; + } + + fcntl(fd, F_SETFD, FD_CLOEXEC); + + handle_request(fd); + } + return 0; +} + +static int do_explicit_dump(pid_t tid, bool dump_backtrace) { + fprintf(stdout, "Sending request to dump task %d.\n", tid); + + 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]; + 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); + } + 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 == 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; + } + } + if (!have_tid) { + usage(); + return 1; + } + return do_explicit_dump(tid, dump_backtrace); +} diff --git a/debuggerd/getevent.c b/debuggerd/getevent.c deleted file mode 100644 index ebd070c..0000000 --- a/debuggerd/getevent.c +++ /dev/null @@ -1,219 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <stdint.h> -#include <dirent.h> -#include <fcntl.h> -#include <sys/ioctl.h> -#include <sys/inotify.h> -#include <sys/limits.h> -#include <sys/poll.h> -#include <linux/input.h> -#include <errno.h> -#include <cutils/log.h> - -static struct pollfd *ufds; -static char **device_names; -static int nfds; - -static int open_device(const char *device) -{ - int version; - int fd; - struct pollfd *new_ufds; - char **new_device_names; - char name[80]; - char location[80]; - char idstr[80]; - struct input_id id; - - fd = open(device, O_RDWR); - if(fd < 0) { - return -1; - } - - if(ioctl(fd, EVIOCGVERSION, &version)) { - return -1; - } - if(ioctl(fd, EVIOCGID, &id)) { - return -1; - } - name[sizeof(name) - 1] = '\0'; - location[sizeof(location) - 1] = '\0'; - idstr[sizeof(idstr) - 1] = '\0'; - if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) { - //fprintf(stderr, "could not get device name for %s, %s\n", device, strerror(errno)); - name[0] = '\0'; - } - if(ioctl(fd, EVIOCGPHYS(sizeof(location) - 1), &location) < 1) { - //fprintf(stderr, "could not get location for %s, %s\n", device, strerror(errno)); - location[0] = '\0'; - } - if(ioctl(fd, EVIOCGUNIQ(sizeof(idstr) - 1), &idstr) < 1) { - //fprintf(stderr, "could not get idstring for %s, %s\n", device, strerror(errno)); - idstr[0] = '\0'; - } - - new_ufds = realloc(ufds, sizeof(ufds[0]) * (nfds + 1)); - if(new_ufds == NULL) { - fprintf(stderr, "out of memory\n"); - return -1; - } - ufds = new_ufds; - new_device_names = realloc(device_names, sizeof(device_names[0]) * (nfds + 1)); - if(new_device_names == NULL) { - fprintf(stderr, "out of memory\n"); - return -1; - } - device_names = new_device_names; - ufds[nfds].fd = fd; - ufds[nfds].events = POLLIN; - device_names[nfds] = strdup(device); - nfds++; - - return 0; -} - -int close_device(const char *device) -{ - int i; - for(i = 1; i < nfds; i++) { - if(strcmp(device_names[i], device) == 0) { - int count = nfds - i - 1; - free(device_names[i]); - memmove(device_names + i, device_names + i + 1, sizeof(device_names[0]) * count); - memmove(ufds + i, ufds + i + 1, sizeof(ufds[0]) * count); - nfds--; - return 0; - } - } - return -1; -} - -static int read_notify(const char *dirname, int nfd) -{ - int res; - char devname[PATH_MAX]; - char *filename; - char event_buf[512]; - int event_size; - int event_pos = 0; - struct inotify_event *event; - - res = read(nfd, event_buf, sizeof(event_buf)); - if(res < (int)sizeof(*event)) { - if(errno == EINTR) - return 0; - fprintf(stderr, "could not get event, %s\n", strerror(errno)); - return 1; - } - //printf("got %d bytes of event information\n", res); - - strcpy(devname, dirname); - filename = devname + strlen(devname); - *filename++ = '/'; - - while(res >= (int)sizeof(*event)) { - event = (struct inotify_event *)(event_buf + event_pos); - //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : ""); - if(event->len) { - strcpy(filename, event->name); - if(event->mask & IN_CREATE) { - open_device(devname); - } - else { - close_device(devname); - } - } - event_size = sizeof(*event) + event->len; - res -= event_size; - event_pos += event_size; - } - return 0; -} - -static int scan_dir(const char *dirname) -{ - char devname[PATH_MAX]; - char *filename; - DIR *dir; - struct dirent *de; - dir = opendir(dirname); - if(dir == NULL) - return -1; - strcpy(devname, dirname); - filename = devname + strlen(devname); - *filename++ = '/'; - while((de = readdir(dir))) { - if(de->d_name[0] == '.' && - (de->d_name[1] == '\0' || - (de->d_name[1] == '.' && de->d_name[2] == '\0'))) - continue; - strcpy(filename, de->d_name); - open_device(devname); - } - closedir(dir); - return 0; -} - -int init_getevent() -{ - int res; - const char *device_path = "/dev/input"; - - nfds = 1; - ufds = calloc(1, sizeof(ufds[0])); - ufds[0].fd = inotify_init(); - ufds[0].events = POLLIN; - - res = inotify_add_watch(ufds[0].fd, device_path, IN_DELETE | IN_CREATE); - if(res < 0) { - return 1; - } - res = scan_dir(device_path); - if(res < 0) { - return 1; - } - return 0; -} - -void uninit_getevent() -{ - int i; - for(i = 0; i < nfds; i++) { - close(ufds[i].fd); - } - free(ufds); - ufds = 0; - nfds = 0; -} - -int get_event(struct input_event* event, int timeout) -{ - int res; - int i; - int pollres; - const char *device_path = "/dev/input"; - while(1) { - pollres = poll(ufds, nfds, timeout); - if (pollres == 0) { - return 1; - } - if(ufds[0].revents & POLLIN) { - read_notify(device_path, ufds[0].fd); - } - for(i = 1; i < nfds; i++) { - if(ufds[i].revents) { - if(ufds[i].revents & POLLIN) { - res = read(ufds[i].fd, event, sizeof(*event)); - if(res < (int)sizeof(event)) { - fprintf(stderr, "could not get event\n"); - return -1; - } - return 0; - } - } - } - } - return 0; -} diff --git a/debuggerd/getevent.cpp b/debuggerd/getevent.cpp new file mode 100644 index 0000000..751c4fb --- /dev/null +++ b/debuggerd/getevent.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2014 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <dirent.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/inotify.h> +#include <sys/limits.h> +#include <sys/poll.h> +#include <linux/input.h> +#include <errno.h> +#include <cutils/log.h> + +static struct pollfd* ufds; +static char** device_names; +static int nfds; + +static int open_device(const char* device) { + int version; + int fd; + struct pollfd* new_ufds; + char** new_device_names; + char name[80]; + char location[80]; + char idstr[80]; + struct input_id id; + + fd = open(device, O_RDWR); + if (fd < 0) { + return -1; + } + + if (ioctl(fd, EVIOCGVERSION, &version)) { + return -1; + } + if (ioctl(fd, EVIOCGID, &id)) { + return -1; + } + name[sizeof(name) - 1] = '\0'; + location[sizeof(location) - 1] = '\0'; + idstr[sizeof(idstr) - 1] = '\0'; + if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) { + name[0] = '\0'; + } + if (ioctl(fd, EVIOCGPHYS(sizeof(location) - 1), &location) < 1) { + location[0] = '\0'; + } + if (ioctl(fd, EVIOCGUNIQ(sizeof(idstr) - 1), &idstr) < 1) { + idstr[0] = '\0'; + } + + new_ufds = reinterpret_cast<pollfd*>(realloc(ufds, sizeof(ufds[0]) * (nfds + 1))); + if (new_ufds == NULL) { + fprintf(stderr, "out of memory\n"); + return -1; + } + ufds = new_ufds; + new_device_names = reinterpret_cast<char**>(realloc( + device_names, sizeof(device_names[0]) * (nfds + 1))); + if (new_device_names == NULL) { + fprintf(stderr, "out of memory\n"); + return -1; + } + device_names = new_device_names; + ufds[nfds].fd = fd; + ufds[nfds].events = POLLIN; + device_names[nfds] = strdup(device); + nfds++; + + return 0; +} + +int close_device(const char* device) { + int i; + for (i = 1; i < nfds; i++) { + if (strcmp(device_names[i], device) == 0) { + int count = nfds - i - 1; + free(device_names[i]); + memmove(device_names + i, device_names + i + 1, sizeof(device_names[0]) * count); + memmove(ufds + i, ufds + i + 1, sizeof(ufds[0]) * count); + nfds--; + return 0; + } + } + return -1; +} + +static int read_notify(const char* dirname, int nfd) { + int res; + char devname[PATH_MAX]; + char* filename; + char event_buf[512]; + int event_size; + int event_pos = 0; + struct inotify_event *event; + + res = read(nfd, event_buf, sizeof(event_buf)); + if (res < (int)sizeof(*event)) { + if (errno == EINTR) + return 0; + fprintf(stderr, "could not get event, %s\n", strerror(errno)); + return 1; + } + + strcpy(devname, dirname); + filename = devname + strlen(devname); + *filename++ = '/'; + + while (res >= (int)sizeof(*event)) { + event = reinterpret_cast<struct inotify_event*>(event_buf + event_pos); + if (event->len) { + strcpy(filename, event->name); + if (event->mask & IN_CREATE) { + open_device(devname); + } else { + close_device(devname); + } + } + event_size = sizeof(*event) + event->len; + res -= event_size; + event_pos += event_size; + } + return 0; +} + +static int scan_dir(const char* dirname) { + char devname[PATH_MAX]; + char* filename; + DIR* dir; + struct dirent* de; + dir = opendir(dirname); + if (dir == NULL) + return -1; + strcpy(devname, dirname); + filename = devname + strlen(devname); + *filename++ = '/'; + while ((de = readdir(dir))) { + if ((de->d_name[0] == '.' && de->d_name[1] == '\0') || + (de->d_name[1] == '.' && de->d_name[2] == '\0')) + continue; + strcpy(filename, de->d_name); + open_device(devname); + } + closedir(dir); + return 0; +} + +int init_getevent() { + int res; + const char* device_path = "/dev/input"; + + nfds = 1; + ufds = reinterpret_cast<pollfd*>(calloc(1, sizeof(ufds[0]))); + ufds[0].fd = inotify_init(); + ufds[0].events = POLLIN; + + res = inotify_add_watch(ufds[0].fd, device_path, IN_DELETE | IN_CREATE); + if (res < 0) { + return 1; + } + res = scan_dir(device_path); + if (res < 0) { + return 1; + } + return 0; +} + +void uninit_getevent() { + int i; + for (i = 0; i < nfds; i++) { + close(ufds[i].fd); + } + free(ufds); + ufds = 0; + nfds = 0; +} + +int get_event(struct input_event* event, int timeout) { + int res; + int i; + int pollres; + const char* device_path = "/dev/input"; + while (1) { + pollres = poll(ufds, nfds, timeout); + if (pollres == 0) { + return 1; + } + if (ufds[0].revents & POLLIN) { + read_notify(device_path, ufds[0].fd); + } + for (i = 1; i < nfds; i++) { + if (ufds[i].revents) { + if (ufds[i].revents & POLLIN) { + res = read(ufds[i].fd, event, sizeof(*event)); + if (res < static_cast<int>(sizeof(event))) { + fprintf(stderr, "could not get event\n"); + return -1; + } + return 0; + } + } + } + } + return 0; +} diff --git a/debuggerd/machine.h b/debuggerd/machine.h index 1619dd3..2f1e201 100644 --- a/debuggerd/machine.h +++ b/debuggerd/machine.h @@ -17,15 +17,11 @@ #ifndef _DEBUGGERD_MACHINE_H #define _DEBUGGERD_MACHINE_H -#include <stddef.h> -#include <stdbool.h> #include <sys/types.h> -#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); +void dump_memory_and_code(log_t* log, pid_t tid, int scope_flags); +void dump_registers(log_t* log, pid_t tid, int scope_flags); #endif // _DEBUGGERD_MACHINE_H diff --git a/debuggerd/mips/machine.c b/debuggerd/mips/machine.c deleted file mode 100644 index 65fdf02..0000000 --- a/debuggerd/mips/machine.c +++ /dev/null @@ -1,179 +0,0 @@ -/* system/debuggerd/debuggerd.c -** -** Copyright 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 <errno.h> -#include <sys/types.h> -#include <sys/ptrace.h> - -#include <corkscrew/ptrace.h> - -#include <linux/user.h> - -#include "../utility.h" -#include "../machine.h" - -/* enable to dump memory pointed to by every register */ -#define DUMP_MEMORY_FOR_ALL_REGISTERS 1 - -#define R(x) ((unsigned int)(x)) - -static void dump_memory(log_t* log, pid_t tid, uintptr_t addr, int scopeFlags) { - 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, scopeFlags, " %s %s\n", code_buffer, ascii_buffer); - } -} - -/* - * If configured to do so, dump memory around *all* registers - * for the crashing thread. - */ -void dump_memory_and_code(const ptrace_context_t* context __attribute((unused)), - log_t* log, pid_t tid, bool at_fault) { - pt_regs_mips_t r; - if(ptrace(PTRACE_GETREGS, tid, 0, &r)) { - return; - } - - int scopeFlags = at_fault ? SCOPE_AT_FAULT : 0; - if (at_fault && DUMP_MEMORY_FOR_ALL_REGISTERS) { - static const char REG_NAMES[] = "$0atv0v1a0a1a2a3t0t1t2t3t4t5t6t7s0s1s2s3s4s5s6s7t8t9k0k1gpsps8ra"; - - for (int reg = 0; reg < 32; reg++) { - /* skip uninteresting registers */ - if (reg == 0 /* $0 */ - || reg == 26 /* $k0 */ - || reg == 27 /* $k1 */ - || reg == 31 /* $ra (done below) */ - ) - continue; - - uintptr_t addr = R(r.regs[reg]); - - /* - * Don't bother if it looks like a small int or ~= null, or if - * it's in the kernel area. - */ - if (addr < 4096 || addr >= 0x80000000) { - continue; - } - - _LOG(log, scopeFlags | SCOPE_SENSITIVE, "\nmemory near %.2s:\n", ®_NAMES[reg * 2]); - dump_memory(log, tid, addr, scopeFlags | SCOPE_SENSITIVE); - } - } - - unsigned int pc = R(r.cp0_epc); - unsigned int ra = R(r.regs[31]); - - _LOG(log, scopeFlags, "\ncode around pc:\n"); - dump_memory(log, tid, (uintptr_t)pc, scopeFlags); - - if (pc != ra) { - _LOG(log, scopeFlags, "\ncode around ra:\n"); - dump_memory(log, tid, (uintptr_t)ra, scopeFlags); - } -} - -void dump_registers(const ptrace_context_t* context __attribute((unused)), - log_t* log, pid_t tid, bool at_fault) -{ - pt_regs_mips_t r; - int scopeFlags = at_fault ? SCOPE_AT_FAULT : 0; - - if(ptrace(PTRACE_GETREGS, tid, 0, &r)) { - _LOG(log, scopeFlags, "cannot get registers: %s\n", strerror(errno)); - return; - } - - _LOG(log, scopeFlags, " zr %08x at %08x v0 %08x v1 %08x\n", - R(r.regs[0]), R(r.regs[1]), R(r.regs[2]), R(r.regs[3])); - _LOG(log, scopeFlags, " a0 %08x a1 %08x a2 %08x a3 %08x\n", - R(r.regs[4]), R(r.regs[5]), R(r.regs[6]), R(r.regs[7])); - _LOG(log, scopeFlags, " t0 %08x t1 %08x t2 %08x t3 %08x\n", - R(r.regs[8]), R(r.regs[9]), R(r.regs[10]), R(r.regs[11])); - _LOG(log, scopeFlags, " t4 %08x t5 %08x t6 %08x t7 %08x\n", - R(r.regs[12]), R(r.regs[13]), R(r.regs[14]), R(r.regs[15])); - _LOG(log, scopeFlags, " s0 %08x s1 %08x s2 %08x s3 %08x\n", - R(r.regs[16]), R(r.regs[17]), R(r.regs[18]), R(r.regs[19])); - _LOG(log, scopeFlags, " s4 %08x s5 %08x s6 %08x s7 %08x\n", - R(r.regs[20]), R(r.regs[21]), R(r.regs[22]), R(r.regs[23])); - _LOG(log, scopeFlags, " t8 %08x t9 %08x k0 %08x k1 %08x\n", - R(r.regs[24]), R(r.regs[25]), R(r.regs[26]), R(r.regs[27])); - _LOG(log, scopeFlags, " gp %08x sp %08x s8 %08x ra %08x\n", - R(r.regs[28]), R(r.regs[29]), R(r.regs[30]), R(r.regs[31])); - _LOG(log, scopeFlags, " hi %08x lo %08x bva %08x epc %08x\n", - R(r.hi), R(r.lo), R(r.cp0_badvaddr), R(r.cp0_epc)); -} diff --git a/debuggerd/mips/machine.cpp b/debuggerd/mips/machine.cpp new file mode 100644 index 0000000..d1a7f2d --- /dev/null +++ b/debuggerd/mips/machine.cpp @@ -0,0 +1,162 @@ +/* + * Copyright 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 <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/ptrace.h> + +#include <corkscrew/ptrace.h> + +#include <sys/user.h> + +#include "../utility.h" +#include "../machine.h" + +// enable to dump memory pointed to by every register +#define DUMP_MEMORY_FOR_ALL_REGISTERS 1 + +#define R(x) (static_cast<unsigned int>(x)) + +static void dump_memory(log_t* log, pid_t tid, uintptr_t addr, int scope_flags) { + 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, scope_flags, " %s %s\n", code_buffer, ascii_buffer); + } +} + +// If configured to do so, dump memory around *all* registers +// for the crashing thread. +void dump_memory_and_code(log_t* log, pid_t tid, int scope_flags) { + pt_regs_mips_t r; + if (ptrace(PTRACE_GETREGS, tid, 0, &r)) { + return; + } + + if (IS_AT_FAULT(scope_flags) && DUMP_MEMORY_FOR_ALL_REGISTERS) { + static const char REG_NAMES[] = "$0atv0v1a0a1a2a3t0t1t2t3t4t5t6t7s0s1s2s3s4s5s6s7t8t9k0k1gpsps8ra"; + + for (int reg = 0; reg < 32; reg++) { + // skip uninteresting registers + if (reg == 0 // $0 + || reg == 26 // $k0 + || reg == 27 // $k1 + || reg == 31 // $ra (done below) + ) + continue; + + uintptr_t addr = R(r.regs[reg]); + + // Don't bother if it looks like a small int or ~= null, or if + // it's in the kernel area. + if (addr < 4096 || addr >= 0x80000000) { + continue; + } + + _LOG(log, scope_flags | SCOPE_SENSITIVE, "\nmemory near %.2s:\n", ®_NAMES[reg * 2]); + dump_memory(log, tid, addr, scope_flags | SCOPE_SENSITIVE); + } + } + + unsigned int pc = R(r.cp0_epc); + unsigned int ra = R(r.regs[31]); + + _LOG(log, scope_flags, "\ncode around pc:\n"); + dump_memory(log, tid, (uintptr_t)pc, scope_flags); + + if (pc != ra) { + _LOG(log, scope_flags, "\ncode around ra:\n"); + dump_memory(log, tid, (uintptr_t)ra, scope_flags); + } +} + +void dump_registers(log_t* log, pid_t tid, int scope_flags) { + pt_regs_mips_t r; + if(ptrace(PTRACE_GETREGS, tid, 0, &r)) { + _LOG(log, scope_flags, "cannot get registers: %s\n", strerror(errno)); + return; + } + + _LOG(log, scope_flags, " zr %08x at %08x v0 %08x v1 %08x\n", + R(r.regs[0]), R(r.regs[1]), R(r.regs[2]), R(r.regs[3])); + _LOG(log, scope_flags, " a0 %08x a1 %08x a2 %08x a3 %08x\n", + R(r.regs[4]), R(r.regs[5]), R(r.regs[6]), R(r.regs[7])); + _LOG(log, scope_flags, " t0 %08x t1 %08x t2 %08x t3 %08x\n", + R(r.regs[8]), R(r.regs[9]), R(r.regs[10]), R(r.regs[11])); + _LOG(log, scope_flags, " t4 %08x t5 %08x t6 %08x t7 %08x\n", + R(r.regs[12]), R(r.regs[13]), R(r.regs[14]), R(r.regs[15])); + _LOG(log, scope_flags, " s0 %08x s1 %08x s2 %08x s3 %08x\n", + R(r.regs[16]), R(r.regs[17]), R(r.regs[18]), R(r.regs[19])); + _LOG(log, scope_flags, " s4 %08x s5 %08x s6 %08x s7 %08x\n", + R(r.regs[20]), R(r.regs[21]), R(r.regs[22]), R(r.regs[23])); + _LOG(log, scope_flags, " t8 %08x t9 %08x k0 %08x k1 %08x\n", + R(r.regs[24]), R(r.regs[25]), R(r.regs[26]), R(r.regs[27])); + _LOG(log, scope_flags, " gp %08x sp %08x s8 %08x ra %08x\n", + R(r.regs[28]), R(r.regs[29]), R(r.regs[30]), R(r.regs[31])); + _LOG(log, scope_flags, " hi %08x lo %08x bva %08x epc %08x\n", + R(r.hi), R(r.lo), R(r.cp0_badvaddr), R(r.cp0_epc)); +} diff --git a/debuggerd/tombstone.c b/debuggerd/tombstone.c deleted file mode 100644 index 7009a8e..0000000 --- a/debuggerd/tombstone.c +++ /dev/null @@ -1,838 +0,0 @@ -/* - * 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 <log/logger.h> -#include <cutils/properties.h> - -#include <corkscrew/demangle.h> -#include <corkscrew/backtrace.h> - -#include <sys/socket.h> -#include <linux/un.h> - -#include <selinux/android.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" - -/* Must match the path defined in NativeCrashListener.java */ -#define NCRASH_SOCKET_PATH "/data/system/ndebugsocket" - -#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"; -#ifdef SIGSTKFLT - case SIGSTKFLT: return "SIGSTKFLT"; -#endif - case SIGSTOP: return "SIGSTOP"; - default: return "?"; - } -} - -static const char *get_sigcode(int signo, int code) -{ - // Try the signal-specific codes... - 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; - case SIGTRAP: - switch (code) { - case TRAP_BRKPT: return "TRAP_BRKPT"; - case TRAP_TRACE: return "TRAP_TRACE"; - } - break; - } - // Then the other codes... - switch (code) { - case SI_USER: return "SI_USER"; -#if defined(SI_KERNEL) - case SI_KERNEL: return "SI_KERNEL"; -#endif - case SI_QUEUE: return "SI_QUEUE"; - case SI_TIMER: return "SI_TIMER"; - case SI_MESGQ: return "SI_MESGQ"; - case SI_ASYNCIO: return "SI_ASYNCIO"; -#if defined(SI_SIGIO) - case SI_SIGIO: return "SI_SIGIO"; -#endif -#if defined(SI_TKILL) - case SI_TKILL: return "SI_TKILL"; -#endif - } - // Then give up... - return "?"; -} - -static void dump_revision_info(log_t* log) -{ - char revision[PROPERTY_VALUE_MAX]; - - property_get("ro.revision", revision, "unknown"); - - _LOG(log, SCOPE_AT_FAULT, "Revision: '%s'\n", revision); -} - -static void dump_build_info(log_t* log) -{ - char fingerprint[PROPERTY_VALUE_MAX]; - - property_get("ro.build.fingerprint", fingerprint, "unknown"); - - _LOG(log, SCOPE_AT_FAULT, "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, SCOPE_AT_FAULT, "cannot get siginfo: %s\n", strerror(errno)); - } else if (signal_has_address(sig)) { - _LOG(log, SCOPE_AT_FAULT, "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, SCOPE_AT_FAULT, "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, SCOPE_AT_FAULT, "pid: %d, tid: %d, name: %s >>> %s <<<\n", pid, tid, - threadname ? threadname : "UNKNOWN", - procname ? procname : "UNKNOWN"); - } else { - _LOG(log, 0, "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) { - int scopeFlags = at_fault ? SCOPE_AT_FAULT : 0; - _LOG(log, scopeFlags, "\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, scopeFlags, " %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, - int scopeFlags, 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, scopeFlags, " #%02d %08x %08x %s (%s+%u)\n", - label, *sp, stack_content, mi ? mi->name : "", symbol_name, offset); - } else { - _LOG(log, scopeFlags, " #%02d %08x %08x %s (%s)\n", - label, *sp, stack_content, mi ? mi->name : "", symbol_name); - } - } else { - if (offset) { - _LOG(log, scopeFlags, " %08x %08x %s (%s+%u)\n", - *sp, stack_content, mi ? mi->name : "", symbol_name, offset); - } else { - _LOG(log, scopeFlags, " %08x %08x %s (%s)\n", - *sp, stack_content, mi ? mi->name : "", symbol_name); - } - } - free(demangled_name); - } else { - if (!i && label >= 0) { - _LOG(log, scopeFlags, " #%02d %08x %08x %s\n", - label, *sp, stack_content, mi ? mi->name : ""); - } else { - _LOG(log, scopeFlags, " %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; - } - - int scopeFlags = SCOPE_SENSITIVE | (at_fault ? SCOPE_AT_FAULT : 0); - _LOG(log, scopeFlags, "\nstack:\n"); - - // Dump a few words before the first frame. - uintptr_t sp = backtrace[first].stack_top - STACK_WORDS * sizeof(uint32_t); - dump_stack_segment(context, log, tid, scopeFlags, &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, scopeFlags, " ........ ........\n"); - sp = frame->stack_top; - } - if (i - first == 3) { - scopeFlags &= (~SCOPE_AT_FAULT); - } - if (i == last) { - dump_stack_segment(context, log, tid, scopeFlags, &sp, STACK_WORDS, i); - if (sp < frame->stack_top + frame->stack_size) { - _LOG(log, scopeFlags, " ........ ........\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, scopeFlags, &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_map(log_t* log, map_info_t* m, const char* what, int scopeFlags) { - if (m != NULL) { - _LOG(log, scopeFlags, " %08x-%08x %c%c%c %s\n", m->start, m->end, - m->is_readable ? 'r' : '-', - m->is_writable ? 'w' : '-', - m->is_executable ? 'x' : '-', - m->name); - } else { - _LOG(log, scopeFlags, " (no %s)\n", what); - } -} - -static void dump_nearby_maps(const ptrace_context_t* context, log_t* log, pid_t tid, bool at_fault) { - int scopeFlags = SCOPE_SENSITIVE | (at_fault ? SCOPE_AT_FAULT : 0); - siginfo_t si; - memset(&si, 0, sizeof(si)); - if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si)) { - _LOG(log, scopeFlags, "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, scopeFlags, "\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). - */ - dump_map(log, next, "map below", scopeFlags); - dump_map(log, map, "map for address", scopeFlags); - dump_map(log, prev, "map above", scopeFlags); -} - -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, at_fault); - } -} - -/* 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* de; - while ((de = readdir(d)) != NULL) { - /* 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, 0, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\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, 0, "Error while reading log: %s\n", - strerror(errno)); - break; - } - } else if (actual == 0) { - _LOG(log, 0, "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, 0, "--------- %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, 0, "%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, 0, "%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); -} - -static void dump_abort_message(log_t* log, pid_t tid, uintptr_t address) { - if (address == 0) { - return; - } - - address += sizeof(size_t); // Skip the buffer length. - - char msg[512]; - memset(msg, 0, sizeof(msg)); - char* p = &msg[0]; - while (p < &msg[sizeof(msg)]) { - uint32_t data; - if (!try_get_word_ptrace(tid, address, &data)) { - break; - } - address += sizeof(uint32_t); - - if ((*p++ = (data >> 0) & 0xff) == 0) { - break; - } - if ((*p++ = (data >> 8) & 0xff) == 0) { - break; - } - if ((*p++ = (data >> 16) & 0xff) == 0) { - break; - } - if ((*p++ = (data >> 24) & 0xff) == 0) { - break; - } - } - msg[sizeof(msg) - 1] = '\0'; - - _LOG(log, SCOPE_AT_FAULT, "Abort message: '%s'\n", msg); -} - -/* - * 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, uintptr_t abort_msg_address, - 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'); - - if (log->amfd >= 0) { - /* - * Activity Manager protocol: binary 32-bit network-byte-order ints for the - * pid and signal number, followed by the raw text of the dump, culminating - * in a zero byte that marks end-of-data. - */ - uint32_t datum = htonl(pid); - TEMP_FAILURE_RETRY( write(log->amfd, &datum, 4) ); - datum = htonl(signal); - TEMP_FAILURE_RETRY( write(log->amfd, &datum, 4) ); - } - - _LOG(log, SCOPE_AT_FAULT, - "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); - dump_build_info(log); - dump_revision_info(log); - dump_thread_info(log, pid, tid, true); - if (signal) { - dump_fault_addr(log, tid, signal); - } - dump_abort_message(log, tid, abort_msg_address); - - 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); - } - - /* send EOD to the Activity Manager, then wait for its ack to avoid racing ahead - * and killing the target out from under it */ - if (log->amfd >= 0) { - uint8_t eodMarker = 0; - TEMP_FAILURE_RETRY( write(log->amfd, &eodMarker, 1) ); - /* 3 sec timeout reading the ack; we're fine if that happens */ - TEMP_FAILURE_RETRY( read(log->amfd, &eodMarker, 1) ); - } - - 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); -} - -static int activity_manager_connect() { - int amfd = socket(PF_UNIX, SOCK_STREAM, 0); - if (amfd >= 0) { - struct sockaddr_un address; - int err; - - memset(&address, 0, sizeof(address)); - address.sun_family = AF_UNIX; - strncpy(address.sun_path, NCRASH_SOCKET_PATH, sizeof(address.sun_path)); - err = TEMP_FAILURE_RETRY( connect(amfd, (struct sockaddr*) &address, sizeof(address)) ); - if (!err) { - struct timeval tv; - memset(&tv, 0, sizeof(tv)); - tv.tv_sec = 1; // tight leash - err = setsockopt(amfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - if (!err) { - tv.tv_sec = 3; // 3 seconds on handshake read - err = setsockopt(amfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - } - } - if (err) { - close(amfd); - amfd = -1; - } - } - - return amfd; -} - -char* engrave_tombstone(pid_t pid, pid_t tid, int signal, uintptr_t abort_msg_address, - 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); - - if (selinux_android_restorecon(TOMBSTONE_DIR) == -1) { - *detach_failed = false; - return NULL; - } - - int fd; - char* path = find_and_open_tombstone(&fd); - if (!path) { - *detach_failed = false; - return NULL; - } - - log_t log; - log.tfd = fd; - log.amfd = activity_manager_connect(); - log.quiet = quiet; - *detach_failed = dump_crash(&log, pid, tid, signal, abort_msg_address, dump_sibling_threads, - total_sleep_time_usec); - - close(log.amfd); - close(fd); - return path; -} diff --git a/debuggerd/tombstone.cpp b/debuggerd/tombstone.cpp new file mode 100644 index 0000000..c9a2376 --- /dev/null +++ b/debuggerd/tombstone.cpp @@ -0,0 +1,774 @@ +/* + * Copyright (C) 2012-2014 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 <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 <inttypes.h> + +#include <private/android_filesystem_config.h> + +#include <log/log.h> +#include <log/logger.h> +#include <cutils/properties.h> + +#include <backtrace/Backtrace.h> +#include <backtrace/BacktraceMap.h> + +#include <sys/socket.h> +#include <linux/un.h> + +#include <selinux/android.h> + +#include <UniquePtr.h> + +#include "machine.h" +#include "tombstone.h" +#include "backtrace.h" + +#define STACK_WORDS 16 + +#define MAX_TOMBSTONES 10 +#define TOMBSTONE_DIR "/data/tombstones" + +// Must match the path defined in NativeCrashListener.java +#define NCRASH_SOCKET_PATH "/data/system/ndebugsocket" + +#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"; +#ifdef SIGSTKFLT + case SIGSTKFLT: return "SIGSTKFLT"; +#endif + case SIGSTOP: return "SIGSTOP"; + default: return "?"; + } +} + +static const char* get_sigcode(int signo, int code) { + // Try the signal-specific codes... + 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; + case SIGTRAP: + switch (code) { + case TRAP_BRKPT: return "TRAP_BRKPT"; + case TRAP_TRACE: return "TRAP_TRACE"; + } + break; + } + // Then the other codes... + switch (code) { + case SI_USER: return "SI_USER"; +#if defined(SI_KERNEL) + case SI_KERNEL: return "SI_KERNEL"; +#endif + case SI_QUEUE: return "SI_QUEUE"; + case SI_TIMER: return "SI_TIMER"; + case SI_MESGQ: return "SI_MESGQ"; + case SI_ASYNCIO: return "SI_ASYNCIO"; +#if defined(SI_SIGIO) + case SI_SIGIO: return "SI_SIGIO"; +#endif +#if defined(SI_TKILL) + case SI_TKILL: return "SI_TKILL"; +#endif + } + // Then give up... + return "?"; +} + +static void dump_revision_info(log_t* log) { + char revision[PROPERTY_VALUE_MAX]; + + property_get("ro.revision", revision, "unknown"); + + _LOG(log, SCOPE_AT_FAULT, "Revision: '%s'\n", revision); +} + +static void dump_build_info(log_t* log) { + char fingerprint[PROPERTY_VALUE_MAX]; + + property_get("ro.build.fingerprint", fingerprint, "unknown"); + + _LOG(log, SCOPE_AT_FAULT, "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, SCOPE_AT_FAULT, "cannot get siginfo: %s\n", strerror(errno)); + } else if (signal_has_address(sig)) { + _LOG(log, SCOPE_AT_FAULT, "signal %d (%s), code %d (%s), fault addr %0*" PRIxPTR "\n", + sig, get_signame(sig), si.si_code, get_sigcode(sig, si.si_code), + sizeof(uintptr_t)*2, reinterpret_cast<uintptr_t>(si.si_addr)); + } else { + _LOG(log, SCOPE_AT_FAULT, "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, int scope_flags) { + 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 (IS_AT_FAULT(scope_flags)) { + 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, SCOPE_AT_FAULT, "pid: %d, tid: %d, name: %s >>> %s <<<\n", pid, tid, + threadname ? threadname : "UNKNOWN", procname ? procname : "UNKNOWN"); + } else { + _LOG(log, 0, "pid: %d, tid: %d, name: %s\n", pid, tid, threadname ? threadname : "UNKNOWN"); + } +} + +static void dump_stack_segment( + Backtrace* backtrace, log_t* log, int scope_flags, uintptr_t* sp, size_t words, int label) { + for (size_t i = 0; i < words; i++) { + uint32_t stack_content; + if (!backtrace->ReadWord(*sp, &stack_content)) { + break; + } + + const backtrace_map_t* map = backtrace->FindMap(stack_content); + const char* map_name; + if (!map) { + map_name = ""; + } else { + map_name = map->name.c_str(); + } + uintptr_t offset = 0; + std::string func_name(backtrace->GetFunctionName(stack_content, &offset)); + if (!func_name.empty()) { + if (!i && label >= 0) { + if (offset) { + _LOG(log, scope_flags, " #%02d %08x %08x %s (%s+%u)\n", + label, *sp, stack_content, map_name, func_name.c_str(), offset); + } else { + _LOG(log, scope_flags, " #%02d %08x %08x %s (%s)\n", + label, *sp, stack_content, map_name, func_name.c_str()); + } + } else { + if (offset) { + _LOG(log, scope_flags, " %08x %08x %s (%s+%u)\n", + *sp, stack_content, map_name, func_name.c_str(), offset); + } else { + _LOG(log, scope_flags, " %08x %08x %s (%s)\n", + *sp, stack_content, map_name, func_name.c_str()); + } + } + } else { + if (!i && label >= 0) { + _LOG(log, scope_flags, " #%02d %08x %08x %s\n", + label, *sp, stack_content, map_name); + } else { + _LOG(log, scope_flags, " %08x %08x %s\n", + *sp, stack_content, map_name); + } + } + + *sp += sizeof(uint32_t); + } +} + +static void dump_stack(Backtrace* backtrace, log_t* log, int scope_flags) { + size_t first = 0, last; + for (size_t i = 0; i < backtrace->NumFrames(); i++) { + const backtrace_frame_data_t* frame = backtrace->GetFrame(i); + if (frame->sp) { + if (!first) { + first = i+1; + } + last = i; + } + } + if (!first) { + return; + } + first--; + + scope_flags |= SCOPE_SENSITIVE; + + // Dump a few words before the first frame. + uintptr_t sp = backtrace->GetFrame(first)->sp - STACK_WORDS * sizeof(uint32_t); + dump_stack_segment(backtrace, log, scope_flags, &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_data_t* frame = backtrace->GetFrame(i); + if (sp != frame->sp) { + _LOG(log, scope_flags, " ........ ........\n"); + sp = frame->sp; + } + if (i - first == 3) { + scope_flags &= (~SCOPE_AT_FAULT); + } + if (i == last) { + dump_stack_segment(backtrace, log, scope_flags, &sp, STACK_WORDS, i); + if (sp < frame->sp + frame->stack_size) { + _LOG(log, scope_flags, " ........ ........\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(backtrace, log, scope_flags, &sp, words, i); + } + } +} + +static void dump_backtrace_and_stack(Backtrace* backtrace, log_t* log, int scope_flags) { + if (backtrace->NumFrames()) { + _LOG(log, scope_flags, "\nbacktrace:\n"); + dump_backtrace_to_log(backtrace, log, scope_flags, " "); + + _LOG(log, scope_flags, "\nstack:\n"); + dump_stack(backtrace, log, scope_flags); + } +} + +static void dump_map(log_t* log, const backtrace_map_t* map, const char* what, int scope_flags) { + if (map != NULL) { + _LOG(log, scope_flags, " %08x-%08x %c%c%c %s\n", map->start, map->end, + (map->flags & PROT_READ) ? 'r' : '-', (map->flags & PROT_WRITE) ? 'w' : '-', + (map->flags & PROT_EXEC) ? 'x' : '-', map->name.c_str()); + } else { + _LOG(log, scope_flags, " (no %s)\n", what); + } +} + +static void dump_nearby_maps(BacktraceMap* map, log_t* log, pid_t tid, int scope_flags) { + scope_flags |= SCOPE_SENSITIVE; + siginfo_t si; + memset(&si, 0, sizeof(si)); + if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si)) { + _LOG(log, scope_flags, "cannot get siginfo for %d: %s\n", tid, strerror(errno)); + return; + } + if (!signal_has_address(si.si_signo)) { + return; + } + + uintptr_t addr = reinterpret_cast<uintptr_t>(si.si_addr); + addr &= ~0xfff; // round to 4K page boundary + if (addr == 0) { // null-pointer deref + return; + } + + _LOG(log, scope_flags, "\nmemory map around fault addr %" PRIxPTR ":\n", + reinterpret_cast<uintptr_t>(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. + const backtrace_map_t* cur_map = NULL; + const backtrace_map_t* next_map = NULL; + const backtrace_map_t* prev_map = NULL; + for (BacktraceMap::const_iterator it = map->begin(); it != map->end(); ++it) { + if (addr >= it->start && addr < it->end) { + cur_map = &*it; + if (it != map->begin()) { + prev_map = &*(it-1); + } + if (++it != map->end()) { + next_map = &*it; + } + break; + } + } + + // Show the map address in ascending order (like /proc/pid/maps). + dump_map(log, prev_map, "map below", scope_flags); + dump_map(log, cur_map, "map for address", scope_flags); + dump_map(log, next_map, "map above", scope_flags); +} + +static void dump_thread( + Backtrace* backtrace, log_t* log, int scope_flags, int* total_sleep_time_usec) { + wait_for_stop(backtrace->Tid(), total_sleep_time_usec); + + dump_registers(log, backtrace->Tid(), scope_flags); + dump_backtrace_and_stack(backtrace, log, scope_flags); + if (IS_AT_FAULT(scope_flags)) { + dump_memory_and_code(log, backtrace->Tid(), scope_flags); + dump_nearby_maps(backtrace->GetMap(), log, backtrace->Tid(), scope_flags); + } +} + +// Return true if some thread is not detached cleanly +static bool dump_sibling_thread_report( + log_t* log, pid_t pid, pid_t tid, int* total_sleep_time_usec, BacktraceMap* map) { + char task_path[64]; + snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid); + + DIR* d = opendir(task_path); + // Bail early if the task directory cannot be opened + 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) { + // 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, 0, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); + dump_thread_info(log, pid, new_tid, 0); + + UniquePtr<Backtrace> backtrace(Backtrace::Create(pid, new_tid, map)); + if (backtrace->Unwind(0)) { + dump_thread(backtrace.get(), log, 0, 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 "tail" is set, we only print the last few lines. +static void dump_log_file(log_t* log, pid_t pid, const char* filename, + unsigned int tail) { + bool first = true; + struct logger_list *logger_list; + + logger_list = android_logger_list_open( + android_name_to_log_id(filename), O_RDONLY | O_NONBLOCK, tail, pid); + + if (!logger_list) { + XLOG("Unable to open %s: %s\n", filename, strerror(errno)); + return; + } + + struct log_msg log_entry; + + while (true) { + ssize_t actual = android_logger_list_read(logger_list, &log_entry); + + if (actual < 0) { + if (actual == -EINTR) { + // interrupted by signal, retry + continue; + } else if (actual == -EAGAIN) { + // non-blocking EOF; we're done + break; + } else { + _LOG(log, 0, "Error while reading log: %s\n", + strerror(-actual)); + break; + } + } else if (actual == 0) { + _LOG(log, 0, "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_v1; + + if (entry->pid != static_cast<int32_t>(pid)) { + // wrong pid, ignore + continue; + } + + if (first) { + _LOG(log, 0, "--------- %slog %s\n", + tail ? "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). + static const char* kPrioChars = "!.VDIWEFS"; + unsigned hdr_size = log_entry.entry.hdr_size; + if (!hdr_size) { + hdr_size = sizeof(log_entry.entry_v1); + } + char* msg = (char *)log_entry.buf + hdr_size; + unsigned char prio = msg[0]; + char* tag = msg + 1; + msg = tag + strlen(tag) + 1; + + // consume any trailing newlines + char* nl = msg + strlen(msg) - 1; + while (nl >= msg && *nl == '\n') { + *nl-- = '\0'; + } + + char prioChar = (prio < strlen(kPrioChars) ? kPrioChars[prio] : '?'); + + char timeBuf[32]; + time_t sec = static_cast<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); + + // Look for line breaks ('\n') and display each text line + // on a separate line, prefixed with the header, like logcat does. + do { + nl = strchr(msg, '\n'); + if (nl) { + *nl = '\0'; + ++nl; + } + + _LOG(log, 0, "%s.%03d %5d %5d %c %-8s: %s\n", + timeBuf, entry->nsec / 1000000, entry->pid, entry->tid, + prioChar, tag, msg); + + } while ((msg = nl)); + } + + android_logger_list_free(logger_list); +} + +// 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, unsigned tail) { + dump_log_file(log, pid, "system", tail); + dump_log_file(log, pid, "main", tail); +} + +static void dump_abort_message(Backtrace* backtrace, log_t* log, uintptr_t address) { + if (address == 0) { + return; + } + + address += sizeof(size_t); // Skip the buffer length. + + char msg[512]; + memset(msg, 0, sizeof(msg)); + char* p = &msg[0]; + while (p < &msg[sizeof(msg)]) { + uint32_t data; + if (!backtrace->ReadWord(address, &data)) { + break; + } + address += sizeof(uint32_t); + + if ((*p++ = (data >> 0) & 0xff) == 0) { + break; + } + if ((*p++ = (data >> 8) & 0xff) == 0) { + break; + } + if ((*p++ = (data >> 16) & 0xff) == 0) { + break; + } + if ((*p++ = (data >> 24) & 0xff) == 0) { + break; + } + } + msg[sizeof(msg) - 1] = '\0'; + + _LOG(log, SCOPE_AT_FAULT, "Abort message: '%s'\n", msg); +} + +// 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, uintptr_t abort_msg_address, + 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'); + + if (log->amfd >= 0) { + // Activity Manager protocol: binary 32-bit network-byte-order ints for the + // pid and signal number, followed by the raw text of the dump, culminating + // in a zero byte that marks end-of-data. + uint32_t datum = htonl(pid); + TEMP_FAILURE_RETRY( write(log->amfd, &datum, 4) ); + datum = htonl(signal); + TEMP_FAILURE_RETRY( write(log->amfd, &datum, 4) ); + } + + _LOG(log, SCOPE_AT_FAULT, + "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); + dump_build_info(log); + dump_revision_info(log); + dump_thread_info(log, pid, tid, SCOPE_AT_FAULT); + if (signal) { + dump_fault_addr(log, tid, signal); + } + + UniquePtr<BacktraceMap> map(BacktraceMap::Create(pid)); + UniquePtr<Backtrace> backtrace(Backtrace::Create(pid, tid, map.get())); + if (backtrace->Unwind(0)) { + dump_abort_message(backtrace.get(), log, abort_msg_address); + dump_thread(backtrace.get(), log, SCOPE_AT_FAULT, total_sleep_time_usec); + } + + if (want_logs) { + dump_logs(log, pid, 5); + } + + bool detach_failed = false; + if (dump_sibling_threads) { + detach_failed = dump_sibling_thread_report(log, pid, tid, total_sleep_time_usec, map.get()); + } + + if (want_logs) { + dump_logs(log, pid, 0); + } + + // send EOD to the Activity Manager, then wait for its ack to avoid racing ahead + // and killing the target out from under it + if (log->amfd >= 0) { + uint8_t eodMarker = 0; + TEMP_FAILURE_RETRY( write(log->amfd, &eodMarker, 1) ); + // 3 sec timeout reading the ack; we're fine if that happens + TEMP_FAILURE_RETRY( read(log->amfd, &eodMarker, 1) ); + } + + 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); +} + +static int activity_manager_connect() { + int amfd = socket(PF_UNIX, SOCK_STREAM, 0); + if (amfd >= 0) { + struct sockaddr_un address; + int err; + + memset(&address, 0, sizeof(address)); + address.sun_family = AF_UNIX; + strncpy(address.sun_path, NCRASH_SOCKET_PATH, sizeof(address.sun_path)); + err = TEMP_FAILURE_RETRY(connect( + amfd, reinterpret_cast<struct sockaddr*>(&address), sizeof(address))); + if (!err) { + struct timeval tv; + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = 1; // tight leash + err = setsockopt(amfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + if (!err) { + tv.tv_sec = 3; // 3 seconds on handshake read + err = setsockopt(amfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + } + } + if (err) { + close(amfd); + amfd = -1; + } + } + + return amfd; +} + +char* engrave_tombstone( + pid_t pid, pid_t tid, int signal, uintptr_t abort_msg_address, 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 = -1; + char* path = NULL; + if (selinux_android_restorecon(TOMBSTONE_DIR) == 0) { + path = find_and_open_tombstone(&fd); + } else { + LOG("Failed to restore security context, not writing tombstone.\n"); + } + + if (fd < 0 && quiet) { + LOG("Skipping tombstone write, nothing to do.\n"); + *detach_failed = false; + return NULL; + } + + log_t log; + log.tfd = fd; + // Preserve amfd since it can be modified through the calls below without + // being closed. + int amfd = activity_manager_connect(); + log.amfd = amfd; + log.quiet = quiet; + *detach_failed = dump_crash( + &log, pid, tid, signal, abort_msg_address, dump_sibling_threads, total_sleep_time_usec); + + // Either of these file descriptors can be -1, any error is ignored. + close(amfd); + close(fd); + + return path; +} diff --git a/debuggerd/tombstone.h b/debuggerd/tombstone.h index d4a1a96..e9878bf 100644 --- a/debuggerd/tombstone.h +++ b/debuggerd/tombstone.h @@ -21,8 +21,6 @@ #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, uintptr_t abort_msg_address, diff --git a/debuggerd/utility.c b/debuggerd/utility.c deleted file mode 100644 index 41be982..0000000 --- a/debuggerd/utility.c +++ /dev/null @@ -1,130 +0,0 @@ -/* system/debuggerd/utility.c -** -** Copyright 2008, 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 <stdio.h> -#include <string.h> -#include <errno.h> -#include <unistd.h> -#include <signal.h> -#include <log/logd.h> -#include <sys/ptrace.h> -#include <sys/wait.h> -#include <arpa/inet.h> -#include <assert.h> - -#include "utility.h" - -const int sleep_time_usec = 50000; /* 0.05 seconds */ -const int max_total_sleep_usec = 10000000; /* 10 seconds */ - -static int write_to_am(int fd, const char* buf, int len) { - int to_write = len; - while (to_write > 0) { - int written = TEMP_FAILURE_RETRY( write(fd, buf + len - to_write, to_write) ); - if (written < 0) { - /* hard failure */ - LOG("AM write failure (%d / %s)\n", errno, strerror(errno)); - return -1; - } - to_write -= written; - } - return len; -} - -void _LOG(log_t* log, int scopeFlags, const char *fmt, ...) { - char buf[512]; - bool want_tfd_write; - bool want_log_write; - bool want_amfd_write; - int len = 0; - - va_list ap; - va_start(ap, fmt); - - // where is the information going to go? - want_tfd_write = log && log->tfd >= 0; - want_log_write = IS_AT_FAULT(scopeFlags) && (!log || !log->quiet); - want_amfd_write = IS_AT_FAULT(scopeFlags) && !IS_SENSITIVE(scopeFlags) && log && log->amfd >= 0; - - // if we're going to need the literal string, generate it once here - if (want_tfd_write || want_amfd_write) { - vsnprintf(buf, sizeof(buf), fmt, ap); - len = strlen(buf); - } - - if (want_tfd_write) { - write(log->tfd, buf, len); - } - - if (want_log_write) { - // whatever goes to logcat also goes to the Activity Manager - __android_log_vprint(ANDROID_LOG_INFO, "DEBUG", fmt, ap); - if (want_amfd_write && len > 0) { - int written = write_to_am(log->amfd, buf, len); - if (written <= 0) { - // timeout or other failure on write; stop informing the activity manager - log->amfd = -1; - } - } - } - va_end(ap); -} - -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("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; - } -} - -void wait_for_stop(pid_t tid, int* total_sleep_time_usec) { - siginfo_t si; - 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; - } - - usleep(sleep_time_usec); - *total_sleep_time_usec += sleep_time_usec; - } -} diff --git a/debuggerd/utility.cpp b/debuggerd/utility.cpp new file mode 100644 index 0000000..35f061e --- /dev/null +++ b/debuggerd/utility.cpp @@ -0,0 +1,129 @@ +/* + * Copyright 2008, 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 <stdio.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <log/logd.h> +#include <sys/ptrace.h> +#include <sys/wait.h> +#include <arpa/inet.h> +#include <assert.h> + +#include "utility.h" + +const int sleep_time_usec = 50000; // 0.05 seconds +const int max_total_sleep_usec = 10000000; // 10 seconds + +static int write_to_am(int fd, const char* buf, int len) { + int to_write = len; + while (to_write > 0) { + int written = TEMP_FAILURE_RETRY( write(fd, buf + len - to_write, to_write) ); + if (written < 0) { + // hard failure + LOG("AM write failure (%d / %s)\n", errno, strerror(errno)); + return -1; + } + to_write -= written; + } + return len; +} + +void _LOG(log_t* log, int scopeFlags, const char* fmt, ...) { + char buf[512]; + bool want_tfd_write; + bool want_log_write; + bool want_amfd_write; + int len = 0; + + va_list ap; + va_start(ap, fmt); + + // where is the information going to go? + want_tfd_write = log && log->tfd >= 0; + want_log_write = IS_AT_FAULT(scopeFlags) && (!log || !log->quiet); + want_amfd_write = IS_AT_FAULT(scopeFlags) && !IS_SENSITIVE(scopeFlags) && log && log->amfd >= 0; + + // if we're going to need the literal string, generate it once here + if (want_tfd_write || want_amfd_write) { + vsnprintf(buf, sizeof(buf), fmt, ap); + len = strlen(buf); + } + + if (want_tfd_write) { + write(log->tfd, buf, len); + } + + if (want_log_write) { + // whatever goes to logcat also goes to the Activity Manager + __android_log_vprint(ANDROID_LOG_INFO, "DEBUG", fmt, ap); + if (want_amfd_write && len > 0) { + int written = write_to_am(log->amfd, buf, len); + if (written <= 0) { + // timeout or other failure on write; stop informing the activity manager + log->amfd = -1; + } + } + } + va_end(ap); +} + +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("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; + } +} + +void wait_for_stop(pid_t tid, int* total_sleep_time_usec) { + siginfo_t si; + 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; + } + + usleep(sleep_time_usec); + *total_sleep_time_usec += sleep_time_usec; + } +} diff --git a/debuggerd/x86/machine.c b/debuggerd/x86/machine.c deleted file mode 100644 index af79092..0000000 --- a/debuggerd/x86/machine.c +++ /dev/null @@ -1,58 +0,0 @@ -/* system/debuggerd/debuggerd.c -** -** Copyright 2006, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#include <stddef.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <errno.h> -#include <sys/types.h> -#include <sys/ptrace.h> - -#include <corkscrew/ptrace.h> - -#include <linux/user.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) { -} - -void dump_registers(const ptrace_context_t* context __attribute((unused)), - log_t* log, pid_t tid, bool at_fault) { - struct pt_regs_x86 r; - int scopeFlags = (at_fault ? SCOPE_AT_FAULT : 0); - - if(ptrace(PTRACE_GETREGS, tid, 0, &r)) { - _LOG(log, scopeFlags, "cannot get registers: %s\n", strerror(errno)); - return; - } - //if there is no stack, no print just like arm - if(!r.ebp) - return; - _LOG(log, scopeFlags, " eax %08x ebx %08x ecx %08x edx %08x\n", - r.eax, r.ebx, r.ecx, r.edx); - _LOG(log, scopeFlags, " esi %08x edi %08x\n", - r.esi, r.edi); - _LOG(log, scopeFlags, " xcs %08x xds %08x xes %08x xfs %08x xss %08x\n", - r.xcs, r.xds, r.xes, r.xfs, r.xss); - _LOG(log, scopeFlags, " eip %08x ebp %08x esp %08x flags %08x\n", - r.eip, r.ebp, r.esp, r.eflags); -} diff --git a/debuggerd/x86/machine.cpp b/debuggerd/x86/machine.cpp new file mode 100644 index 0000000..141f19a --- /dev/null +++ b/debuggerd/x86/machine.cpp @@ -0,0 +1,45 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/ptrace.h> + +#include "../utility.h" +#include "../machine.h" + +void dump_memory_and_code(log_t* log, pid_t tid, int scope_flags) { +} + +void dump_registers(log_t* log, pid_t tid, int scope_flags) { + struct pt_regs r; + if (ptrace(PTRACE_GETREGS, tid, 0, &r) == -1) { + _LOG(log, scope_flags, "cannot get registers: %s\n", strerror(errno)); + return; + } + _LOG(log, scope_flags, " eax %08lx ebx %08lx ecx %08lx edx %08lx\n", + r.eax, r.ebx, r.ecx, r.edx); + _LOG(log, scope_flags, " esi %08lx edi %08lx\n", + r.esi, r.edi); + _LOG(log, scope_flags, " xcs %08x xds %08x xes %08x xfs %08x xss %08x\n", + r.xcs, r.xds, r.xes, r.xfs, r.xss); + _LOG(log, scope_flags, " eip %08lx ebp %08lx esp %08lx flags %08lx\n", + r.eip, r.ebp, r.esp, r.eflags); +} |