crash/unwind_arm.c

756 lines
19 KiB
C

/*
* Stack unwinding support for ARM
*
* This code is derived from the kernel source:
* arch/arm/kernel/unwind.c
* Copyright (C) 2008 ARM Limited
*
* Created by: Mika Westerberg <ext-mika.1.westerberg@nokia.com>
* Copyright (C) 2010 Nokia Corporation
*
* For more information about ARM unwind tables see "Exception handling ABI for
* the ARM architecture" document at:
*
* http://infocenter.arm.com/help/topic/com.arm.doc.subset.swdev.abi/index.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifdef ARM
#include "defs.h"
/**
* struct unwind_idx - index table entry
* @addr: prel31 offset to the start of the function
* @insn: index table entry.
*
* @insn can be encoded as follows:
* 1. if bit31 is clear this points to the start of the EHT entry
* (prel31 offset)
* 2. if bit31 is set, this contains the EHT entry itself
* 3. if 0x1, cannot unwind.
*/
struct unwind_idx {
ulong addr;
ulong insn;
};
/**
* struct unwind_table - per-module unwind table
* @idx: pointer to the star of the unwind table
* @start: pointer to the start of the index table
* @end: pointer to the last element +1 of the index table
* @begin_addr: start address which this table covers
* @end_addr: end address which this table covers
* @kv_base: kernel virtual address of the start of the index table
*
* Kernel stores per-module unwind tables in this format. There can be more than
* one table per module as we have different ELF sections in the module.
*/
struct unwind_table {
struct unwind_idx *idx;
struct unwind_idx *start;
struct unwind_idx *end;
ulong begin_addr;
ulong end_addr;
ulong kv_base;
};
/*
* Unwind table pointers to master kernel table and for modules.
*/
static struct unwind_table *kernel_unwind_table;
static struct unwind_table *module_unwind_tables;
struct unwind_ctrl_block {
ulong vrs[16];
ulong insn;
ulong insn_kvaddr;
int entries;
int byte;
};
struct stackframe {
ulong fp;
ulong sp;
ulong lr;
ulong pc;
};
enum regs {
FP = 11,
SP = 13,
LR = 14,
PC = 15,
};
static int init_kernel_unwind_table(void);
static int read_module_unwind_table(struct unwind_table *, ulong);
static int init_module_unwind_tables(void);
static int unwind_get_insn(struct unwind_ctrl_block *);
static ulong unwind_get_byte(struct unwind_ctrl_block *);
static ulong get_value_from_stack(ulong *);
static int unwind_exec_insn(struct unwind_ctrl_block *);
static int is_core_kernel_text(ulong);
static struct unwind_table *search_table(ulong);
static struct unwind_idx *search_index(const struct unwind_table *, ulong);
static ulong prel31_to_addr(ulong, ulong);
static void index_prel31_to_addr(struct unwind_table *);
static int unwind_frame(struct stackframe *, ulong);
/*
* Function reads in-memory kernel and module unwind tables and makes
* local copy of them for unwinding. If unwinding tables cannot be found, this
* function returns FALSE, otherwise TRUE.
*/
int
init_unwind_tables(void)
{
if (!symbol_exists("__start_unwind_idx") ||
!symbol_exists("__stop_unwind_idx") ||
!symbol_exists("__start_unwind_tab") ||
!symbol_exists("__stop_unwind_tab") ||
!symbol_exists("unwind_tables")) {
return FALSE;
}
if (!init_kernel_unwind_table()) {
error(WARNING,
"UNWIND: failed to initialize kernel unwind table\n");
return FALSE;
}
/*
* Initialize symbols for per-module unwind tables. Actually there are
* several tables per module (one per code section).
*/
STRUCT_SIZE_INIT(unwind_table, "unwind_table");
MEMBER_OFFSET_INIT(unwind_table_list, "unwind_table", "list");
MEMBER_OFFSET_INIT(unwind_table_start, "unwind_table", "start");
MEMBER_OFFSET_INIT(unwind_table_stop, "unwind_table", "stop");
MEMBER_OFFSET_INIT(unwind_table_begin_addr, "unwind_table",
"begin_addr");
MEMBER_OFFSET_INIT(unwind_table_end_addr, "unwind_table", "end_addr");
STRUCT_SIZE_INIT(unwind_idx, "unwind_idx");
MEMBER_OFFSET_INIT(unwind_idx_addr, "unwind_idx", "addr");
MEMBER_OFFSET_INIT(unwind_idx_insn, "unwind_idx", "insn");
if (!init_module_unwind_tables()) {
error(WARNING,
"UNWIND: failed to initialize module unwind tables\n");
}
/*
* We abuse DWARF_UNWIND flag a little here as ARM unwinding tables are
* not in DWARF format but we can use the flags to indicate that we have
* unwind tables support ready.
*/
kt->flags |= DWARF_UNWIND_CAPABLE;
kt->flags |= DWARF_UNWIND;
return TRUE;
}
/*
* Allocate and fill master kernel unwind table.
*/
static int
init_kernel_unwind_table(void)
{
ulong idx_start, idx_end, idx_size;
kernel_unwind_table = calloc(sizeof(*kernel_unwind_table), 1);
if (!kernel_unwind_table)
return FALSE;
idx_start = symbol_value("__start_unwind_idx");
idx_end = symbol_value("__stop_unwind_idx");
idx_size = idx_end - idx_start;
kernel_unwind_table->idx = calloc(idx_size, 1);
if (!kernel_unwind_table->idx)
goto fail;
/* now read in the index table */
if (!readmem(idx_start, KVADDR, kernel_unwind_table->idx, idx_size,
"master kernel unwind table", RETURN_ON_ERROR)) {
free(kernel_unwind_table->idx);
goto fail;
}
/*
* Kernel versions before v3.2 (specifically, before commit
* de66a979012db "ARM: 7187/1: fix unwinding for XIP kernels")
* converted the prel31 offsets in the unwind index table to absolute
* addresses on startup. Newer kernels don't perform this conversion,
* and have a slightly more involved search algorithm.
*
* We always just use the older search method (a straightforward binary
* search) and convert the index table offsets ourselves if we detect
* that the kernel didn't do it.
*/
machdep->machspec->unwind_index_prel31 = !is_kernel_text(kernel_unwind_table->idx[0].addr);
kernel_unwind_table->start = kernel_unwind_table->idx;
kernel_unwind_table->end = (struct unwind_idx *)
((char *)kernel_unwind_table->idx + idx_size);
kernel_unwind_table->begin_addr = kernel_unwind_table->start->addr;
kernel_unwind_table->end_addr = (kernel_unwind_table->end - 1)->addr;
kernel_unwind_table->kv_base = idx_start;
if (machdep->machspec->unwind_index_prel31)
index_prel31_to_addr(kernel_unwind_table);
if (CRASHDEBUG(1)) {
fprintf(fp, "UNWIND: master kernel table start\n");
fprintf(fp, "UNWIND: size : %ld\n", idx_size);
fprintf(fp, "UNWIND: start : %p\n", kernel_unwind_table->start);
fprintf(fp, "UNWIND: end : %p\n", kernel_unwind_table->end);
fprintf(fp, "UNWIND: begin_addr: 0x%lx\n",
kernel_unwind_table->begin_addr);
fprintf(fp, "UNWIND: begin_addr: 0x%lx\n",
kernel_unwind_table->end_addr);
fprintf(fp, "UNWIND: master kernel table end\n");
}
return TRUE;
fail:
free(kernel_unwind_table);
return FALSE;
}
/*
* Read single module unwind table from addr.
*/
static int
read_module_unwind_table(struct unwind_table *tbl, ulong addr)
{
ulong idx_start, idx_stop, idx_size;
char *buf;
buf = GETBUF(SIZE(unwind_table));
/*
* First read in the unwind table for this module. It then contains
* pointers to the index table which we will read later.
*/
if (!readmem(addr, KVADDR, buf, SIZE(unwind_table),
"module unwind table", RETURN_ON_ERROR)) {
error(WARNING, "UNWIND: cannot read unwind table\n");
goto fail;
}
#define TABLE_VALUE(b, offs) (*((ulong *)((b) + OFFSET(offs))))
idx_start = TABLE_VALUE(buf, unwind_table_start);
idx_stop = TABLE_VALUE(buf, unwind_table_stop);
idx_size = idx_stop - idx_start;
/*
* We know the size of the index table. Allocate memory for
* the table and read the contents from the kernel memory.
*/
tbl->idx = calloc(idx_size, 1);
if (!tbl->idx)
goto fail;
if (!readmem(idx_start, KVADDR, tbl->idx, idx_size,
"module unwind index table", RETURN_ON_ERROR)) {
free(tbl->idx);
goto fail;
}
tbl->start = &tbl->idx[0];
tbl->end = (struct unwind_idx *)((char *)tbl->start + idx_size);
tbl->begin_addr = TABLE_VALUE(buf, unwind_table_begin_addr);
tbl->end_addr = TABLE_VALUE(buf, unwind_table_end_addr);
tbl->kv_base = idx_start;
if (machdep->machspec->unwind_index_prel31)
index_prel31_to_addr(tbl);
if (CRASHDEBUG(1)) {
fprintf(fp, "UNWIND: module table start\n");
fprintf(fp, "UNWIND: start : %p\n", tbl->start);
fprintf(fp, "UNWIND: end : %p\n", tbl->end);
fprintf(fp, "UNWIND: begin_addr: 0x%lx\n", tbl->begin_addr);
fprintf(fp, "UNWIND: begin_addr: 0x%lx\n", tbl->end_addr);
fprintf(fp, "UNWIND: module table end\n");
}
FREEBUF(buf);
return TRUE;
fail:
FREEBUF(buf);
return FALSE;
}
/*
* Allocate and fill per-module unwind tables.
*/
static int
init_module_unwind_tables(void)
{
ulong head = symbol_value("unwind_tables");
struct unwind_table *tbl;
struct list_data ld;
ulong *table_list;
int cnt, i, n;
BZERO(&ld, sizeof(ld));
ld.start = head;
ld.member_offset = OFFSET(unwind_table_list);
ld.flags = RETURN_ON_LIST_ERROR;
if (CRASHDEBUG(1))
ld.flags |= VERBOSE;
/*
* Iterate through unwind table list and store start address of each
* table in table_list.
*/
hq_open();
cnt = do_list(&ld);
if (cnt == -1) {
error(WARNING, "UNWIND: failed to gather unwind_table list\n");
hq_close();
return FALSE;
}
table_list = (ulong *)GETBUF(cnt * sizeof(ulong));
cnt = retrieve_list(table_list, cnt);
hq_close();
module_unwind_tables = calloc(sizeof(struct unwind_table), cnt);
if (!module_unwind_tables) {
error(WARNING,
"UNWIND: failed to allocate memory for (%d tables)\n",
cnt);
FREEBUF(table_list);
return FALSE;
}
/* we skip the first address as it is just head pointer */
for (i = 1, n = 0; i < cnt; i++, n++) {
tbl = &module_unwind_tables[n];
if (!read_module_unwind_table(tbl, table_list[i]))
goto fail;
}
/* just in case, zero the last entry (again) */
BZERO(&module_unwind_tables[n], sizeof(module_unwind_tables[n]));
FREEBUF(table_list);
return TRUE;
fail:
FREEBUF(table_list);
while (--n >= 0) {
tbl = &module_unwind_tables[n];
free(tbl->idx);
}
free(module_unwind_tables);
module_unwind_tables = NULL;
return FALSE;
}
/*
* Read next unwind instruction pointed by ctrl->insn_kvaddr into
* ctrl->insn. As a side-effect, increase the ctrl->insn_kvaddr to
* point to the next instruction.
*/
static int
unwind_get_insn(struct unwind_ctrl_block *ctrl)
{
if (readmem(ctrl->insn_kvaddr, KVADDR, &ctrl->insn, sizeof(ctrl->insn),
"unwind insn", RETURN_ON_ERROR)) {
ctrl->insn_kvaddr += sizeof(ctrl->insn);
return TRUE;
}
return FALSE;
}
/*
* Return next insn byte from ctl or 0 in case of failure. As a side-effect,
* changes ctrl according the next byte.
*/
static ulong
unwind_get_byte(struct unwind_ctrl_block *ctrl)
{
ulong ret;
if (ctrl->entries <= 0) {
error(WARNING, "UNWIND: corrupt unwind entry\n");
return 0;
}
ret = (ctrl->insn >> (ctrl->byte * 8)) & 0xff;
if (!ctrl->byte && --ctrl->entries > 0) {
if (!unwind_get_insn(ctrl))
return 0;
ctrl->byte = 3;
} else {
ctrl->byte--;
}
return ret;
}
/*
* Gets one value from stack pointed by vsp.
*/
static ulong
get_value_from_stack(ulong *vsp)
{
ulong val;
/*
* We just read the value from kernel memory instead of peeking it from
* the bt->stack.
*/
if (!readmem((ulong)vsp, KVADDR, &val, sizeof(val),
"unwind stack value", RETURN_ON_ERROR)) {
error(FATAL, "unwind: failed to read value from stack\n");
}
return val;
}
/*
* Execute the next unwind instruction.
*/
static int
unwind_exec_insn(struct unwind_ctrl_block *ctrl)
{
ulong insn = unwind_get_byte(ctrl);
if ((insn & 0xc0) == 0) {
/*
* 00xx xxxx: vsp = vsp + (xx xxx << 2) + 4
*
* Note that it seems that there is a typo in the spec and this
* is corrected in kernel.
*/
ctrl->vrs[SP] += ((insn & 0x3f) << 2) + 4;
} else if ((insn & 0xc0) == 0x40) {
/* 00xx xxxx: vsp = vsp + (xx xxx << 2) + 4 */
ctrl->vrs[SP] -= ((insn & 0x3f) << 2) + 4;
} else if ((insn & 0xf0) == 0x80) {
/*
* Pop up to 12 integer registers under masks
* {r15-r12}, {r11-r4}.
*/
ulong mask;
ulong *vsp = (ulong *)ctrl->vrs[SP];
int load_sp, reg = 4;
insn = (insn << 8) | unwind_get_byte(ctrl);
mask = insn & 0x0fff;
if (mask == 0) {
error(WARNING, "UNWIND: refuse to unwind\n");
return FALSE;
}
/* pop {r4-r15} according to mask */
load_sp = mask & (1 << (13 - 4));
while (mask) {
if (mask & 1)
ctrl->vrs[reg] = get_value_from_stack(vsp++);
mask >>= 1;
reg++;
}
if (!load_sp)
ctrl->vrs[SP] = (ulong)vsp;
} else if ((insn & 0xf0) == 0x90 &&
(insn & 0x0d) != 0x0d) {
/* 1001 nnnn: set vsp = r[nnnn] */
ctrl->vrs[SP] = ctrl->vrs[insn & 0x0f];
} else if ((insn & 0xf0) == 0xa0) {
/*
* 1010 0nnn: pop r4-r[4+nnn]
* 1010 1nnn: pop r4-r[4+nnn], r14
*/
ulong *vsp = (ulong *)ctrl->vrs[SP];
int reg;
for (reg = 4; reg <= 4 + (insn & 7); reg++)
ctrl->vrs[reg] = get_value_from_stack(vsp++);
if (insn & 0x80)
ctrl->vrs[14] = get_value_from_stack(vsp++);
ctrl->vrs[SP] = (ulong)vsp;
} else if (insn == 0xb0) {
/* 1011 0000: finish */
if (ctrl->vrs[PC] == 0)
ctrl->vrs[PC] = ctrl->vrs[LR];
/* no further processing */
ctrl->entries = 0;
} else if (insn == 0xb1) {
/* 1011 0001 xxxx yyyy: spare */
ulong mask = unwind_get_byte(ctrl);
ulong *vsp = (ulong *)ctrl->vrs[SP];
int reg = 0;
if (mask == 0 || mask & 0xf0) {
error(WARNING, "UNWIND: spare error\n");
return FALSE;
}
/* pop r0-r3 according to mask */
while (mask) {
if (mask & 1)
ctrl->vrs[reg] = get_value_from_stack(vsp++);
mask >>= 1;
reg++;
}
ctrl->vrs[SP] = (ulong)vsp;
} else if (insn == 0xb2) {
/* 1011 0010 uleb128: vsp = vsp + 0x204 (uleb128 << 2) */
ulong uleb128 = unwind_get_byte(ctrl);
ctrl->vrs[SP] += 0x204 + (uleb128 << 2);
} else {
error(WARNING, "UNWIND: unhandled instruction: %02lx\n", insn);
return FALSE;
}
return TRUE;
}
static int
is_core_kernel_text(ulong pc)
{
ulong text_start = machdep->machspec->kernel_text_start;
ulong text_end = machdep->machspec->kernel_text_end;
if (text_start && text_end)
return (pc >= text_start && pc <= text_end);
return FALSE;
}
static struct unwind_table *
search_table(ulong ip)
{
/*
* First check if this address is in the master kernel unwind table or
* some of the module unwind tables.
*/
if (is_core_kernel_text(ip)) {
return kernel_unwind_table;
} else if (module_unwind_tables) {
struct unwind_table *tbl;
for (tbl = &module_unwind_tables[0]; tbl->idx; tbl++) {
if (ip >= tbl->begin_addr && ip < tbl->end_addr)
return tbl;
}
}
return NULL;
}
static struct unwind_idx *
search_index(const struct unwind_table *tbl, ulong ip)
{
struct unwind_idx *start = tbl->start;
struct unwind_idx *end = tbl->end;
/*
* Do a binary search for the addresses in the index table.
* Addresses are guaranteed to be sorted in ascending order.
*/
while (start < end - 1) {
struct unwind_idx *mid = start + ((end - start + 1) >> 1);
if (ip < mid->addr)
end = mid;
else
start = mid;
}
return start;
}
/*
* Convert a prel31 symbol to an absolute kernel virtual address.
*/
static ulong
prel31_to_addr(ulong addr, ulong insn)
{
/* sign extend to 32 bits */
long offset = ((long)insn << 1) >> 1;
return addr + offset;
}
static void
index_prel31_to_addr(struct unwind_table *tbl)
{
struct unwind_idx *idx = tbl->start;
ulong kvaddr = tbl->kv_base;
for (; idx < tbl->end; idx++, kvaddr += sizeof(struct unwind_idx))
idx->addr = prel31_to_addr(kvaddr, idx->addr);
}
static int
unwind_frame(struct stackframe *frame, ulong stacktop)
{
const struct unwind_table *tbl;
struct unwind_ctrl_block ctrl;
struct unwind_idx *idx;
ulong low, high;
low = frame->sp;
high = stacktop;
if (!is_kernel_text(frame->pc))
return FALSE;
tbl = search_table(frame->pc);
if (!tbl) {
error(WARNING, "UNWIND: cannot find unwind table for %lx\n",
frame->pc);
return FALSE;
}
idx = search_index(tbl, frame->pc);
ctrl.vrs[FP] = frame->fp;
ctrl.vrs[SP] = frame->sp;
ctrl.vrs[LR] = frame->lr;
ctrl.vrs[PC] = 0;
if (CRASHDEBUG(5)) {
fprintf(fp, "UNWIND: >frame: FP=%lx\n", ctrl.vrs[FP]);
fprintf(fp, "UNWIND: >frame: SP=%lx\n", ctrl.vrs[SP]);
fprintf(fp, "UNWIND: >frame: LR=%lx\n", ctrl.vrs[LR]);
fprintf(fp, "UNWIND: >frame: PC=%lx\n", ctrl.vrs[PC]);
}
if (idx->insn == 1) {
/* can't unwind */
return FALSE;
} else if ((idx->insn & 0x80000000) == 0) {
/* insn contains prel31 offset to the EHT entry */
/*
* Calculate a byte offset for idx->insn from the
* start of our copy of the index table. This offset
* is used to get a kernel virtual address of the
* unwind index entry (idx_kvaddr).
*/
ulong idx_offset = (ulong)&idx->insn - (ulong)tbl->start;
ulong idx_kvaddr = tbl->kv_base + idx_offset;
/*
* Now compute a kernel virtual address for the EHT
* entry by adding prel31 offset (idx->insn) to the
* unwind index entry address (idx_kvaddr) and read
* the EHT entry.
*/
ctrl.insn_kvaddr = prel31_to_addr(idx_kvaddr, idx->insn);
if (!unwind_get_insn(&ctrl))
return FALSE;
} else if ((idx->insn & 0xff000000) == 0x80000000) {
/* EHT entry is encoded in the insn itself */
ctrl.insn = idx->insn;
} else {
error(WARNING, "UNWIND: unsupported instruction %lx\n",
idx->insn);
return FALSE;
}
/* check the personality routine */
if ((ctrl.insn & 0xff000000) == 0x80000000) {
/* personality routine 0 */
ctrl.byte = 2;
ctrl.entries = 1;
} else if ((ctrl.insn & 0xff000000) == 0x81000000) {
/* personality routine 1 */
ctrl.byte = 1;
ctrl.entries = 1 + ((ctrl.insn & 0x00ff0000) >> 16);
} else {
error(WARNING, "UNWIND: unsupported personality routine\n");
return FALSE;
}
/* now, execute the instructions */
while (ctrl.entries > 0) {
if (!unwind_exec_insn(&ctrl)) {
error(WARNING, "UNWIND: failed to exec instruction\n");
return FALSE;
}
if (ctrl.vrs[SP] < low || ctrl.vrs[SP] >= high)
return FALSE;
}
if (ctrl.vrs[PC] == 0)
ctrl.vrs[PC] = ctrl.vrs[LR];
if (frame->pc == ctrl.vrs[PC])
return FALSE;
frame->fp = ctrl.vrs[FP];
frame->sp = ctrl.vrs[SP];
frame->lr = ctrl.vrs[LR];
frame->pc = ctrl.vrs[PC];
if (CRASHDEBUG(5)) {
fprintf(fp, "UNWIND: <frame: FP=%lx\n", ctrl.vrs[FP]);
fprintf(fp, "UNWIND: <frame: SP=%lx\n", ctrl.vrs[SP]);
fprintf(fp, "UNWIND: <frame: LR=%lx\n", ctrl.vrs[LR]);
fprintf(fp, "UNWIND: <frame: PC=%lx\n", ctrl.vrs[PC]);
}
return TRUE;
}
void
unwind_backtrace(struct bt_info *bt)
{
struct stackframe frame;
int n = 0;
BZERO(&frame, sizeof(frame));
frame.fp = bt->frameptr;
frame.sp = bt->stkptr;
frame.pc = bt->instptr;
/*
* In case bt->machdep contains pointer to a full register set, we take
* LR from there.
*/
if (bt->machdep) {
const struct arm_pt_regs *regs = bt->machdep;
frame.fp = regs->ARM_fp;
frame.lr = regs->ARM_lr;
}
while (IS_KVADDR(bt->instptr)) {
if (!unwind_frame(&frame, bt->stacktop))
break;
arm_dump_backtrace_entry(bt, n++, frame.lr, frame.sp);
bt->instptr = frame.pc;
bt->stkptr = frame.sp;
}
}
#endif /* ARM */