summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2011-11-03 17:58:44 -0700
committerJeff Brown <jeffbrown@google.com>2011-11-04 01:11:04 -0700
commitf0c5872637a63e28e3cd314cfc915c07f76df9c6 (patch)
treef4eebc44d0345e4bb2cf6636f55ea51defbe1307
parentfe6c59489cbac15ddd3b68115cd872c06365096c (diff)
Improve stack unwinder robustness.
Keep track of whether memory maps are readable. Use the information in try_get_word to try to avoid accidentally dereferencing an invalid pointer within the current process. (Note that I haven't ever seen that happen during normal unwinding, but it pays to be a little more careful.) Refactored try_get_word a little to make it easier to pass it the needed state for validation checks by way of a little memory_t struct. Improved how the memory map for the current process is cached. This is important because we need up to date information about readable maps. Use a 5 second cache expiration. Improved the PC -> LR fallback logic in the unwinder so we can eke out an extra frame sometimes. Fixed a bug reading ELF program headers. The phnum & phentsize fields are half-words. We were incorrectly interpreting phnum as a whole word. Used android_atomic_* operations carefully in the unwinder to prevent possible memory races between the dumper and the dumpee. This was highly unlikely (or even impossible due to the presence of other barriers along the way) but the code is clearer now about its invariants. Fixed a bug in debuggerd where the pid was being passed to have its stack dump taken instead of the tid, resulting in short stacks because ptrace couldn't read the data if pid != tid. Did a full sweep to ensure that we use pid / tid correctly everywhere. Ported old code from debuggerd to rewind the program counter back one instruction so that it points to the branch instruction itself instead of the return address. Change-Id: Icc4eb08320052975a4ae7f0f5f0ac9308a2d33d7
-rw-r--r--debuggerd/arm/machine.c4
-rw-r--r--debuggerd/debuggerd.c8
-rw-r--r--debuggerd/machine.h2
-rw-r--r--debuggerd/utility.c23
-rw-r--r--debuggerd/utility.h4
-rw-r--r--debuggerd/x86/machine.c8
-rw-r--r--include/corkscrew/backtrace.h4
-rw-r--r--include/corkscrew/map_info.h17
-rw-r--r--include/corkscrew/ptrace.h29
-rw-r--r--libcorkscrew/arch-arm/backtrace-arm.c205
-rw-r--r--libcorkscrew/arch-arm/ptrace-arm.c21
-rw-r--r--libcorkscrew/arch-x86/backtrace-x86.c28
-rw-r--r--libcorkscrew/backtrace-arch.h4
-rw-r--r--libcorkscrew/backtrace.c80
-rw-r--r--libcorkscrew/map_info.c92
-rw-r--r--libcorkscrew/ptrace.c47
16 files changed, 407 insertions, 169 deletions
diff --git a/debuggerd/arm/machine.c b/debuggerd/arm/machine.c
index d941684f5..ca45c9b51 100644
--- a/debuggerd/arm/machine.c
+++ b/debuggerd/arm/machine.c
@@ -87,7 +87,7 @@ static void dump_memory_and_code(int tfd, pid_t tid, bool at_fault) {
}
}
-void dump_registers(ptrace_context_t* context __attribute((unused)),
+void dump_registers(const ptrace_context_t* context __attribute((unused)),
int tfd, pid_t tid, bool at_fault)
{
struct pt_regs r;
@@ -125,7 +125,7 @@ void dump_registers(ptrace_context_t* context __attribute((unused)),
#endif
}
-void dump_thread(ptrace_context_t* context, int tfd, pid_t tid, bool at_fault) {
+void dump_thread(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault) {
dump_registers(context, tfd, tid, at_fault);
dump_backtrace_and_stack(context, tfd, tid, at_fault);
diff --git a/debuggerd/debuggerd.c b/debuggerd/debuggerd.c
index 5a180f1a9..20ffc13af 100644
--- a/debuggerd/debuggerd.c
+++ b/debuggerd/debuggerd.c
@@ -114,12 +114,12 @@ static const char *get_sigcode(int signo, int code)
return "?";
}
-static void dump_fault_addr(int tfd, pid_t pid, int sig)
+static void dump_fault_addr(int tfd, pid_t tid, int sig)
{
siginfo_t si;
memset(&si, 0, sizeof(si));
- if(ptrace(PTRACE_GETSIGINFO, pid, 0, &si)){
+ if(ptrace(PTRACE_GETSIGINFO, tid, 0, &si)){
_LOG(tfd, false, "cannot get siginfo: %s\n", strerror(errno));
} else if (signal_has_address(sig)) {
_LOG(tfd, false, "signal %d (%s), code %d (%s), fault addr %08x\n",
@@ -157,7 +157,7 @@ static void dump_crash_banner(int tfd, pid_t pid, pid_t tid, int sig)
}
/* Return true if some thread is not detached cleanly */
-static bool dump_sibling_thread_report(ptrace_context_t* context,
+static bool dump_sibling_thread_report(const ptrace_context_t* context,
int tfd, pid_t pid, pid_t tid) {
char task_path[64];
snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
@@ -361,7 +361,7 @@ static bool dump_crash(int tfd, pid_t pid, pid_t tid, int signal,
dump_crash_banner(tfd, pid, tid, signal);
- ptrace_context_t* context = load_ptrace_context(pid);
+ ptrace_context_t* context = load_ptrace_context(tid);
dump_thread(context, tfd, tid, true);
diff --git a/debuggerd/machine.h b/debuggerd/machine.h
index f9ca6bd6a..6049b69d0 100644
--- a/debuggerd/machine.h
+++ b/debuggerd/machine.h
@@ -20,6 +20,6 @@
#include <corkscrew/backtrace.h>
#include <sys/types.h>
-void dump_thread(ptrace_context_t* context, int tfd, pid_t tid, bool at_fault);
+void dump_thread(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault);
#endif // _DEBUGGERD_MACHINE_H
diff --git a/debuggerd/utility.c b/debuggerd/utility.c
index c0fb13a09..64e59809b 100644
--- a/debuggerd/utility.c
+++ b/debuggerd/utility.c
@@ -57,8 +57,8 @@ bool signal_has_address(int sig) {
}
}
-static void dump_backtrace(ptrace_context_t* context __attribute((unused)),
- int tfd, int pid __attribute((unused)), bool at_fault,
+static void dump_backtrace(const ptrace_context_t* context __attribute((unused)),
+ int tfd, pid_t tid __attribute((unused)), bool at_fault,
const backtrace_frame_t* backtrace, size_t frames) {
_LOG(tfd, !at_fault, "\nbacktrace:\n");
@@ -66,7 +66,7 @@ static void dump_backtrace(ptrace_context_t* context __attribute((unused)),
get_backtrace_symbols_ptrace(context, backtrace, frames, backtrace_symbols);
for (size_t i = 0; i < frames; i++) {
const backtrace_symbol_t* symbol = &backtrace_symbols[i];
- const char* map_name = symbol->map_info ? symbol->map_info->name : "<unknown>";
+ const char* map_name = symbol->map_name ? symbol->map_name : "<unknown>";
const char* symbol_name = symbol->demangled_name ? symbol->demangled_name : symbol->name;
if (symbol_name) {
_LOG(tfd, !at_fault, " #%02d pc %08x %s (%s)\n",
@@ -79,11 +79,11 @@ static void dump_backtrace(ptrace_context_t* context __attribute((unused)),
free_backtrace_symbols(backtrace_symbols, frames);
}
-static void dump_stack_segment(ptrace_context_t* context, int tfd, int pid,
+static void dump_stack_segment(const ptrace_context_t* context, int tfd, pid_t tid,
bool only_in_tombstone, uintptr_t* sp, size_t words, int label) {
for (size_t i = 0; i < words; i++) {
uint32_t stack_content;
- if (!try_get_word(pid, *sp, &stack_content)) {
+ if (!try_get_word_ptrace(tid, *sp, &stack_content)) {
break;
}
@@ -116,7 +116,7 @@ static void dump_stack_segment(ptrace_context_t* context, int tfd, int pid,
}
}
-static void dump_stack(ptrace_context_t* context, int tfd, int pid, bool at_fault,
+static void dump_stack(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault,
const backtrace_frame_t* backtrace, size_t frames) {
bool have_first = false;
size_t first, last;
@@ -138,7 +138,7 @@ static void dump_stack(ptrace_context_t* context, int tfd, int pid, bool at_faul
// Dump a few words before the first frame.
bool only_in_tombstone = !at_fault;
uintptr_t sp = backtrace[first].stack_top - STACK_WORDS * sizeof(uint32_t);
- dump_stack_segment(context, tfd, pid, only_in_tombstone, &sp, STACK_WORDS, -1);
+ dump_stack_segment(context, tfd, tid, only_in_tombstone, &sp, STACK_WORDS, -1);
// Dump a few words from all successive frames.
// Only log the first 3 frames, put the rest in the tombstone.
@@ -152,7 +152,7 @@ static void dump_stack(ptrace_context_t* context, int tfd, int pid, bool at_faul
only_in_tombstone = true;
}
if (i == last) {
- dump_stack_segment(context, tfd, pid, only_in_tombstone, &sp, STACK_WORDS, i);
+ dump_stack_segment(context, tfd, tid, only_in_tombstone, &sp, STACK_WORDS, i);
if (sp < frame->stack_top + frame->stack_size) {
_LOG(tfd, only_in_tombstone, " ........ ........\n");
}
@@ -163,12 +163,13 @@ static void dump_stack(ptrace_context_t* context, int tfd, int pid, bool at_faul
} else if (words > STACK_WORDS) {
words = STACK_WORDS;
}
- dump_stack_segment(context, tfd, pid, only_in_tombstone, &sp, words, i);
+ dump_stack_segment(context, tfd, tid, only_in_tombstone, &sp, words, i);
}
}
}
-void dump_backtrace_and_stack(ptrace_context_t* context, int tfd, pid_t tid, bool at_fault) {
+void dump_backtrace_and_stack(const ptrace_context_t* context, int tfd, pid_t tid,
+ bool at_fault) {
backtrace_frame_t backtrace[STACK_DEPTH];
ssize_t frames = unwind_backtrace_ptrace(tid, context, backtrace, 0, STACK_DEPTH);
if (frames > 0) {
@@ -237,7 +238,7 @@ void dump_memory(int tfd, pid_t tid, uintptr_t addr, bool at_fault) {
}
}
-void dump_nearby_maps(ptrace_context_t* context, int tfd, pid_t tid) {
+void dump_nearby_maps(const ptrace_context_t* context, int tfd, pid_t tid) {
siginfo_t si;
memset(&si, 0, sizeof(si));
if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si)) {
diff --git a/debuggerd/utility.h b/debuggerd/utility.h
index 879c8b43f..39f91cba4 100644
--- a/debuggerd/utility.h
+++ b/debuggerd/utility.h
@@ -52,7 +52,7 @@ bool signal_has_address(int sig);
/*
* Dumps the backtrace and contents of the stack.
*/
-void dump_backtrace_and_stack(ptrace_context_t* context, int tfd, pid_t pid, bool at_fault);
+void dump_backtrace_and_stack(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault);
/*
* Dumps a few bytes of memory, starting a bit before and ending a bit
@@ -66,7 +66,7 @@ void dump_memory(int tfd, pid_t tid, uintptr_t addr, bool at_fault);
*
* This only makes sense to do on the thread that crashed.
*/
-void dump_nearby_maps(ptrace_context_t* context, int tfd, pid_t tid);
+void dump_nearby_maps(const ptrace_context_t* context, int tfd, pid_t tid);
#endif // _DEBUGGERD_UTILITY_H
diff --git a/debuggerd/x86/machine.c b/debuggerd/x86/machine.c
index 57f51c831..2729c7edc 100644
--- a/debuggerd/x86/machine.c
+++ b/debuggerd/x86/machine.c
@@ -39,12 +39,12 @@
#include "../machine.h"
#include "../utility.h"
-static void dump_registers(ptrace_context_t* context __attribute((unused)),
- int tfd, pid_t pid, bool at_fault) {
+static void dump_registers(const ptrace_context_t* context __attribute((unused)),
+ int tfd, pid_t tid, bool at_fault) {
struct pt_regs_x86 r;
bool only_in_tombstone = !at_fault;
- if(ptrace(PTRACE_GETREGS, pid, 0, &r)) {
+ if(ptrace(PTRACE_GETREGS, tid, 0, &r)) {
_LOG(tfd, only_in_tombstone, "cannot get registers: %s\n", strerror(errno));
return;
}
@@ -61,7 +61,7 @@ static void dump_registers(ptrace_context_t* context __attribute((unused)),
r.eip, r.ebp, r.esp, r.eflags);
}
-void dump_thread(ptrace_context_t* context, int tfd, pid_t tid, bool at_fault) {
+void dump_thread(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault) {
dump_registers(context, tfd, tid, at_fault);
dump_backtrace_and_stack(context, tfd, tid, at_fault);
diff --git a/include/corkscrew/backtrace.h b/include/corkscrew/backtrace.h
index cecb13db6..157d02934 100644
--- a/include/corkscrew/backtrace.h
+++ b/include/corkscrew/backtrace.h
@@ -43,8 +43,8 @@ typedef struct {
typedef struct {
uintptr_t relative_pc; /* relative PC offset from the start of the library,
or the absolute PC if the library is unknown */
- const map_info_t* map_info; /* memory map of the library, or NULL if unknown */
- const char* name; /* symbol name, or NULL if unknown */
+ char* map_name; /* executable or library name, or NULL if unknown */
+ char* name; /* symbol name, or NULL if unknown */
char* demangled_name; /* demangled symbol name, or NULL if unknown */
} backtrace_symbol_t;
diff --git a/include/corkscrew/map_info.h b/include/corkscrew/map_info.h
index a1c344f4d..c5cd8f81e 100644
--- a/include/corkscrew/map_info.h
+++ b/include/corkscrew/map_info.h
@@ -30,6 +30,7 @@ typedef struct map_info {
struct map_info* next;
uintptr_t start;
uintptr_t end;
+ bool is_readable;
bool is_executable;
void* data; // arbitrary data associated with the map by the user, initially NULL
char name[];
@@ -44,8 +45,20 @@ void free_map_info_list(map_info_t* milist);
/* Finds the memory map that contains the specified address. */
const map_info_t* find_map_info(const map_info_t* milist, uintptr_t addr);
-/* Gets the memory map for this process. (result is cached) */
-const map_info_t* my_map_info_list();
+/* Returns true if the addr is in an readable map. */
+bool is_readable_map(const map_info_t* milist, uintptr_t addr);
+
+/* Returns true if the addr is in an executable map. */
+bool is_executable_map(const map_info_t* milist, uintptr_t addr);
+
+/* Acquires a reference to the memory map for this process.
+ * The result is cached and refreshed automatically.
+ * Make sure to release the map info when done. */
+map_info_t* acquire_my_map_info_list();
+
+/* Releases a reference to the map info for this process that was
+ * previous acquired using acquire_my_map_info_list(). */
+void release_my_map_info_list(map_info_t* milist);
#ifdef __cplusplus
}
diff --git a/include/corkscrew/ptrace.h b/include/corkscrew/ptrace.h
index 6acf9eb05..172e348d5 100644
--- a/include/corkscrew/ptrace.h
+++ b/include/corkscrew/ptrace.h
@@ -35,6 +35,12 @@ typedef struct {
map_info_t* map_info_list;
} ptrace_context_t;
+/* Describes how to access memory from a process. */
+typedef struct {
+ pid_t tid;
+ const map_info_t* map_info_list;
+} memory_t;
+
#if __i386__
/* ptrace() register context. */
typedef struct pt_regs_x86 {
@@ -59,11 +65,28 @@ typedef struct pt_regs_x86 {
#endif
/*
+ * Initializes a memory structure for accessing memory from this process.
+ */
+void init_memory(memory_t* memory, const map_info_t* map_info_list);
+
+/*
+ * Initializes a memory structure for accessing memory from another process
+ * using ptrace().
+ */
+void init_memory_ptrace(memory_t* memory, pid_t tid);
+
+/*
* Reads a word of memory safely.
- * Uses ptrace() if tid >= 0, local memory otherwise.
- * Returns false if the word could not be read.
+ * If the memory is local, ensures that the address is readable before dereferencing it.
+ * Returns false and a value of 0xffffffff if the word could not be read.
+ */
+bool try_get_word(const memory_t* memory, uintptr_t ptr, uint32_t* out_value);
+
+/*
+ * Reads a word of memory safely using ptrace().
+ * Returns false and a value of 0xffffffff if the word could not be read.
*/
-bool try_get_word(pid_t tid, uintptr_t ptr, uint32_t* out_value);
+bool try_get_word_ptrace(pid_t tid, uintptr_t ptr, uint32_t* out_value);
/*
* Loads information needed for examining a remote process using ptrace().
diff --git a/libcorkscrew/arch-arm/backtrace-arm.c b/libcorkscrew/arch-arm/backtrace-arm.c
index 6bed2ae5e..cf9a5ab32 100644
--- a/libcorkscrew/arch-arm/backtrace-arm.c
+++ b/libcorkscrew/arch-arm/backtrace-arm.c
@@ -120,35 +120,31 @@ static uintptr_t prel_to_absolute(uintptr_t place, uint32_t prel_offset) {
return place + (((int32_t)(prel_offset << 1)) >> 1);
}
-static uintptr_t get_exception_handler(
- const ptrace_context_t* context, pid_t tid, uintptr_t pc) {
+static uintptr_t get_exception_handler(const memory_t* memory,
+ const map_info_t* map_info_list, uintptr_t pc) {
+ if (!pc) {
+ ALOGV("get_exception_handler: pc is zero, no handler");
+ return 0;
+ }
+
uintptr_t exidx_start;
size_t exidx_size;
const map_info_t* mi;
- if (tid < 0) {
+ if (memory->tid < 0) {
mi = NULL;
exidx_start = find_exidx(pc, &exidx_size);
} else {
- mi = find_map_info(context->map_info_list, pc);
+ mi = find_map_info(map_info_list, pc);
if (mi && mi->data) {
const map_info_data_t* data = (const map_info_data_t*)mi->data;
exidx_start = data->exidx_start;
- exidx_size = data->exidx_size / 8;
+ exidx_size = data->exidx_size;
} else {
exidx_start = 0;
exidx_size = 0;
}
}
- // The PC points to the instruction following the branch.
- // We want to find the exception handler entry that corresponds to the branch itself,
- // so we offset the PC backwards into the previous instruction.
- // ARM instructions are 4 bytes, Thumb are 2, so we just subtract two so we either
- // end up in the middle (ARM) or at the beginning of the instruction (Thumb).
- if (pc >= 2) {
- pc -= 2;
- }
-
uintptr_t handler = 0;
if (exidx_start) {
uint32_t low = 0;
@@ -157,7 +153,7 @@ static uintptr_t get_exception_handler(
uint32_t index = (low + high) / 2;
uintptr_t entry = exidx_start + index * 8;
uint32_t entry_prel_pc;
- if (!try_get_word(tid, entry, &entry_prel_pc)) {
+ if (!try_get_word(memory, entry, &entry_prel_pc)) {
break;
}
uintptr_t entry_pc = prel_to_absolute(entry, entry_prel_pc);
@@ -168,7 +164,7 @@ static uintptr_t get_exception_handler(
if (index + 1 < exidx_size) {
uintptr_t next_entry = entry + 8;
uint32_t next_entry_prel_pc;
- if (!try_get_word(tid, next_entry, &next_entry_prel_pc)) {
+ if (!try_get_word(memory, next_entry, &next_entry_prel_pc)) {
break;
}
uintptr_t next_entry_pc = prel_to_absolute(next_entry, next_entry_prel_pc);
@@ -180,7 +176,7 @@ static uintptr_t get_exception_handler(
uintptr_t entry_handler_ptr = entry + 4;
uint32_t entry_handler;
- if (!try_get_word(tid, entry_handler_ptr, &entry_handler)) {
+ if (!try_get_word(memory, entry_handler_ptr, &entry_handler)) {
break;
}
if (entry_handler & (1L << 31)) {
@@ -191,10 +187,15 @@ static uintptr_t get_exception_handler(
break;
}
}
- ALOGV("get_exception_handler: pc=0x%08x, module='%s', module_start=0x%08x, "
- "exidx_start=0x%08x, exidx_size=%d, handler=0x%08x",
- pc, mi ? mi->name : "<unknown>", mi ? mi->start : 0,
- exidx_start, exidx_size, handler);
+ if (mi) {
+ ALOGV("get_exception_handler: pc=0x%08x, module='%s', module_start=0x%08x, "
+ "exidx_start=0x%08x, exidx_size=%d, handler=0x%08x",
+ pc, mi->name, mi->start, exidx_start, exidx_size, handler);
+ } else {
+ ALOGV("get_exception_handler: pc=0x%08x, "
+ "exidx_start=0x%08x, exidx_size=%d, handler=0x%08x",
+ pc, exidx_start, exidx_size, handler);
+ }
return handler;
}
@@ -203,11 +204,11 @@ typedef struct {
uint32_t word;
} byte_stream_t;
-static bool try_next_byte(pid_t tid, byte_stream_t* stream, uint8_t* out_value) {
+static bool try_next_byte(const memory_t* memory, byte_stream_t* stream, uint8_t* out_value) {
uint8_t result;
switch (stream->ptr & 3) {
case 0:
- if (!try_get_word(tid, stream->ptr, &stream->word)) {
+ if (!try_get_word(memory, stream->ptr, &stream->word)) {
*out_value = 0;
return false;
}
@@ -237,13 +238,13 @@ static void set_reg(unwind_state_t* state, uint32_t reg, uint32_t value) {
state->gregs[reg] = value;
}
-static bool try_pop_registers(pid_t tid, unwind_state_t* state, uint32_t mask) {
+static bool try_pop_registers(const memory_t* memory, unwind_state_t* state, uint32_t mask) {
uint32_t sp = state->gregs[R_SP];
bool sp_updated = false;
for (int i = 0; i < 16; i++) {
if (mask & (1 << i)) {
uint32_t value;
- if (!try_get_word(tid, sp, &value)) {
+ if (!try_get_word(memory, sp, &value)) {
return false;
}
if (i == R_SP) {
@@ -272,8 +273,8 @@ static bool try_pop_registers(pid_t tid, unwind_state_t* state, uint32_t mask) {
* virtual register state (including the stack pointer) such that
* the call frame is unwound and the PC register points to the call site.
*/
-static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
- byte_stream_t* stream, int pr_index) {
+static bool execute_personality_routine(const memory_t* memory,
+ unwind_state_t* state, byte_stream_t* stream, int pr_index) {
size_t size;
switch (pr_index) {
case 0: // Personality routine #0, short frame, descriptors have 16-bit scope.
@@ -282,7 +283,7 @@ static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
case 1: // Personality routine #1, long frame, descriptors have 16-bit scope.
case 2: { // Personality routine #2, long frame, descriptors have 32-bit scope.
uint8_t size_byte;
- if (!try_next_byte(tid, stream, &size_byte)) {
+ if (!try_next_byte(memory, stream, &size_byte)) {
return false;
}
size = (uint32_t)size_byte * sizeof(uint32_t) + 2;
@@ -295,7 +296,7 @@ static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
bool pc_was_set = false;
while (size--) {
uint8_t op;
- if (!try_next_byte(tid, stream, &op)) {
+ if (!try_next_byte(memory, stream, &op)) {
return false;
}
if ((op & 0xc0) == 0x00) {
@@ -306,13 +307,13 @@ static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
set_reg(state, R_SP, state->gregs[R_SP] - ((op & 0x3f) << 2) - 4);
} else if ((op & 0xf0) == 0x80) {
uint8_t op2;
- if (!(size--) || !try_next_byte(tid, stream, &op2)) {
+ if (!(size--) || !try_next_byte(memory, stream, &op2)) {
return false;
}
uint32_t mask = (((uint32_t)op & 0x0f) << 12) | ((uint32_t)op2 << 4);
if (mask) {
// "Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}"
- if (!try_pop_registers(tid, state, mask)) {
+ if (!try_pop_registers(memory, state, mask)) {
return false;
}
if (mask & (1 << R_PC)) {
@@ -334,13 +335,13 @@ static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
} else if ((op & 0xf8) == 0xa0) {
// "Pop r4-r[4+nnn]"
uint32_t mask = (0x0ff0 >> (7 - (op & 0x07))) & 0x0ff0;
- if (!try_pop_registers(tid, state, mask)) {
+ if (!try_pop_registers(memory, state, mask)) {
return false;
}
} else if ((op & 0xf8) == 0xa8) {
// "Pop r4-r[4+nnn], r14"
uint32_t mask = ((0x0ff0 >> (7 - (op & 0x07))) & 0x0ff0) | 0x4000;
- if (!try_pop_registers(tid, state, mask)) {
+ if (!try_pop_registers(memory, state, mask)) {
return false;
}
} else if (op == 0xb0) {
@@ -348,12 +349,12 @@ static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
break;
} else if (op == 0xb1) {
uint8_t op2;
- if (!(size--) || !try_next_byte(tid, stream, &op2)) {
+ if (!(size--) || !try_next_byte(memory, stream, &op2)) {
return false;
}
if (op2 != 0x00 && (op2 & 0xf0) == 0x00) {
// "Pop integer registers under mask {r3, r2, r1, r0}"
- if (!try_pop_registers(tid, state, op2)) {
+ if (!try_pop_registers(memory, state, op2)) {
return false;
}
} else {
@@ -366,7 +367,7 @@ static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
uint32_t shift = 0;
uint8_t op2;
do {
- if (!(size--) || !try_next_byte(tid, stream, &op2)) {
+ if (!(size--) || !try_next_byte(memory, stream, &op2)) {
return false;
}
value |= (op2 & 0x7f) << shift;
@@ -376,7 +377,7 @@ static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
} else if (op == 0xb3) {
// "Pop VFP double-precision registers D[ssss]-D[ssss+cccc] saved (as if) by FSTMFDX"
uint8_t op2;
- if (!(size--) || !try_next_byte(tid, stream, &op2)) {
+ if (!(size--) || !try_next_byte(memory, stream, &op2)) {
return false;
}
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op2 & 0x0f) * 8 + 12);
@@ -389,13 +390,13 @@ static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
} else if (op == 0xc6) {
// "Intel Wireless MMX pop wR[ssss]-wR[ssss+cccc]"
uint8_t op2;
- if (!(size--) || !try_next_byte(tid, stream, &op2)) {
+ if (!(size--) || !try_next_byte(memory, stream, &op2)) {
return false;
}
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op2 & 0x0f) * 8 + 8);
} else if (op == 0xc7) {
uint8_t op2;
- if (!(size--) || !try_next_byte(tid, stream, &op2)) {
+ if (!(size--) || !try_next_byte(memory, stream, &op2)) {
return false;
}
if (op2 != 0x00 && (op2 & 0xf0) == 0x00) {
@@ -409,14 +410,14 @@ static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
// "Pop VFP double precision registers D[16+ssss]-D[16+ssss+cccc]
// saved (as if) by FSTMFD"
uint8_t op2;
- if (!(size--) || !try_next_byte(tid, stream, &op2)) {
+ if (!(size--) || !try_next_byte(memory, stream, &op2)) {
return false;
}
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op2 & 0x0f) * 8 + 8);
} else if (op == 0xc9) {
// "Pop VFP double precision registers D[ssss]-D[ssss+cccc] saved (as if) by FSTMFDD"
uint8_t op2;
- if (!(size--) || !try_next_byte(tid, stream, &op2)) {
+ if (!(size--) || !try_next_byte(memory, stream, &op2)) {
return false;
}
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op2 & 0x0f) * 8 + 8);
@@ -434,52 +435,86 @@ static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
return true;
}
-static ssize_t unwind_backtrace_common(pid_t tid, const ptrace_context_t* context,
+static bool try_get_half_word(const memory_t* memory, uint32_t pc, uint16_t* out_value) {
+ uint32_t word;
+ if (try_get_word(memory, pc & ~2, &word)) {
+ *out_value = pc & 2 ? word >> 16 : word & 0xffff;
+ return true;
+ }
+ return false;
+}
+
+uintptr_t rewind_pc_arch(const memory_t* memory, uintptr_t pc) {
+ if (pc & 1) {
+ /* Thumb mode - need to check whether the bl(x) has long offset or not.
+ * Examples:
+ *
+ * arm blx in the middle of thumb:
+ * 187ae: 2300 movs r3, #0
+ * 187b0: f7fe ee1c blx 173ec
+ * 187b4: 2c00 cmp r4, #0
+ *
+ * arm bl in the middle of thumb:
+ * 187d8: 1c20 adds r0, r4, #0
+ * 187da: f136 fd15 bl 14f208
+ * 187de: 2800 cmp r0, #0
+ *
+ * pure thumb:
+ * 18894: 189b adds r3, r3, r2
+ * 18896: 4798 blx r3
+ * 18898: b001 add sp, #4
+ */
+ pc &= ~1;
+ uint16_t prev1, prev2;
+ if (try_get_half_word(memory, pc - 4, &prev1)
+ && ((prev1 & 0xf000) == 0xf000)
+ && try_get_half_word(memory, pc - 2, &prev2)
+ && ((prev2 & 0xe000) == 0xe000)) {
+ pc -= 4; // long offset
+ } else {
+ pc -= 2;
+ }
+ } else {
+ /* ARM mode, all instructions are 32bit. Yay! */
+ pc -= 4;
+ }
+ return pc;
+}
+
+static ssize_t unwind_backtrace_common(const memory_t* memory,
+ const map_info_t* map_info_list,
unwind_state_t* state, backtrace_frame_t* backtrace,
size_t ignore_depth, size_t max_depth) {
size_t ignored_frames = 0;
size_t returned_frames = 0;
- uintptr_t handler = get_exception_handler(context, tid, state->gregs[R_PC]);
- if (!handler) {
- // If there is no handler for the PC, the program may have branched to
- // an invalid address. Check whether we have a handler for the LR
- // where we came from and use that instead.
- backtrace_frame_t* frame = add_backtrace_entry(state->gregs[R_PC], backtrace,
- ignore_depth, max_depth, &ignored_frames, &returned_frames);
+ for (size_t index = 0; returned_frames < max_depth; index++) {
+ uintptr_t pc = index ? rewind_pc_arch(memory, state->gregs[R_PC])
+ : state->gregs[R_PC];
+ backtrace_frame_t* frame = add_backtrace_entry(pc,
+ backtrace, ignore_depth, max_depth, &ignored_frames, &returned_frames);
if (frame) {
frame->stack_top = state->gregs[R_SP];
}
- handler = get_exception_handler(context, tid, state->gregs[R_LR]);
+ uintptr_t handler = get_exception_handler(memory, map_info_list, pc);
if (!handler) {
- // We don't have a handler here either. Unwinding will not be possible.
- // Return the PC and LR (if it looks sane) and call it good.
- if (state->gregs[R_LR] && state->gregs[R_LR] != state->gregs[R_PC]) {
- // Don't return the SP for this second frame because we don't
- // know how big the first one is so we don't know where this
- // one starts.
- add_backtrace_entry(state->gregs[R_LR], backtrace,
- ignore_depth, max_depth, &ignored_frames, &returned_frames);
+ // If there is no handler for the PC and this is the first frame,
+ // then the program may have branched to an invalid address.
+ // Try starting from the LR instead, otherwise stop unwinding.
+ if (index == 0 && state->gregs[R_LR]
+ && state->gregs[R_LR] != state->gregs[R_PC]) {
+ set_reg(state, R_PC, state->gregs[R_LR]);
+ continue;
+ } else {
+ break;
}
- return returned_frames;
- }
-
- // Ok, continue from the LR.
- set_reg(state, R_PC, state->gregs[R_LR]);
- }
-
- while (handler && returned_frames < max_depth) {
- backtrace_frame_t* frame = add_backtrace_entry(state->gregs[R_PC], backtrace,
- ignore_depth, max_depth, &ignored_frames, &returned_frames);
- if (frame) {
- frame->stack_top = state->gregs[R_SP];
}
byte_stream_t stream;
stream.ptr = handler;
uint8_t pr;
- if (!try_next_byte(tid, &stream, &pr)) {
+ if (!try_next_byte(memory, &stream, &pr)) {
break;
}
if ((pr & 0xf0) != 0x80) {
@@ -490,19 +525,33 @@ static ssize_t unwind_backtrace_common(pid_t tid, const ptrace_context_t* contex
// The first byte indicates the personality routine to execute.
// Following bytes provide instructions to the personality routine.
- if (!execute_personality_routine(tid, state, &stream, pr & 0x0f)) {
+ if (!execute_personality_routine(memory, state, &stream, pr & 0x0f)) {
break;
}
if (frame && state->gregs[R_SP] > frame->stack_top) {
frame->stack_size = state->gregs[R_SP] - frame->stack_top;
}
+ if (!state->gregs[R_PC]) {
+ break;
+ }
+ }
- handler = get_exception_handler(context, tid, state->gregs[R_PC]);
+ // Ran out of frames that we could unwind using handlers.
+ // Add a final entry for the LR if it looks sane and call it good.
+ if (returned_frames < max_depth
+ && state->gregs[R_LR]
+ && state->gregs[R_LR] != state->gregs[R_PC]
+ && is_executable_map(map_info_list, state->gregs[R_LR])) {
+ // We don't know where the stack for this extra frame starts so we
+ // don't return any stack information for it.
+ add_backtrace_entry(rewind_pc_arch(memory, state->gregs[R_LR]),
+ backtrace, ignore_depth, max_depth, &ignored_frames, &returned_frames);
}
return returned_frames;
}
ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo, void* sigcontext,
+ const map_info_t* map_info_list,
backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth) {
const ucontext_t* uc = (const ucontext_t*)sigcontext;
@@ -511,7 +560,10 @@ ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo, void* sigcontext,
state.gregs[i] = uc->uc_mcontext.gregs[i];
}
- return unwind_backtrace_common(-1, NULL, &state, backtrace, ignore_depth, max_depth);
+ memory_t memory;
+ init_memory(&memory, map_info_list);
+ return unwind_backtrace_common(&memory, map_info_list, &state,
+ backtrace, ignore_depth, max_depth);
}
ssize_t unwind_backtrace_ptrace_arch(pid_t tid, const ptrace_context_t* context,
@@ -526,5 +578,8 @@ ssize_t unwind_backtrace_ptrace_arch(pid_t tid, const ptrace_context_t* context,
state.gregs[i] = regs.uregs[i];
}
- return unwind_backtrace_common(tid, context, &state, backtrace, ignore_depth, max_depth);
+ memory_t memory;
+ init_memory_ptrace(&memory, tid);
+ return unwind_backtrace_common(&memory, context->map_info_list, &state,
+ backtrace, ignore_depth, max_depth);
}
diff --git a/libcorkscrew/arch-arm/ptrace-arm.c b/libcorkscrew/arch-arm/ptrace-arm.c
index fd155056f..868230ceb 100644
--- a/libcorkscrew/arch-arm/ptrace-arm.c
+++ b/libcorkscrew/arch-arm/ptrace-arm.c
@@ -29,26 +29,31 @@
static void load_exidx_header(pid_t pid, map_info_t* mi,
uintptr_t* out_exidx_start, size_t* out_exidx_size) {
uint32_t elf_phoff;
- uint32_t elf_phnum;
- if (try_get_word(pid, mi->start + offsetof(Elf32_Ehdr, e_phoff), &elf_phoff)
- && try_get_word(pid, mi->start + offsetof(Elf32_Ehdr, e_phnum), &elf_phnum)) {
+ uint32_t elf_phentsize_phnum;
+ if (try_get_word_ptrace(pid, mi->start + offsetof(Elf32_Ehdr, e_phoff), &elf_phoff)
+ && try_get_word_ptrace(pid, mi->start + offsetof(Elf32_Ehdr, e_phnum),
+ &elf_phentsize_phnum)) {
+ uint32_t elf_phentsize = elf_phentsize_phnum >> 16;
+ uint32_t elf_phnum = elf_phentsize_phnum & 0xffff;
for (uint32_t i = 0; i < elf_phnum; i++) {
- uintptr_t elf_phdr = mi->start + elf_phoff + i * sizeof(Elf32_Phdr);
+ uintptr_t elf_phdr = mi->start + elf_phoff + i * elf_phentsize;
uint32_t elf_phdr_type;
- if (!try_get_word(pid, elf_phdr + offsetof(Elf32_Phdr, p_type), &elf_phdr_type)) {
+ if (!try_get_word_ptrace(pid, elf_phdr + offsetof(Elf32_Phdr, p_type), &elf_phdr_type)) {
break;
}
if (elf_phdr_type == PT_ARM_EXIDX) {
uint32_t elf_phdr_offset;
uint32_t elf_phdr_filesz;
- if (!try_get_word(pid, elf_phdr + offsetof(Elf32_Phdr, p_offset),
+ if (!try_get_word_ptrace(pid, elf_phdr + offsetof(Elf32_Phdr, p_offset),
&elf_phdr_offset)
- || !try_get_word(pid, elf_phdr + offsetof(Elf32_Phdr, p_filesz),
+ || !try_get_word_ptrace(pid, elf_phdr + offsetof(Elf32_Phdr, p_filesz),
&elf_phdr_filesz)) {
break;
}
*out_exidx_start = mi->start + elf_phdr_offset;
- *out_exidx_size = elf_phdr_filesz;
+ *out_exidx_size = elf_phdr_filesz / 8;
+ ALOGV("Parsed EXIDX header info for %s: start=0x%08x, size=%d", mi->name,
+ *out_exidx_start, *out_exidx_size);
return;
}
}
diff --git a/libcorkscrew/arch-x86/backtrace-x86.c b/libcorkscrew/arch-x86/backtrace-x86.c
index 1324899e3..24fadcb03 100644
--- a/libcorkscrew/arch-x86/backtrace-x86.c
+++ b/libcorkscrew/arch-x86/backtrace-x86.c
@@ -73,14 +73,21 @@ typedef struct {
uint32_t esp;
} unwind_state_t;
-static ssize_t unwind_backtrace_common(pid_t tid, const ptrace_context_t* context,
+uintptr_t rewind_pc_arch(const memory_t* memory, uintptr_t pc) {
+ // TODO: Implement for x86.
+ return pc;
+}
+
+static ssize_t unwind_backtrace_common(const memory_t* memory,
+ const map_info_t* map_info_list,
unwind_state_t* state, backtrace_frame_t* backtrace,
size_t ignore_depth, size_t max_depth) {
size_t ignored_frames = 0;
size_t returned_frames = 0;
- while (state->ebp && returned_frames < max_depth) {
- backtrace_frame_t* frame = add_backtrace_entry(state->eip,
+ for (size_t index = 0; state->ebp && returned_frames < max_depth; index++) {
+ backtrace_frame_t* frame = add_backtrace_entry(
+ index ? rewind_pc_arch(memory, state->eip) : state->eip,
backtrace, ignore_depth, max_depth,
&ignored_frames, &returned_frames);
uint32_t next_esp = state->ebp + 8;
@@ -91,8 +98,8 @@ static ssize_t unwind_backtrace_common(pid_t tid, const ptrace_context_t* contex
}
}
state->esp = next_esp;
- if (!try_get_word(tid, state->ebp + 4, &state->eip)
- || !try_get_word(tid, state->ebp, &state->ebp)
+ if (!try_get_word(memory, state->ebp + 4, &state->eip)
+ || !try_get_word(memory, state->ebp, &state->ebp)
|| !state->eip) {
break;
}
@@ -102,6 +109,7 @@ static ssize_t unwind_backtrace_common(pid_t tid, const ptrace_context_t* contex
}
ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo, void* sigcontext,
+ const map_info_t* map_info_list,
backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth) {
const ucontext_t* uc = (const ucontext_t*)sigcontext;
@@ -110,7 +118,10 @@ ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo, void* sigcontext,
state.eip = uc->uc_mcontext.eip;
state.esp = uc->uc_mcontext.esp;
- return unwind_backtrace_common(-1, NULL, &state, backtrace, ignore_depth, max_depth);
+ memory_t memory;
+ init_memory(&memory, map_info_list);
+ return unwind_backtrace_common(&memory, map_info_list,
+ &state, backtrace, ignore_depth, max_depth);
}
ssize_t unwind_backtrace_ptrace_arch(pid_t tid, const ptrace_context_t* context,
@@ -125,5 +136,8 @@ ssize_t unwind_backtrace_ptrace_arch(pid_t tid, const ptrace_context_t* context,
state.eip = regs.eip;
state.esp = regs.esp;
- return unwind_backtrace_common(tid, context, &state, backtrace, ignore_depth, max_depth);
+ memory_t memory;
+ init_memory_ptrace(&memory, tid);
+ return unwind_backtrace_common(&memory, context->map_info_list,
+ &state, backtrace, ignore_depth, max_depth);
}
diff --git a/libcorkscrew/backtrace-arch.h b/libcorkscrew/backtrace-arch.h
index 80bca2bbb..a46f80b56 100644
--- a/libcorkscrew/backtrace-arch.h
+++ b/libcorkscrew/backtrace-arch.h
@@ -28,7 +28,11 @@
extern "C" {
#endif
+/* Rewind the program counter by one instruction. */
+uintptr_t rewind_pc_arch(const memory_t* memory, uintptr_t pc);
+
ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo, void* sigcontext,
+ const map_info_t* map_info_list,
backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth);
ssize_t unwind_backtrace_ptrace_arch(pid_t tid, const ptrace_context_t* context,
diff --git a/libcorkscrew/backtrace.c b/libcorkscrew/backtrace.c
index b03c43fb3..f9a49ec41 100644
--- a/libcorkscrew/backtrace.c
+++ b/libcorkscrew/backtrace.c
@@ -31,6 +31,7 @@
#include <unwind.h>
#include <sys/exec_elf.h>
#include <cutils/log.h>
+#include <cutils/atomic.h>
#if HAVE_DLADDR
#include <dlfcn.h>
@@ -42,6 +43,7 @@ typedef struct {
size_t max_depth;
size_t ignored_frames;
size_t returned_frames;
+ memory_t memory;
} backtrace_state_t;
static _Unwind_Reason_Code unwind_backtrace_callback(struct _Unwind_Context* context, void* arg) {
@@ -52,7 +54,7 @@ static _Unwind_Reason_Code unwind_backtrace_callback(struct _Unwind_Context* con
// This will require a new architecture-specific function to query
// the appropriate registers. Current callers of unwind_backtrace
// don't need this information, so we won't bother collecting it just yet.
- add_backtrace_entry(pc, state->backtrace,
+ add_backtrace_entry(rewind_pc_arch(&state->memory, pc), state->backtrace,
state->ignore_depth, state->max_depth,
&state->ignored_frames, &state->returned_frames);
}
@@ -60,14 +62,22 @@ static _Unwind_Reason_Code unwind_backtrace_callback(struct _Unwind_Context* con
}
ssize_t unwind_backtrace(backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth) {
+ ALOGV("Unwinding current thread %d.", gettid());
+
+ map_info_t* milist = acquire_my_map_info_list();
+
backtrace_state_t state;
state.backtrace = backtrace;
state.ignore_depth = ignore_depth;
state.max_depth = max_depth;
state.ignored_frames = 0;
state.returned_frames = 0;
+ init_memory(&state.memory, milist);
_Unwind_Reason_Code rc =_Unwind_Backtrace(unwind_backtrace_callback, &state);
+
+ release_my_map_info_list(milist);
+
if (state.returned_frames) {
return state.returned_frames;
}
@@ -77,28 +87,39 @@ ssize_t unwind_backtrace(backtrace_frame_t* backtrace, size_t ignore_depth, size
#ifdef CORKSCREW_HAVE_ARCH
static pthread_mutex_t g_unwind_signal_mutex = PTHREAD_MUTEX_INITIALIZER;
static volatile struct {
+ int32_t tid;
+ const map_info_t* map_info_list;
backtrace_frame_t* backtrace;
size_t ignore_depth;
size_t max_depth;
size_t returned_frames;
- bool done;
} g_unwind_signal_state;
static void unwind_backtrace_thread_signal_handler(int n, siginfo_t* siginfo, void* sigcontext) {
- backtrace_frame_t* backtrace = g_unwind_signal_state.backtrace;
- if (backtrace) {
- g_unwind_signal_state.backtrace = NULL;
+ int32_t tid = android_atomic_acquire_load(&g_unwind_signal_state.tid);
+ if (tid == gettid()) {
g_unwind_signal_state.returned_frames = unwind_backtrace_signal_arch(
- siginfo, sigcontext, backtrace,
+ siginfo, sigcontext,
+ g_unwind_signal_state.map_info_list,
+ g_unwind_signal_state.backtrace,
g_unwind_signal_state.ignore_depth,
g_unwind_signal_state.max_depth);
- g_unwind_signal_state.done = true;
+ android_atomic_release_store(-1, &g_unwind_signal_state.tid);
+ } else {
+ ALOGV("Received spurious SIGURG on thread %d that was intended for thread %d.",
+ gettid(), tid);
}
}
#endif
ssize_t unwind_backtrace_thread(pid_t tid, backtrace_frame_t* backtrace,
size_t ignore_depth, size_t max_depth) {
+ if (tid == gettid()) {
+ return unwind_backtrace(backtrace, ignore_depth + 1, max_depth);
+ }
+
+ ALOGV("Unwinding thread %d from thread %d.", tid, gettid());
+
#ifdef CORKSCREW_HAVE_ARCH
struct sigaction act;
struct sigaction oact;
@@ -108,26 +129,32 @@ ssize_t unwind_backtrace_thread(pid_t tid, backtrace_frame_t* backtrace,
sigemptyset(&act.sa_mask);
pthread_mutex_lock(&g_unwind_signal_mutex);
-
- g_unwind_signal_state.backtrace = backtrace;
- g_unwind_signal_state.ignore_depth = ignore_depth;
- g_unwind_signal_state.max_depth = max_depth;
- g_unwind_signal_state.returned_frames = 0;
- g_unwind_signal_state.done = false;
+ map_info_t* milist = acquire_my_map_info_list();
ssize_t frames = -1;
if (!sigaction(SIGURG, &act, &oact)) {
- if (!kill(tid, SIGURG)) {
- while (!g_unwind_signal_state.done) {
+ g_unwind_signal_state.map_info_list = milist;
+ g_unwind_signal_state.backtrace = backtrace;
+ g_unwind_signal_state.ignore_depth = ignore_depth;
+ g_unwind_signal_state.max_depth = max_depth;
+ g_unwind_signal_state.returned_frames = 0;
+ android_atomic_release_store(tid, &g_unwind_signal_state.tid);
+
+ if (kill(tid, SIGURG)) {
+ ALOGV("Failed to send SIGURG to thread %d.", tid);
+ android_atomic_release_store(-1, &g_unwind_signal_state.tid);
+ } else {
+ while (android_atomic_acquire_load(&g_unwind_signal_state.tid) == tid) {
+ ALOGV("Waiting for response from thread %d...", tid);
usleep(1000);
}
frames = g_unwind_signal_state.returned_frames;
}
+
sigaction(SIGURG, &oact, NULL);
}
- g_unwind_signal_state.backtrace = NULL;
-
+ release_my_map_info_list(milist);
pthread_mutex_unlock(&g_unwind_signal_mutex);
return frames;
#else
@@ -146,14 +173,14 @@ ssize_t unwind_backtrace_ptrace(pid_t tid, const ptrace_context_t* context,
static void init_backtrace_symbol(backtrace_symbol_t* symbol, uintptr_t pc) {
symbol->relative_pc = pc;
- symbol->map_info = NULL;
+ symbol->map_name = NULL;
symbol->name = NULL;
symbol->demangled_name = NULL;
}
void get_backtrace_symbols(const backtrace_frame_t* backtrace, size_t frames,
backtrace_symbol_t* backtrace_symbols) {
- const map_info_t* milist = my_map_info_list();
+ map_info_t* milist = acquire_my_map_info_list();
for (size_t i = 0; i < frames; i++) {
const backtrace_frame_t* frame = &backtrace[i];
backtrace_symbol_t* symbol = &backtrace_symbols[i];
@@ -162,16 +189,19 @@ void get_backtrace_symbols(const backtrace_frame_t* backtrace, size_t frames,
const map_info_t* mi = find_map_info(milist, frame->absolute_pc);
if (mi) {
symbol->relative_pc = frame->absolute_pc - mi->start;
- symbol->map_info = mi;
+ if (mi->name[0]) {
+ symbol->map_name = strdup(mi->name);
+ }
#if HAVE_DLADDR
Dl_info info;
if (dladdr((const void*)frame->absolute_pc, &info) && info.dli_sname) {
- symbol->name = info.dli_sname;
+ symbol->name = strdup(info.dli_sname);
symbol->demangled_name = demangle_symbol_name(symbol->name);
}
#endif
}
}
+ release_my_map_info_list(milist);
}
void get_backtrace_symbols_ptrace(const ptrace_context_t* context,
@@ -187,10 +217,12 @@ void get_backtrace_symbols_ptrace(const ptrace_context_t* context,
find_symbol_ptrace(context, frame->absolute_pc, &mi, &s);
if (mi) {
symbol->relative_pc = frame->absolute_pc - mi->start;
- symbol->map_info = mi;
+ if (mi->name[0]) {
+ symbol->map_name = strdup(mi->name);
+ }
}
if (s) {
- symbol->name = s->name;
+ symbol->name = strdup(s->name);
symbol->demangled_name = demangle_symbol_name(symbol->name);
}
}
@@ -199,6 +231,8 @@ void get_backtrace_symbols_ptrace(const ptrace_context_t* context,
void free_backtrace_symbols(backtrace_symbol_t* backtrace_symbols, size_t frames) {
for (size_t i = 0; i < frames; i++) {
backtrace_symbol_t* symbol = &backtrace_symbols[i];
+ free(symbol->map_name);
+ free(symbol->name);
free(symbol->demangled_name);
init_backtrace_symbol(symbol, 0);
}
diff --git a/libcorkscrew/map_info.c b/libcorkscrew/map_info.c
index 60ed9b43d..f33378fce 100644
--- a/libcorkscrew/map_info.c
+++ b/libcorkscrew/map_info.c
@@ -26,6 +26,7 @@
#include <pthread.h>
#include <unistd.h>
#include <cutils/log.h>
+#include <sys/time.h>
// 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so\n
// 012345678901234567890123456789012345678901234567890123456789
@@ -54,10 +55,14 @@ static map_info_t* parse_maps_line(const char* line)
if (mi) {
mi->start = start;
mi->end = end;
+ mi->is_readable = strlen(permissions) == 4 && permissions[0] == 'r';
mi->is_executable = strlen(permissions) == 4 && permissions[2] == 'x';
mi->data = NULL;
memcpy(mi->name, name, name_len);
mi->name[name_len] = '\0';
+ ALOGV("Parsed map: start=0x%08x, end=0x%08x, "
+ "is_readable=%d, is_executable=%d, name=%s",
+ mi->start, mi->end, mi->is_readable, mi->is_executable, mi->name);
}
return mi;
}
@@ -99,14 +104,87 @@ const map_info_t* find_map_info(const map_info_t* milist, uintptr_t addr) {
return mi;
}
-static pthread_once_t g_my_milist_once = PTHREAD_ONCE_INIT;
-static map_info_t* g_my_milist = NULL;
+bool is_readable_map(const map_info_t* milist, uintptr_t addr) {
+ const map_info_t* mi = find_map_info(milist, addr);
+ return mi && mi->is_readable;
+}
+
+bool is_executable_map(const map_info_t* milist, uintptr_t addr) {
+ const map_info_t* mi = find_map_info(milist, addr);
+ return mi && mi->is_executable;
+}
+
+static pthread_mutex_t g_my_map_info_list_mutex = PTHREAD_MUTEX_INITIALIZER;
+static map_info_t* g_my_map_info_list = NULL;
+
+static const int64_t MAX_CACHE_AGE = 5 * 1000 * 1000000LL;
+
+typedef struct {
+ uint32_t refs;
+ int64_t timestamp;
+} my_map_info_data_t;
+
+static int64_t now() {
+ struct timespec t;
+ t.tv_sec = t.tv_nsec = 0;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return t.tv_sec * 1000000000LL + t.tv_nsec;
+}
+
+static void dec_ref(map_info_t* milist, my_map_info_data_t* data) {
+ if (!--data->refs) {
+ ALOGV("Freed my_map_info_list %p.", milist);
+ free(data);
+ free_map_info_list(milist);
+ }
+}
+
+map_info_t* acquire_my_map_info_list() {
+ pthread_mutex_lock(&g_my_map_info_list_mutex);
-static void init_my_milist_once() {
- g_my_milist = load_map_info_list(getpid());
+ int64_t time = now();
+ if (g_my_map_info_list) {
+ my_map_info_data_t* data = (my_map_info_data_t*)g_my_map_info_list->data;
+ int64_t age = time - data->timestamp;
+ if (age >= MAX_CACHE_AGE) {
+ ALOGV("Invalidated my_map_info_list %p, age=%lld.", g_my_map_info_list, age);
+ dec_ref(g_my_map_info_list, data);
+ g_my_map_info_list = NULL;
+ } else {
+ ALOGV("Reusing my_map_info_list %p, age=%lld.", g_my_map_info_list, age);
+ }
+ }
+
+ if (!g_my_map_info_list) {
+ my_map_info_data_t* data = (my_map_info_data_t*)malloc(sizeof(my_map_info_data_t));
+ g_my_map_info_list = load_map_info_list(getpid());
+ if (g_my_map_info_list) {
+ ALOGV("Loaded my_map_info_list %p.", g_my_map_info_list);
+ g_my_map_info_list->data = data;
+ data->refs = 1;
+ data->timestamp = time;
+ } else {
+ free(data);
+ }
+ }
+
+ map_info_t* milist = g_my_map_info_list;
+ if (milist) {
+ my_map_info_data_t* data = (my_map_info_data_t*)g_my_map_info_list->data;
+ data->refs += 1;
+ }
+
+ pthread_mutex_unlock(&g_my_map_info_list_mutex);
+ return milist;
}
-const map_info_t* my_map_info_list() {
- pthread_once(&g_my_milist_once, init_my_milist_once);
- return g_my_milist;
+void release_my_map_info_list(map_info_t* milist) {
+ if (milist) {
+ pthread_mutex_lock(&g_my_map_info_list_mutex);
+
+ my_map_info_data_t* data = (my_map_info_data_t*)milist->data;
+ dec_ref(milist, data);
+
+ pthread_mutex_unlock(&g_my_map_info_list_mutex);
+ }
}
diff --git a/libcorkscrew/ptrace.c b/libcorkscrew/ptrace.c
index a308bb5cc..cbea8ca82 100644
--- a/libcorkscrew/ptrace.c
+++ b/libcorkscrew/ptrace.c
@@ -34,44 +34,55 @@ static const uint32_t ELF_MAGIC = 0x464C457f; // "ELF\0177"
#define PAGE_MASK (~(PAGE_SIZE - 1))
#endif
-bool try_get_word(pid_t tid, uintptr_t ptr, uint32_t* out_value) {
+void init_memory(memory_t* memory, const map_info_t* map_info_list) {
+ memory->tid = -1;
+ memory->map_info_list = map_info_list;
+}
+
+void init_memory_ptrace(memory_t* memory, pid_t tid) {
+ memory->tid = tid;
+ memory->map_info_list = NULL;
+}
+
+bool try_get_word(const memory_t* memory, uintptr_t ptr, uint32_t* out_value) {
+ ALOGV("try_get_word: reading word at 0x%08x", ptr);
if (ptr & 3) {
ALOGV("try_get_word: invalid pointer 0x%08x", ptr);
- *out_value = 0;
+ *out_value = 0xffffffffL;
return false;
}
- if (tid < 0) {
-#if 0 /*unreliable, unclear whether this is safe from a signal handler context*/
- // Determine whether the pointer is likely to be valid before dereferencing it.
- unsigned char vec[1];
- while (mincore((void*)(ptr & PAGE_MASK), sizeof(uint32_t), vec)) {
- if (errno != EAGAIN && errno != EINTR) {
- ALOGV("try_get_word: invalid pointer 0x%08x, mincore() errno=%d", ptr, errno);
- *out_value = 0;
- return false;
- }
+ if (memory->tid < 0) {
+ if (!is_readable_map(memory->map_info_list, ptr)) {
+ ALOGV("try_get_word: pointer 0x%08x not in a readable map", ptr);
+ *out_value = 0xffffffffL;
+ return false;
}
-#endif
*out_value = *(uint32_t*)ptr;
return true;
} else {
// ptrace() returns -1 and sets errno when the operation fails.
// To disambiguate -1 from a valid result, we clear errno beforehand.
errno = 0;
- *out_value = ptrace(PTRACE_PEEKTEXT, tid, (void*)ptr, NULL);
+ *out_value = ptrace(PTRACE_PEEKTEXT, memory->tid, (void*)ptr, NULL);
if (*out_value == 0xffffffffL && errno) {
- ALOGV("try_get_word: invalid pointer 0x%08x, ptrace() errno=%d", ptr, errno);
- *out_value = 0;
+ ALOGV("try_get_word: invalid pointer 0x%08x reading from tid %d, "
+ "ptrace() errno=%d", ptr, memory->tid, errno);
return false;
}
return true;
}
}
+bool try_get_word_ptrace(pid_t tid, uintptr_t ptr, uint32_t* out_value) {
+ memory_t memory;
+ init_memory_ptrace(&memory, tid);
+ return try_get_word(&memory, ptr, out_value);
+}
+
static void load_ptrace_map_info_data(pid_t pid, map_info_t* mi) {
- if (mi->is_executable) {
+ if (mi->is_executable && mi->is_readable) {
uint32_t elf_magic;
- if (try_get_word(pid, mi->start, &elf_magic) && elf_magic == ELF_MAGIC) {
+ if (try_get_word_ptrace(pid, mi->start, &elf_magic) && elf_magic == ELF_MAGIC) {
map_info_data_t* data = (map_info_data_t*)calloc(1, sizeof(map_info_data_t));
if (data) {
mi->data = data;