From owner-svn-src-all@FreeBSD.ORG Thu Jan 17 09:47:57 2013 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.FreeBSD.org [8.8.178.115]) by hub.freebsd.org (Postfix) with ESMTP id A7ED4E64; Thu, 17 Jan 2013 09:47:57 +0000 (UTC) (envelope-from andrew@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) by mx1.freebsd.org (Postfix) with ESMTP id 8267EFCA; Thu, 17 Jan 2013 09:47:57 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.5/8.14.5) with ESMTP id r0H9lvZu088576; Thu, 17 Jan 2013 09:47:57 GMT (envelope-from andrew@svn.freebsd.org) Received: (from andrew@localhost) by svn.freebsd.org (8.14.5/8.14.5/Submit) id r0H9lvRG088575; Thu, 17 Jan 2013 09:47:57 GMT (envelope-from andrew@svn.freebsd.org) Message-Id: <201301170947.r0H9lvRG088575@svn.freebsd.org> From: Andrew Turner Date: Thu, 17 Jan 2013 09:47:57 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r245549 - head/sys/arm/arm X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 17 Jan 2013 09:47:57 -0000 Author: andrew Date: Thu Jan 17 09:47:56 2013 New Revision: 245549 URL: http://svnweb.freebsd.org/changeset/base/245549 Log: Implement stack unwinding based on section 9 of the "Exception handling ABI for the ARM architecture" documentation. The unwind tables are currently not stored in the kernel but will be added later. Modified: head/sys/arm/arm/db_trace.c Modified: head/sys/arm/arm/db_trace.c ============================================================================== --- head/sys/arm/arm/db_trace.c Thu Jan 17 09:37:42 2013 (r245548) +++ head/sys/arm/arm/db_trace.c Thu Jan 17 09:47:56 2013 (r245549) @@ -50,6 +50,395 @@ __FBSDID("$FreeBSD$"); #include #include +#ifdef __ARM_EABI__ +/* + * Definitions for the instruction interpreter. + * + * The ARM EABI specifies how to perform the frame unwinding in the + * Exception Handling ABI for the ARM Architecture document. To perform + * the unwind we need to know the initial frame pointer, stack pointer, + * link register and program counter. We then find the entry within the + * index table that points to the function the program counter is within. + * This gives us either a list of three instructions to process, a 31-bit + * relative offset to a table of instructions, or a value telling us + * we can't unwind any further. + * + * When we have the instructions to process we need to decode them + * following table 4 in section 9.3. This describes a collection of bit + * patterns to encode that steps to take to update the stack pointer and + * link register to the correct values at the start of the function. + */ + +/* A special case when we are unable to unwind past this function */ +#define EXIDX_CANTUNWIND 1 + +/* The register names */ +#define FP 11 +#define SP 13 +#define LR 14 +#define PC 15 + +/* + * These are set in the linker script. Their addresses will be + * either the start or end of the exception table or index. + */ +extern int extab_start, extab_end, exidx_start, exidx_end; + +/* + * Entry types. + * These are the only entry types that have been seen in the kernel. + */ +#define ENTRY_MASK 0xff000000 +#define ENTRY_ARM_SU16 0x80000000 +#define ENTRY_ARM_LU16 0x81000000 + +/* Instruction masks. */ +#define INSN_VSP_MASK 0xc0 +#define INSN_VSP_SIZE_MASK 0x3f +#define INSN_STD_MASK 0xf0 +#define INSN_STD_DATA_MASK 0x0f +#define INSN_POP_TYPE_MASK 0x08 +#define INSN_POP_COUNT_MASK 0x07 +#define INSN_VSP_LARGE_INC_MASK 0xff + +/* Instruction definitions */ +#define INSN_VSP_INC 0x00 +#define INSN_VSP_DEC 0x40 +#define INSN_POP_MASKED 0x80 +#define INSN_VSP_REG 0x90 +#define INSN_POP_COUNT 0xa0 +#define INSN_FINISH 0xb0 +#define INSN_VSP_LARGE_INC 0xb2 + +/* An item in the exception index table */ +struct unwind_idx { + uint32_t offset; + uint32_t insn; +}; + +/* The state of the unwind process */ +struct unwind_state { + uint32_t registers[16]; + uint32_t start_pc; + uint32_t *insn; + u_int entries; + u_int byte; + uint16_t update_mask; +}; + +/* We need to provide these but never use them */ +void __aeabi_unwind_cpp_pr0(void); +void __aeabi_unwind_cpp_pr1(void); +void __aeabi_unwind_cpp_pr2(void); + +void +__aeabi_unwind_cpp_pr0(void) +{ + panic("__aeabi_unwind_cpp_pr0"); +} + +void +__aeabi_unwind_cpp_pr1(void) +{ + panic("__aeabi_unwind_cpp_pr1"); +} + +void +__aeabi_unwind_cpp_pr2(void) +{ + panic("__aeabi_unwind_cpp_pr2"); +} + +/* Expand a 31-bit signed value to a 32-bit signed value */ +static __inline int32_t +db_expand_prel31(uint32_t prel31) +{ + + return ((int32_t)(prel31 & 0x7fffffffu) << 1) / 2; +} + +/* + * Perform a binary search of the index table to find the function + * with the largest address that doesn't exceed addr. + */ +static struct unwind_idx * +db_find_index(uint32_t addr) +{ + unsigned int min, mid, max; + struct unwind_idx *start; + struct unwind_idx *item; + int32_t prel31_addr; + uint32_t func_addr; + + start = (struct unwind_idx *)&exidx_start; + + min = 0; + max = (&exidx_end - &exidx_start) / 2; + + while (min != max) { + mid = min + (max - min + 1) / 2; + + item = &start[mid]; + + prel31_addr = db_expand_prel31(item->offset); + func_addr = (uint32_t)&item->offset + prel31_addr; + + if (func_addr <= addr) { + min = mid; + } else { + max = mid - 1; + } + } + + return &start[min]; +} + +/* Reads the next byte from the instruction list */ +static uint8_t +db_unwind_exec_read_byte(struct unwind_state *state) +{ + uint8_t insn; + + /* Read the unwind instruction */ + insn = (*state->insn) >> (state->byte * 8); + + /* Update the location of the next instruction */ + if (state->byte == 0) { + state->byte = 3; + state->insn++; + state->entries--; + } else + state->byte--; + + return insn; +} + +/* Executes the next instruction on the list */ +static int +db_unwind_exec_insn(struct unwind_state *state) +{ + unsigned int insn; + uint32_t *vsp = (uint32_t *)state->registers[SP]; + int update_vsp = 0; + + /* This should never happen */ + if (state->entries == 0) + return 1; + + /* Read the next instruction */ + insn = db_unwind_exec_read_byte(state); + + if ((insn & INSN_VSP_MASK) == INSN_VSP_INC) { + state->registers[SP] += ((insn & INSN_VSP_SIZE_MASK) << 2) + 4; + + } else if ((insn & INSN_VSP_MASK) == INSN_VSP_DEC) { + state->registers[SP] -= ((insn & INSN_VSP_SIZE_MASK) << 2) + 4; + + } else if ((insn & INSN_STD_MASK) == INSN_POP_MASKED) { + unsigned int mask, reg; + + /* Load the mask */ + mask = db_unwind_exec_read_byte(state); + mask |= (insn & INSN_STD_DATA_MASK) << 8; + + /* We have a refuse to unwind instruction */ + if (mask == 0) + return 1; + + /* Update SP */ + update_vsp = 1; + + /* Load the registers */ + for (reg = 4; mask && reg < 16; mask >>= 1, reg++) { + if (mask & 1) { + state->registers[reg] = *vsp++; + state->update_mask |= 1 << reg; + + /* If we have updated SP kep its value */ + if (reg == SP) + update_vsp = 0; + } + } + + } else if ((insn & INSN_STD_MASK) == INSN_VSP_REG && + ((insn & INSN_STD_DATA_MASK) != 13) && + ((insn & INSN_STD_DATA_MASK) != 15)) { + /* sp = register */ + state->registers[SP] = + state->registers[insn & INSN_STD_DATA_MASK]; + + } else if ((insn & INSN_STD_MASK) == INSN_POP_COUNT) { + unsigned int count, reg; + + /* Read how many registers to load */ + count = insn & INSN_POP_COUNT_MASK; + + /* Update sp */ + update_vsp = 1; + + /* Pop the registers */ + for (reg = 4; reg <= 4 + count; reg++) { + state->registers[reg] = *vsp++; + state->update_mask |= 1 << reg; + } + + /* Check if we are in the pop r14 version */ + if ((insn & INSN_POP_TYPE_MASK) != 0) { + state->registers[14] = *vsp++; + } + + } else if (insn == INSN_FINISH) { + /* Stop processing */ + state->entries = 0; + + } else if ((insn & INSN_VSP_LARGE_INC_MASK) == INSN_VSP_LARGE_INC) { + unsigned int uleb128; + + /* Read the increment value */ + uleb128 = db_unwind_exec_read_byte(state); + + state->registers[SP] += 0x204 + (uleb128 << 2); + + } else { + /* We hit a new instruction that needs to be implemented */ + db_printf("Unhandled instruction %.2x\n", insn); + return 1; + } + + if (update_vsp) { + state->registers[SP] = (uint32_t)vsp; + } + +#if 0 + db_printf("fp = %08x, sp = %08x, lr = %08x, pc = %08x\n", + state->registers[FP], state->registers[SP], state->registers[LR], + state->registers[PC]); +#endif + + return 0; +} + +/* Performs the unwind of a function */ +static int +db_unwind_tab(struct unwind_state *state) +{ + uint32_t entry; + + /* Set PC to a known value */ + state->registers[PC] = 0; + + /* Read the personality */ + entry = *state->insn & ENTRY_MASK; + + if (entry == ENTRY_ARM_SU16) { + state->byte = 2; + state->entries = 1; + } else if (entry == ENTRY_ARM_LU16) { + state->byte = 1; + state->entries = ((*state->insn >> 16) & 0xFF) + 1; + } else { + db_printf("Unknown entry: %x\n", entry); + return 1; + } + + while (state->entries > 0) { + if (db_unwind_exec_insn(state) != 0) + return 1; + } + + /* + * The program counter was not updated, load it from the link register. + */ + if (state->registers[PC] == 0) + state->registers[PC] = state->registers[LR]; + + return 0; +} + +static void +db_stack_trace_cmd(struct unwind_state *state) +{ + struct unwind_idx *index; + const char *name; + db_expr_t value; + db_expr_t offset; + c_db_sym_t sym; + u_int reg, i; + char *sep; + + while (1) { + /* Reset the mask of updated registers */ + state->update_mask = 0; + + /* The pc value is correct and will be overwritten, save it */ + state->start_pc = state->registers[PC]; + + /* Find the item to run */ + index = db_find_index(state->start_pc); + + if (index->insn == EXIDX_CANTUNWIND) { + printf("Unable to unwind\n"); + break; + } else if (index->insn & (1 << 31)) { + /* The data is within the instruction */ + state->insn = &index->insn; + } else { + /* We have a prel31 offset to the unwind table */ + uint32_t prel31_tbl = db_expand_prel31(index->insn); + + state->insn = (uint32_t *)((uintptr_t)&index->insn + + prel31_tbl); + } + + /* Run the unwind function */ + if (db_unwind_tab(state) != 0) + break; + + /* This is not a kernel address, stop processing */ + if (state->registers[PC] < VM_MIN_KERNEL_ADDRESS) + break; + + /* Print the frame details */ + sym = db_search_symbol(state->start_pc, DB_STGY_ANY, &offset); + if (sym == C_DB_SYM_NULL) { + value = 0; + name = "(null)"; + } else + db_symbol_values(sym, &name, &value); + db_printf("%s() at ", name); + db_printsym(state->start_pc, DB_STGY_PROC); + db_printf("\n"); + db_printf("\t pc = 0x%08x lr = 0x%08x (", state->start_pc, + state->registers[LR]); + db_printsym(state->registers[LR], DB_STGY_PROC); + db_printf(")\n"); + db_printf("\t sp = 0x%08x fp = 0x%08x", + state->registers[SP], state->registers[FP]); + + /* Don't print the registers we have already printed */ + state->update_mask &= ~((1 << SP) | (1 << FP) | (1 << LR) | + (1 << PC)); + sep = "\n\t"; + for (i = 0, reg = 0; state->update_mask != 0; + state->update_mask >>= 1, reg++) { + if ((state->update_mask & 1) != 0) { + db_printf("%s%sr%d = 0x%08x", sep, + (reg < 10) ? " " : "", reg, + state->registers[reg]); + i++; + if (i == 2) { + sep = "\n\t"; + i = 0; + } else + sep = " "; + + } + } + db_printf("\n"); + } +} +#endif + /* * APCS stack frames are awkward beasts, so I don't think even trying to use * a structure to represent them is a good idea. @@ -78,6 +467,7 @@ __FBSDID("$FreeBSD$"); * fields are actually present. */ +#ifndef __ARM_EABI__ /* The frame format is differend in AAPCS */ static void db_stack_trace_cmd(db_expr_t addr, db_expr_t count, boolean_t kernel_only) { @@ -171,6 +561,7 @@ db_stack_trace_cmd(db_expr_t addr, db_ex } } } +#endif /* XXX stubs */ void @@ -193,11 +584,24 @@ db_md_set_watchpoint(db_expr_t addr, db_ int db_trace_thread(struct thread *thr, int count) { +#ifdef __ARM_EABI__ + struct unwind_state state; +#endif struct pcb *ctx; if (thr != curthread) { ctx = kdb_thr_ctx(thr); + +#ifdef __ARM_EABI__ + state.registers[FP] = ctx->un_32.pcb32_r11; + state.registers[SP] = ctx->un_32.pcb32_sp; + state.registers[LR] = ctx->un_32.pcb32_lr; + state.registers[PC] = ctx->un_32.pcb32_pc; + + db_stack_trace_cmd(&state); +#else db_stack_trace_cmd(ctx->un_32.pcb32_r11, -1, TRUE); +#endif } else db_trace_self(); return (0); @@ -206,8 +610,20 @@ db_trace_thread(struct thread *thr, int void db_trace_self(void) { +#ifdef __ARM_EABI__ + struct unwind_state state; + register uint32_t sp __asm__ ("sp"); + + state.registers[FP] = (uint32_t)__builtin_frame_address(0); + state.registers[SP] = (uint32_t)sp; + state.registers[LR] = (uint32_t)__builtin_return_address(0); + state.registers[PC] = (uint32_t)db_trace_self; + + db_stack_trace_cmd(&state); +#else db_addr_t addr; addr = (db_addr_t)__builtin_frame_address(0); db_stack_trace_cmd(addr, -1, FALSE); +#endif }