864 lines
32 KiB
C
Executable File
864 lines
32 KiB
C
Executable File
/*
|
|
* Copyright (C) 2011 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.
|
|
*/
|
|
|
|
/*
|
|
* Backtracing functions for x86.
|
|
*/
|
|
|
|
#define LOG_TAG "Corkscrew"
|
|
//#define LOG_NDEBUG 0
|
|
|
|
#include "../backtrace-arch.h"
|
|
#include "../backtrace-helper.h"
|
|
#include "../ptrace-arch.h"
|
|
#include <corkscrew/ptrace.h>
|
|
#include "dwarf.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <limits.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <sys/ptrace.h>
|
|
#include <cutils/log.h>
|
|
|
|
#if defined(__BIONIC__)
|
|
|
|
#if defined(__BIONIC_HAVE_UCONTEXT_T)
|
|
|
|
// Bionic offers the Linux kernel headers.
|
|
#include <asm/sigcontext.h>
|
|
#include <asm/ucontext.h>
|
|
typedef struct ucontext ucontext_t;
|
|
|
|
#else /* __BIONIC_HAVE_UCONTEXT_T */
|
|
|
|
/* Old versions of the Android <signal.h> didn't define ucontext_t. */
|
|
|
|
typedef struct {
|
|
uint32_t gregs[32];
|
|
void* fpregs;
|
|
uint32_t oldmask;
|
|
uint32_t cr2;
|
|
} mcontext_t;
|
|
|
|
enum {
|
|
REG_GS = 0, REG_FS, REG_ES, REG_DS,
|
|
REG_EDI, REG_ESI, REG_EBP, REG_ESP,
|
|
REG_EBX, REG_EDX, REG_ECX, REG_EAX,
|
|
REG_TRAPNO, REG_ERR, REG_EIP, REG_CS,
|
|
REG_EFL, REG_UESP, REG_SS
|
|
};
|
|
|
|
/* Machine context at the time a signal was raised. */
|
|
typedef struct ucontext {
|
|
uint32_t uc_flags;
|
|
struct ucontext* uc_link;
|
|
stack_t uc_stack;
|
|
mcontext_t uc_mcontext;
|
|
uint32_t uc_sigmask;
|
|
} ucontext_t;
|
|
|
|
#endif /* __BIONIC_HAVE_UCONTEXT_T */
|
|
|
|
#elif defined(__APPLE__)
|
|
|
|
#define _XOPEN_SOURCE
|
|
#include <ucontext.h>
|
|
|
|
#else
|
|
|
|
// glibc has its own renaming of the Linux kernel's structures.
|
|
#define __USE_GNU // For REG_EBP, REG_ESP, and REG_EIP.
|
|
#include <ucontext.h>
|
|
|
|
#endif
|
|
|
|
/* Unwind state. */
|
|
typedef struct {
|
|
uint32_t reg[DWARF_REGISTERS];
|
|
} unwind_state_t;
|
|
|
|
typedef struct {
|
|
backtrace_frame_t* backtrace;
|
|
size_t ignore_depth;
|
|
size_t max_depth;
|
|
size_t ignored_frames;
|
|
size_t returned_frames;
|
|
memory_t memory;
|
|
} backtrace_state_t;
|
|
|
|
uintptr_t rewind_pc_arch(const memory_t* memory __attribute__((unused)), uintptr_t pc) {
|
|
/* TODO: x86 instructions are 1-16 bytes, to define exact size of previous instruction
|
|
we have to disassemble from the function entry point up to pc.
|
|
Returning pc-1 is probably enough for now, the only drawback is that
|
|
it points somewhere between the first byte of instruction we are looking for and
|
|
the first byte of the next instruction. */
|
|
|
|
return pc-1;
|
|
/* TODO: We should adjust that for the signal frames and return pc for them instead of pc-1.
|
|
To recognize signal frames we should read cie_info property. */
|
|
}
|
|
|
|
/* Read byte through 4 byte cache. Usually we read byte by byte and updating cursor. */
|
|
static bool try_get_byte(const memory_t* memory, uintptr_t ptr, uint8_t* out_value, uint32_t* cursor) {
|
|
static uintptr_t lastptr;
|
|
static uint32_t buf;
|
|
|
|
ptr += *cursor;
|
|
|
|
if (ptr < lastptr || lastptr + 3 < ptr) {
|
|
lastptr = (ptr >> 2) << 2;
|
|
if (!try_get_word(memory, lastptr, &buf)) {
|
|
return false;
|
|
}
|
|
}
|
|
*out_value = (uint8_t)((buf >> ((ptr & 3) * 8)) & 0xff);
|
|
++*cursor;
|
|
return true;
|
|
}
|
|
|
|
/* Getting X bytes. 4 is maximum for now. */
|
|
static bool try_get_xbytes(const memory_t* memory, uintptr_t ptr, uint32_t* out_value, uint8_t bytes, uint32_t* cursor) {
|
|
uint32_t data = 0;
|
|
if (bytes > 4) {
|
|
ALOGE("can't read more than 4 bytes, trying to read %d", bytes);
|
|
return false;
|
|
}
|
|
for (int i = 0; i < bytes; i++) {
|
|
uint8_t buf;
|
|
if (!try_get_byte(memory, ptr, &buf, cursor)) {
|
|
return false;
|
|
}
|
|
data |= (uint32_t)buf << (i * 8);
|
|
}
|
|
*out_value = data;
|
|
return true;
|
|
}
|
|
|
|
/* Reads signed/unsigned LEB128 encoded data. From 1 to 4 bytes. */
|
|
static bool try_get_leb128(const memory_t* memory, uintptr_t ptr, uint32_t* out_value, uint32_t* cursor, bool sign_extend) {
|
|
uint8_t buf = 0;
|
|
uint32_t val = 0;
|
|
uint8_t c = 0;
|
|
do {
|
|
if (!try_get_byte(memory, ptr, &buf, cursor)) {
|
|
return false;
|
|
}
|
|
val |= ((uint32_t)buf & 0x7f) << (c * 7);
|
|
c++;
|
|
} while (buf & 0x80 && (c * 7) <= 32);
|
|
if (c * 7 > 32) {
|
|
ALOGE("%s: data exceeds expected 4 bytes maximum", __FUNCTION__);
|
|
return false;
|
|
}
|
|
if (sign_extend) {
|
|
if (buf & 0x40) {
|
|
val |= ((uint32_t)-1 << (c * 7));
|
|
}
|
|
}
|
|
*out_value = val;
|
|
return true;
|
|
}
|
|
|
|
/* Reads signed LEB128 encoded data. From 1 to 4 bytes. */
|
|
static bool try_get_sleb128(const memory_t* memory, uintptr_t ptr, uint32_t* out_value, uint32_t* cursor) {
|
|
return try_get_leb128(memory, ptr, out_value, cursor, true);
|
|
}
|
|
|
|
/* Reads unsigned LEB128 encoded data. From 1 to 4 bytes. */
|
|
static bool try_get_uleb128(const memory_t* memory, uintptr_t ptr, uint32_t* out_value, uint32_t* cursor) {
|
|
return try_get_leb128(memory, ptr, out_value, cursor, false);
|
|
}
|
|
|
|
/* Getting data encoded by dwarf encodings. */
|
|
static bool read_dwarf(const memory_t* memory, uintptr_t ptr, uint32_t* out_value, uint8_t encoding, uint32_t* cursor) {
|
|
uint32_t data = 0;
|
|
bool issigned = true;
|
|
uintptr_t addr = ptr + *cursor;
|
|
/* Lower 4 bits is data type/size */
|
|
/* TODO: add more encodings if it becomes necessary */
|
|
switch (encoding & 0xf) {
|
|
case DW_EH_PE_absptr:
|
|
if (!try_get_xbytes(memory, ptr, &data, 4, cursor)) {
|
|
return false;
|
|
}
|
|
*out_value = data;
|
|
return true;
|
|
case DW_EH_PE_udata4:
|
|
issigned = false;
|
|
case DW_EH_PE_sdata4:
|
|
if (!try_get_xbytes(memory, ptr, &data, 4, cursor)) {
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
ALOGE("unrecognized dwarf lower part encoding: 0x%x", encoding);
|
|
return false;
|
|
}
|
|
/* Higher 4 bits is modifier */
|
|
/* TODO: add more encodings if it becomes necessary */
|
|
switch (encoding & 0xf0) {
|
|
case 0:
|
|
*out_value = data;
|
|
break;
|
|
case DW_EH_PE_pcrel:
|
|
if (issigned) {
|
|
*out_value = addr + (int32_t)data;
|
|
} else {
|
|
*out_value = addr + data;
|
|
}
|
|
break;
|
|
/* Assuming ptr is correct base to calculate datarel */
|
|
case DW_EH_PE_datarel:
|
|
if (issigned) {
|
|
*out_value = ptr + (int32_t)data;
|
|
} else {
|
|
*out_value = ptr + data;
|
|
}
|
|
break;
|
|
default:
|
|
ALOGE("unrecognized dwarf higher part encoding: 0x%x", encoding);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Having PC find corresponding FDE by reading .eh_frame_hdr section data. */
|
|
static uintptr_t find_fde(const memory_t* memory,
|
|
const map_info_t* map_info_list, uintptr_t pc) {
|
|
if (!pc) {
|
|
ALOGV("find_fde: pc is zero, no eh_frame");
|
|
return 0;
|
|
}
|
|
const map_info_t* mi = find_map_info(map_info_list, pc);
|
|
if (!mi) {
|
|
ALOGV("find_fde: no map info for pc:0x%x", pc);
|
|
return 0;
|
|
}
|
|
const map_info_data_t* midata = mi->data;
|
|
if (!midata) {
|
|
ALOGV("find_fde: no eh_frame_hdr for map: start=0x%x, end=0x%x", mi->start, mi->end);
|
|
return 0;
|
|
}
|
|
|
|
eh_frame_hdr_info_t eh_hdr_info;
|
|
memset(&eh_hdr_info, 0, sizeof(eh_frame_hdr_info_t));
|
|
|
|
/* Getting the first word of eh_frame_hdr:
|
|
1st byte is version;
|
|
2nd byte is encoding of pointer to eh_frames;
|
|
3rd byte is encoding of count of FDEs in lookup table;
|
|
4th byte is encoding of lookup table entries.
|
|
*/
|
|
uintptr_t eh_frame_hdr = midata->eh_frame_hdr;
|
|
uint32_t c = 0;
|
|
if (!try_get_byte(memory, eh_frame_hdr, &eh_hdr_info.version, &c)) return 0;
|
|
if (!try_get_byte(memory, eh_frame_hdr, &eh_hdr_info.eh_frame_ptr_enc, &c)) return 0;
|
|
if (!try_get_byte(memory, eh_frame_hdr, &eh_hdr_info.fde_count_enc, &c)) return 0;
|
|
if (!try_get_byte(memory, eh_frame_hdr, &eh_hdr_info.fde_table_enc, &c)) return 0;
|
|
|
|
/* TODO: 3rd byte can be DW_EH_PE_omit, that means no lookup table available and we should
|
|
try to parse eh_frame instead. Not sure how often it may occur, skipping now.
|
|
*/
|
|
if (eh_hdr_info.version != 1) {
|
|
ALOGV("find_fde: eh_frame_hdr version %d is not supported", eh_hdr_info.version);
|
|
return 0;
|
|
}
|
|
/* Getting the data:
|
|
2nd word is eh_frame pointer (normally not used, because lookup table has all we need);
|
|
3rd word is count of FDEs in the lookup table;
|
|
starting from 4 word there is FDE lookup table (pairs of PC and FDE pointer) sorted by PC;
|
|
*/
|
|
if (!read_dwarf(memory, eh_frame_hdr, &eh_hdr_info.eh_frame_ptr, eh_hdr_info.eh_frame_ptr_enc, &c)) return 0;
|
|
if (!read_dwarf(memory, eh_frame_hdr, &eh_hdr_info.fde_count, eh_hdr_info.fde_count_enc, &c)) return 0;
|
|
ALOGV("find_fde: found %d FDEs", eh_hdr_info.fde_count);
|
|
|
|
int32_t low = 0;
|
|
int32_t high = eh_hdr_info.fde_count;
|
|
uintptr_t start = 0;
|
|
uintptr_t fde = 0;
|
|
/* eh_frame_hdr + c points to lookup table at this point. */
|
|
while (low <= high) {
|
|
uint32_t mid = (high + low)/2;
|
|
uint32_t entry = c + mid * 8;
|
|
if (!read_dwarf(memory, eh_frame_hdr, &start, eh_hdr_info.fde_table_enc, &entry)) return 0;
|
|
if (pc <= start) {
|
|
high = mid - 1;
|
|
} else {
|
|
low = mid + 1;
|
|
}
|
|
}
|
|
/* Value found is at high. */
|
|
if (high < 0) {
|
|
ALOGV("find_fde: pc %x is out of FDE bounds: %x", pc, start);
|
|
return 0;
|
|
}
|
|
c += high * 8;
|
|
if (!read_dwarf(memory, eh_frame_hdr, &start, eh_hdr_info.fde_table_enc, &c)) return 0;
|
|
if (!read_dwarf(memory, eh_frame_hdr, &fde, eh_hdr_info.fde_table_enc, &c)) return 0;
|
|
ALOGV("pc 0x%x, ENTRY %d: start=0x%x, fde=0x%x", pc, high, start, fde);
|
|
return fde;
|
|
}
|
|
|
|
/* Execute single dwarf instruction and update dwarf state accordingly. */
|
|
static bool execute_dwarf(const memory_t* memory, uintptr_t ptr, cie_info_t* cie_info,
|
|
dwarf_state_t* dstate, uint32_t* cursor,
|
|
dwarf_state_t* stack, uint8_t* stack_ptr) {
|
|
uint8_t inst;
|
|
uint8_t op = 0;
|
|
|
|
if (!try_get_byte(memory, ptr, &inst, cursor)) {
|
|
return false;
|
|
}
|
|
ALOGV("DW_CFA inst: 0x%x", inst);
|
|
|
|
/* For some instructions upper 2 bits is opcode and lower 6 bits is operand. See dwarf-2.0 7.23. */
|
|
if (inst & 0xc0) {
|
|
op = inst & 0x3f;
|
|
inst &= 0xc0;
|
|
}
|
|
|
|
switch ((dwarf_CFA)inst) {
|
|
uint32_t reg = 0;
|
|
uint32_t offset = 0;
|
|
case DW_CFA_advance_loc:
|
|
dstate->loc += op * cie_info->code_align;
|
|
ALOGV("DW_CFA_advance_loc: %d to 0x%x", op, dstate->loc);
|
|
break;
|
|
case DW_CFA_offset:
|
|
if (!try_get_uleb128(memory, ptr, &offset, cursor)) return false;
|
|
dstate->regs[op].rule = 'o';
|
|
dstate->regs[op].value = offset * cie_info->data_align;
|
|
ALOGV("DW_CFA_offset: r%d = o(%d)", op, dstate->regs[op].value);
|
|
break;
|
|
case DW_CFA_restore:
|
|
dstate->regs[op].rule = stack->regs[op].rule;
|
|
dstate->regs[op].value = stack->regs[op].value;
|
|
ALOGV("DW_CFA_restore: r%d = %c(%d)", op, dstate->regs[op].rule, dstate->regs[op].value);
|
|
break;
|
|
case DW_CFA_nop:
|
|
break;
|
|
case DW_CFA_set_loc: // probably we don't have it on x86.
|
|
if (!try_get_xbytes(memory, ptr, &offset, 4, cursor)) return false;
|
|
if (offset < dstate->loc) {
|
|
ALOGE("DW_CFA_set_loc: attempt to move location backward");
|
|
return false;
|
|
}
|
|
dstate->loc = offset * cie_info->code_align;
|
|
ALOGV("DW_CFA_set_loc: %d to 0x%x", offset * cie_info->code_align, dstate->loc);
|
|
break;
|
|
case DW_CFA_advance_loc1:
|
|
if (!try_get_byte(memory, ptr, (uint8_t*)&offset, cursor)) return false;
|
|
dstate->loc += (uint8_t)offset * cie_info->code_align;
|
|
ALOGV("DW_CFA_advance_loc1: %d to 0x%x", (uint8_t)offset * cie_info->code_align, dstate->loc);
|
|
break;
|
|
case DW_CFA_advance_loc2:
|
|
if (!try_get_xbytes(memory, ptr, &offset, 2, cursor)) return false;
|
|
dstate->loc += (uint16_t)offset * cie_info->code_align;
|
|
ALOGV("DW_CFA_advance_loc2: %d to 0x%x", (uint16_t)offset * cie_info->code_align, dstate->loc);
|
|
break;
|
|
case DW_CFA_advance_loc4:
|
|
if (!try_get_xbytes(memory, ptr, &offset, 4, cursor)) return false;
|
|
dstate->loc += offset * cie_info->code_align;
|
|
ALOGV("DW_CFA_advance_loc4: %d to 0x%x", offset * cie_info->code_align, dstate->loc);
|
|
break;
|
|
case DW_CFA_offset_extended: // probably we don't have it on x86.
|
|
if (!try_get_uleb128(memory, ptr, ®, cursor)) return false;
|
|
if (!try_get_uleb128(memory, ptr, &offset, cursor)) return false;
|
|
if (reg > DWARF_REGISTERS) {
|
|
ALOGE("DW_CFA_offset_extended: r%d exceeds supported number of registers (%d)", reg, DWARF_REGISTERS);
|
|
return false;
|
|
}
|
|
dstate->regs[reg].rule = 'o';
|
|
dstate->regs[reg].value = offset * cie_info->data_align;
|
|
ALOGV("DW_CFA_offset_extended: r%d = o(%d)", reg, dstate->regs[reg].value);
|
|
break;
|
|
case DW_CFA_restore_extended: // probably we don't have it on x86.
|
|
if (!try_get_uleb128(memory, ptr, ®, cursor)) return false;
|
|
dstate->regs[reg].rule = stack->regs[reg].rule;
|
|
dstate->regs[reg].value = stack->regs[reg].value;
|
|
if (reg > DWARF_REGISTERS) {
|
|
ALOGE("DW_CFA_restore_extended: r%d exceeds supported number of registers (%d)", reg, DWARF_REGISTERS);
|
|
return false;
|
|
}
|
|
ALOGV("DW_CFA_restore: r%d = %c(%d)", reg, dstate->regs[reg].rule, dstate->regs[reg].value);
|
|
break;
|
|
case DW_CFA_undefined: // probably we don't have it on x86.
|
|
if (!try_get_uleb128(memory, ptr, ®, cursor)) return false;
|
|
dstate->regs[reg].rule = 'u';
|
|
dstate->regs[reg].value = 0;
|
|
if (reg > DWARF_REGISTERS) {
|
|
ALOGE("DW_CFA_undefined: r%d exceeds supported number of registers (%d)", reg, DWARF_REGISTERS);
|
|
return false;
|
|
}
|
|
ALOGV("DW_CFA_undefined: r%d", reg);
|
|
break;
|
|
case DW_CFA_same_value: // probably we don't have it on x86.
|
|
if (!try_get_uleb128(memory, ptr, ®, cursor)) return false;
|
|
dstate->regs[reg].rule = 's';
|
|
dstate->regs[reg].value = 0;
|
|
if (reg > DWARF_REGISTERS) {
|
|
ALOGE("DW_CFA_undefined: r%d exceeds supported number of registers (%d)", reg, DWARF_REGISTERS);
|
|
return false;
|
|
}
|
|
ALOGV("DW_CFA_same_value: r%d", reg);
|
|
break;
|
|
case DW_CFA_register: // probably we don't have it on x86.
|
|
if (!try_get_uleb128(memory, ptr, ®, cursor)) return false;
|
|
/* that's new register actually, not offset */
|
|
if (!try_get_uleb128(memory, ptr, &offset, cursor)) return false;
|
|
if (reg > DWARF_REGISTERS || offset > DWARF_REGISTERS) {
|
|
ALOGE("DW_CFA_register: r%d or r%d exceeds supported number of registers (%d)", reg, offset, DWARF_REGISTERS);
|
|
return false;
|
|
}
|
|
dstate->regs[reg].rule = 'r';
|
|
dstate->regs[reg].value = offset;
|
|
ALOGV("DW_CFA_register: r%d = r(%d)", reg, dstate->regs[reg].value);
|
|
break;
|
|
case DW_CFA_remember_state:
|
|
if (*stack_ptr == DWARF_STATES_STACK) {
|
|
ALOGE("DW_CFA_remember_state: states stack overflow %d", *stack_ptr);
|
|
return false;
|
|
}
|
|
stack[(*stack_ptr)++] = *dstate;
|
|
ALOGV("DW_CFA_remember_state: stacktop moves to %d", *stack_ptr);
|
|
break;
|
|
case DW_CFA_restore_state:
|
|
/* We have CIE state saved at 0 position. It's not supposed to be taken
|
|
by DW_CFA_restore_state. */
|
|
if (*stack_ptr == 1) {
|
|
ALOGE("DW_CFA_restore_state: states stack is empty");
|
|
return false;
|
|
}
|
|
/* Don't touch location on restore. */
|
|
uintptr_t saveloc = dstate->loc;
|
|
*dstate = stack[--*stack_ptr];
|
|
dstate->loc = saveloc;
|
|
ALOGV("DW_CFA_restore_state: stacktop moves to %d", *stack_ptr);
|
|
break;
|
|
case DW_CFA_def_cfa:
|
|
if (!try_get_uleb128(memory, ptr, ®, cursor)) return false;
|
|
if (!try_get_uleb128(memory, ptr, &offset, cursor)) return false;
|
|
dstate->cfa_reg = reg;
|
|
dstate->cfa_off = offset;
|
|
ALOGV("DW_CFA_def_cfa: %x(r%d)", offset, reg);
|
|
break;
|
|
case DW_CFA_def_cfa_register:
|
|
if (!try_get_uleb128(memory, ptr, ®, cursor)) {
|
|
return false;
|
|
}
|
|
dstate->cfa_reg = reg;
|
|
ALOGV("DW_CFA_def_cfa_register: r%d", reg);
|
|
break;
|
|
case DW_CFA_def_cfa_offset:
|
|
if (!try_get_uleb128(memory, ptr, &offset, cursor)) {
|
|
return false;
|
|
}
|
|
dstate->cfa_off = offset;
|
|
ALOGV("DW_CFA_def_cfa_offset: %x", offset);
|
|
break;
|
|
default:
|
|
ALOGE("unrecognized DW_CFA_* instruction: 0x%x", inst);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Restoring particular register value based on dwarf state. */
|
|
static bool get_old_register_value(const memory_t* memory, uint32_t cfa,
|
|
dwarf_state_t* dstate, uint8_t reg,
|
|
unwind_state_t* state, unwind_state_t* newstate) {
|
|
uint32_t addr;
|
|
switch (dstate->regs[reg].rule) {
|
|
case 0:
|
|
/* We don't have dstate updated for this register, so assuming value kept the same.
|
|
Normally we should look into state and return current value as the old one
|
|
but we don't have all registers in state to handle this properly */
|
|
ALOGV("get_old_register_value: value of r%d is the same", reg);
|
|
// for ESP if it's not updated by dwarf rule we assume it's equal to CFA
|
|
if (reg == DWARF_ESP) {
|
|
ALOGV("get_old_register_value: adjusting esp to CFA: 0x%x", cfa);
|
|
newstate->reg[reg] = cfa;
|
|
} else {
|
|
newstate->reg[reg] = state->reg[reg];
|
|
}
|
|
break;
|
|
case 'o':
|
|
addr = cfa + (int32_t)dstate->regs[reg].value;
|
|
if (!try_get_word(memory, addr, &newstate->reg[reg])) {
|
|
ALOGE("get_old_register_value: can't read from 0x%x", addr);
|
|
return false;
|
|
}
|
|
ALOGV("get_old_register_value: r%d at 0x%x is 0x%x", reg, addr, newstate->reg[reg]);
|
|
break;
|
|
case 'r':
|
|
/* We don't have all registers in state so don't even try to look at 'r' */
|
|
ALOGE("get_old_register_value: register lookup not implemented yet");
|
|
break;
|
|
default:
|
|
ALOGE("get_old_register_value: unexpected rule:%c value:%d for register %d",
|
|
dstate->regs[reg].rule, (int32_t)dstate->regs[reg].value, reg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Updaing state based on dwarf state. */
|
|
static bool update_state(const memory_t* memory, unwind_state_t* state,
|
|
dwarf_state_t* dstate, cie_info_t* cie_info) {
|
|
unwind_state_t newstate;
|
|
/* We can restore more registers here if we need them. Meanwile doing minimal work here. */
|
|
/* Getting CFA. */
|
|
uintptr_t cfa = 0;
|
|
if (dstate->cfa_reg == DWARF_ESP) {
|
|
cfa = state->reg[DWARF_ESP] + dstate->cfa_off;
|
|
} else if (dstate->cfa_reg == DWARF_EBP) {
|
|
cfa = state->reg[DWARF_EBP] + dstate->cfa_off;
|
|
} else {
|
|
ALOGE("update_state: unexpected CFA register: %d", dstate->cfa_reg);
|
|
return false;
|
|
}
|
|
ALOGV("update_state: new CFA: 0x%x", cfa);
|
|
/* Getting EIP. */
|
|
if (!get_old_register_value(memory, cfa, dstate, DWARF_EIP, state, &newstate)) return false;
|
|
/* Getting EBP. */
|
|
if (!get_old_register_value(memory, cfa, dstate, DWARF_EBP, state, &newstate)) return false;
|
|
/* Getting ESP. */
|
|
if (!get_old_register_value(memory, cfa, dstate, DWARF_ESP, state, &newstate)) return false;
|
|
|
|
ALOGV("update_state: IP: 0x%x; restore IP: 0x%x", state->reg[DWARF_EIP], newstate.reg[DWARF_EIP]);
|
|
ALOGV("update_state: EBP: 0x%x; restore EBP: 0x%x", state->reg[DWARF_EBP], newstate.reg[DWARF_EBP]);
|
|
ALOGV("update_state: ESP: 0x%x; restore ESP: 0x%x", state->reg[DWARF_ESP], newstate.reg[DWARF_ESP]);
|
|
*state = newstate;
|
|
return true;
|
|
}
|
|
|
|
/* Execute CIE and FDE instructions for FDE found with find_fde. */
|
|
static bool execute_fde(const memory_t* memory,
|
|
const map_info_t* map_info_list,
|
|
uintptr_t fde,
|
|
unwind_state_t* state) {
|
|
uint32_t fde_length = 0;
|
|
uint32_t cie_length = 0;
|
|
uintptr_t cie = 0;
|
|
uintptr_t cie_offset = 0;
|
|
cie_info_t cie_i;
|
|
cie_info_t* cie_info = &cie_i;
|
|
fde_info_t fde_i;
|
|
fde_info_t* fde_info = &fde_i;
|
|
dwarf_state_t dwarf_state;
|
|
dwarf_state_t* dstate = &dwarf_state;
|
|
dwarf_state_t stack[DWARF_STATES_STACK];
|
|
uint8_t stack_ptr = 0;
|
|
|
|
memset(dstate, 0, sizeof(dwarf_state_t));
|
|
memset(cie_info, 0, sizeof(cie_info_t));
|
|
memset(fde_info, 0, sizeof(fde_info_t));
|
|
|
|
/* Read common CIE or FDE area:
|
|
1st word is length;
|
|
2nd word is ID: 0 for CIE, CIE pointer for FDE.
|
|
*/
|
|
if (!try_get_word(memory, fde, &fde_length)) {
|
|
return false;
|
|
}
|
|
if ((int32_t)fde_length == -1) {
|
|
ALOGV("execute_fde: 64-bit dwarf detected, not implemented yet");
|
|
return false;
|
|
}
|
|
if (!try_get_word(memory, fde + 4, &cie_offset)) {
|
|
return false;
|
|
}
|
|
if (cie_offset == 0) {
|
|
/* This is CIE. We shouldn't be here normally. */
|
|
cie = fde;
|
|
cie_length = fde_length;
|
|
} else {
|
|
/* Find CIE. */
|
|
/* Positive cie_offset goes backward from current field. */
|
|
cie = fde + 4 - cie_offset;
|
|
if (!try_get_word(memory, cie, &cie_length)) {
|
|
return false;
|
|
}
|
|
if ((int32_t)cie_length == -1) {
|
|
ALOGV("execute_fde: 64-bit dwarf detected, not implemented yet");
|
|
return false;
|
|
}
|
|
if (!try_get_word(memory, cie + 4, &cie_offset)) {
|
|
return false;
|
|
}
|
|
if (cie_offset != 0) {
|
|
ALOGV("execute_fde: can't find CIE");
|
|
return false;
|
|
}
|
|
}
|
|
ALOGV("execute_fde: FDE length: %d", fde_length);
|
|
ALOGV("execute_fde: CIE pointer: %x", cie);
|
|
ALOGV("execute_fde: CIE length: %d", cie_length);
|
|
|
|
/* Read CIE:
|
|
Augmentation independent:
|
|
1st byte is version;
|
|
next x bytes is /0 terminated augmentation string;
|
|
next x bytes is unsigned LEB128 encoded code alignment factor;
|
|
next x bytes is signed LEB128 encoded data alignment factor;
|
|
next 1 (CIE version 1) or x (CIE version 3 unsigned LEB128) bytes is return register column;
|
|
Augmentation dependent:
|
|
if 'z' next x bytes is unsigned LEB128 encoded augmentation data size;
|
|
if 'L' next 1 byte is LSDA encoding;
|
|
if 'R' next 1 byte is FDE encoding;
|
|
if 'S' CIE represents signal handler stack frame;
|
|
if 'P' next 1 byte is personality encoding folowed by personality function pointer;
|
|
Next x bytes is CIE program.
|
|
*/
|
|
|
|
uint32_t c = 8;
|
|
if (!try_get_byte(memory, cie, &cie_info->version, &c)) {
|
|
return false;
|
|
}
|
|
ALOGV("execute_fde: CIE version: %d", cie_info->version);
|
|
uint8_t ch;
|
|
do {
|
|
if (!try_get_byte(memory, cie, &ch, &c)) {
|
|
return false;
|
|
}
|
|
switch (ch) {
|
|
case '\0': break;
|
|
case 'z': cie_info->aug_z = 1; break;
|
|
case 'L': cie_info->aug_L = 1; break;
|
|
case 'R': cie_info->aug_R = 1; break;
|
|
case 'S': cie_info->aug_S = 1; break;
|
|
case 'P': cie_info->aug_P = 1; break;
|
|
default:
|
|
ALOGV("execute_fde: Unrecognized CIE augmentation char: '%c'", ch);
|
|
return false;
|
|
break;
|
|
}
|
|
} while (ch);
|
|
if (!try_get_uleb128(memory, cie, &cie_info->code_align, &c)) {
|
|
return false;
|
|
}
|
|
if (!try_get_sleb128(memory, cie, &cie_info->data_align, &c)) {
|
|
return false;
|
|
}
|
|
if (cie_info->version >= 3) {
|
|
if (!try_get_uleb128(memory, cie, &cie_info->reg, &c)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!try_get_byte(memory, cie, (uint8_t*)&cie_info->reg, &c)) {
|
|
return false;
|
|
}
|
|
}
|
|
ALOGV("execute_fde: CIE code alignment factor: %d", cie_info->code_align);
|
|
ALOGV("execute_fde: CIE data alignment factor: %d", cie_info->data_align);
|
|
if (cie_info->aug_z) {
|
|
if (!try_get_uleb128(memory, cie, &cie_info->aug_z, &c)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (cie_info->aug_L) {
|
|
if (!try_get_byte(memory, cie, &cie_info->aug_L, &c)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
/* Default encoding. */
|
|
cie_info->aug_L = DW_EH_PE_absptr;
|
|
}
|
|
if (cie_info->aug_R) {
|
|
if (!try_get_byte(memory, cie, &cie_info->aug_R, &c)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
/* Default encoding. */
|
|
cie_info->aug_R = DW_EH_PE_absptr;
|
|
}
|
|
if (cie_info->aug_P) {
|
|
/* Get encoding of personality routine pointer. We don't use it now. */
|
|
if (!try_get_byte(memory, cie, (uint8_t*)&cie_info->aug_P, &c)) {
|
|
return false;
|
|
}
|
|
/* Get routine pointer. */
|
|
if (!read_dwarf(memory, cie, &cie_info->aug_P, (uint8_t)cie_info->aug_P, &c)) {
|
|
return false;
|
|
}
|
|
}
|
|
/* CIE program. */
|
|
/* Length field itself (4 bytes) is not included into length. */
|
|
stack[0] = *dstate;
|
|
stack_ptr = 1;
|
|
while (c < cie_length + 4) {
|
|
if (!execute_dwarf(memory, cie, cie_info, dstate, &c, stack, &stack_ptr)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* We went directly to CIE. Normally it shouldn't occur. */
|
|
if (cie == fde) return true;
|
|
|
|
/* Go back to FDE. */
|
|
c = 8;
|
|
/* Read FDE:
|
|
Augmentation independent:
|
|
next x bytes (encoded as specified in CIE) is FDE starting address;
|
|
next x bytes (encoded as specified in CIE) is FDE number of instructions covered;
|
|
Augmentation dependent:
|
|
if 'z' next x bytes is unsigned LEB128 encoded augmentation data size;
|
|
if 'L' next x bytes is LSDA pointer (encoded as specified in CIE);
|
|
Next x bytes is FDE program.
|
|
*/
|
|
if (!read_dwarf(memory, fde, &fde_info->start, (uint8_t)cie_info->aug_R, &c)) {
|
|
return false;
|
|
}
|
|
dstate->loc = fde_info->start;
|
|
ALOGV("execute_fde: FDE start: %x", dstate->loc);
|
|
if (!read_dwarf(memory, fde, &fde_info->length, 0, &c)) {
|
|
return false;
|
|
}
|
|
ALOGV("execute_fde: FDE length: %x", fde_info->length);
|
|
if (cie_info->aug_z) {
|
|
if (!try_get_uleb128(memory, fde, &fde_info->aug_z, &c)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (cie_info->aug_L && cie_info->aug_L != DW_EH_PE_omit) {
|
|
if (!read_dwarf(memory, fde, &fde_info->aug_L, cie_info->aug_L, &c)) {
|
|
return false;
|
|
}
|
|
}
|
|
/* FDE program. */
|
|
/* Length field itself (4 bytes) is not included into length. */
|
|
/* Save CIE state as 0 element of stack. Used by DW_CFA_restore. */
|
|
stack[0] = *dstate;
|
|
stack_ptr = 1;
|
|
while (c < fde_length + 4 && state->reg[DWARF_EIP] >= dstate->loc) {
|
|
if (!execute_dwarf(memory, fde, cie_info, dstate, &c, stack, &stack_ptr)) {
|
|
return false;
|
|
}
|
|
ALOGV("IP: %x, LOC: %x", state->reg[DWARF_EIP], dstate->loc);
|
|
}
|
|
|
|
return update_state(memory, state, dstate, cie_info);
|
|
}
|
|
|
|
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;
|
|
|
|
ALOGV("Unwinding tid: %d", memory->tid);
|
|
ALOGV("IP: %x", state->reg[DWARF_EIP]);
|
|
ALOGV("BP: %x", state->reg[DWARF_EBP]);
|
|
ALOGV("SP: %x", state->reg[DWARF_ESP]);
|
|
|
|
for (size_t index = 0; returned_frames < max_depth; index++) {
|
|
uintptr_t fde = find_fde(memory, map_info_list, state->reg[DWARF_EIP]);
|
|
/* FDE is not found, it may happen if stack is corrupted or calling wrong adress.
|
|
Getting return address from stack.
|
|
*/
|
|
if (!fde) {
|
|
uint32_t ip;
|
|
ALOGV("trying to restore registers from stack");
|
|
if (!try_get_word(memory, state->reg[DWARF_EBP] + 4, &ip) ||
|
|
ip == state->reg[DWARF_EIP]) {
|
|
ALOGV("can't get IP from stack");
|
|
break;
|
|
}
|
|
/* We've been able to get IP from stack so recording the frame before continue. */
|
|
backtrace_frame_t* frame = add_backtrace_entry(
|
|
index ? rewind_pc_arch(memory, state->reg[DWARF_EIP]) : state->reg[DWARF_EIP],
|
|
backtrace, ignore_depth, max_depth,
|
|
&ignored_frames, &returned_frames);
|
|
state->reg[DWARF_EIP] = ip;
|
|
state->reg[DWARF_ESP] = state->reg[DWARF_EBP] + 8;
|
|
if (!try_get_word(memory, state->reg[DWARF_EBP], &state->reg[DWARF_EBP])) {
|
|
ALOGV("can't get EBP from stack");
|
|
break;
|
|
}
|
|
ALOGV("restore IP: %x", state->reg[DWARF_EIP]);
|
|
ALOGV("restore BP: %x", state->reg[DWARF_EBP]);
|
|
ALOGV("restore SP: %x", state->reg[DWARF_ESP]);
|
|
continue;
|
|
}
|
|
backtrace_frame_t* frame = add_backtrace_entry(
|
|
index ? rewind_pc_arch(memory, state->reg[DWARF_EIP]) : state->reg[DWARF_EIP],
|
|
backtrace, ignore_depth, max_depth,
|
|
&ignored_frames, &returned_frames);
|
|
|
|
uint32_t stack_top = state->reg[DWARF_ESP];
|
|
|
|
if (!execute_fde(memory, map_info_list, fde, state)) break;
|
|
|
|
if (frame) {
|
|
frame->stack_top = stack_top;
|
|
if (stack_top < state->reg[DWARF_ESP]) {
|
|
frame->stack_size = state->reg[DWARF_ESP] - stack_top;
|
|
}
|
|
}
|
|
ALOGV("Stack: 0x%x ... 0x%x - %d bytes", frame->stack_top, state->reg[DWARF_ESP], frame->stack_size);
|
|
}
|
|
return returned_frames;
|
|
}
|
|
|
|
ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo __attribute__((unused)), 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;
|
|
|
|
unwind_state_t state;
|
|
#if defined(__APPLE__)
|
|
state.reg[DWARF_EBP] = uc->uc_mcontext->__ss.__ebp;
|
|
state.reg[DWARF_ESP] = uc->uc_mcontext->__ss.__esp;
|
|
state.reg[DWARF_EIP] = uc->uc_mcontext->__ss.__eip;
|
|
#else
|
|
state.reg[DWARF_EBP] = uc->uc_mcontext.gregs[REG_EBP];
|
|
state.reg[DWARF_ESP] = uc->uc_mcontext.gregs[REG_ESP];
|
|
state.reg[DWARF_EIP] = uc->uc_mcontext.gregs[REG_EIP];
|
|
#endif
|
|
|
|
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,
|
|
backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth) {
|
|
#if defined(__APPLE__)
|
|
return -1;
|
|
#else
|
|
pt_regs_x86_t regs;
|
|
if (ptrace(PTRACE_GETREGS, tid, 0, ®s)) {
|
|
return -1;
|
|
}
|
|
|
|
unwind_state_t state;
|
|
state.reg[DWARF_EBP] = regs.ebp;
|
|
state.reg[DWARF_EIP] = regs.eip;
|
|
state.reg[DWARF_ESP] = regs.esp;
|
|
|
|
memory_t memory;
|
|
init_memory_ptrace(&memory, tid);
|
|
return unwind_backtrace_common(&memory, context->map_info_list,
|
|
&state, backtrace, ignore_depth, max_depth);
|
|
#endif
|
|
}
|