diff options
Diffstat (limited to 'libcorkscrew')
| -rw-r--r-- | libcorkscrew/arch-arm/backtrace-arm.c | 205 | ||||
| -rw-r--r-- | libcorkscrew/arch-arm/ptrace-arm.c | 21 | ||||
| -rw-r--r-- | libcorkscrew/arch-x86/backtrace-x86.c | 28 | ||||
| -rw-r--r-- | libcorkscrew/backtrace-arch.h | 4 | ||||
| -rw-r--r-- | libcorkscrew/backtrace.c | 80 | ||||
| -rw-r--r-- | libcorkscrew/map_info.c | 92 | ||||
| -rw-r--r-- | libcorkscrew/ptrace.c | 47 |
7 files changed, 339 insertions, 138 deletions
diff --git a/libcorkscrew/arch-arm/backtrace-arm.c b/libcorkscrew/arch-arm/backtrace-arm.c index 6bed2ae5..cf9a5ab3 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 fd155056..868230ce 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 1324899e..24fadcb0 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 80bca2bb..a46f80b5 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 b03c43fb..f9a49ec4 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 60ed9b43..f33378fc 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 a308bb5c..cbea8ca8 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; |
