From fb3f3e7fc6d4afb32f9eba32124beaf40313de3c Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Sun, 16 Aug 2009 15:44:08 +0100 Subject: sh: unwinder: Fix memory leak and create our own kmem cache Plug a memory leak in dwarf_unwinder_dump() where we didn't free the memory that we had previously allocated for the DWARF frames and DWARF registers. Now is also a opportune time to implement our own mempool and kmem cache. It's a good idea to have a certain number of frame and register objects in reserve at all times, so that we are guaranteed to have our allocation satisfied even when memory is scarce. Since we have pools to allocate from we can implement the registers for each frame as a linked list as opposed to a sparsely populated array. Whilst it's true that the lookup time for a linked list is larger than for arrays, there's only usually a maximum of 8 registers per frame. So the overhead isn't that much of a concern. Signed-off-by: Matt Fleming --- arch/sh/kernel/dwarf.c | 201 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 65 deletions(-) (limited to 'arch/sh/kernel') diff --git a/arch/sh/kernel/dwarf.c b/arch/sh/kernel/dwarf.c index d0652153f57..e4810375207 100644 --- a/arch/sh/kernel/dwarf.c +++ b/arch/sh/kernel/dwarf.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,17 @@ #include #include +/* Reserve enough memory for two stack frames */ +#define DWARF_FRAME_MIN_REQ 2 +/* ... with 4 registers per frame. */ +#define DWARF_REG_MIN_REQ (DWARF_FRAME_MIN_REQ * 4) + +static struct kmem_cache *dwarf_frame_cachep; +static mempool_t *dwarf_frame_pool; + +static struct kmem_cache *dwarf_reg_cachep; +static mempool_t *dwarf_reg_pool; + static LIST_HEAD(dwarf_cie_list); static DEFINE_SPINLOCK(dwarf_cie_lock); @@ -33,33 +45,25 @@ static DEFINE_SPINLOCK(dwarf_fde_lock); static struct dwarf_cie *cached_cie; -/* - * Figure out whether we need to allocate some dwarf registers. If dwarf - * registers have already been allocated then we may need to realloc - * them. "reg" is a register number that we need to be able to access - * after this call. +/** + * dwarf_frame_alloc_reg - allocate memory for a DWARF register + * @frame: the DWARF frame whose list of registers we insert on + * @reg_num: the register number + * + * Allocate space for, and initialise, a dwarf reg from + * dwarf_reg_pool and insert it onto the (unsorted) linked-list of + * dwarf registers for @frame. * - * Register numbers start at zero, therefore we need to allocate space - * for "reg" + 1 registers. + * Return the initialised DWARF reg. */ -static void dwarf_frame_alloc_regs(struct dwarf_frame *frame, - unsigned int reg) +static struct dwarf_reg *dwarf_frame_alloc_reg(struct dwarf_frame *frame, + unsigned int reg_num) { - struct dwarf_reg *regs; - unsigned int num_regs = reg + 1; - size_t new_size; - size_t old_size; + struct dwarf_reg *reg; - new_size = num_regs * sizeof(*regs); - old_size = frame->num_regs * sizeof(*regs); - - /* Fast path: don't allocate any regs if we've already got enough. */ - if (frame->num_regs >= num_regs) - return; - - regs = kzalloc(new_size, GFP_ATOMIC); - if (!regs) { - printk(KERN_WARNING "Unable to allocate DWARF registers\n"); + reg = mempool_alloc(dwarf_reg_pool, GFP_ATOMIC); + if (!reg) { + printk(KERN_WARNING "Unable to allocate a DWARF register\n"); /* * Let's just bomb hard here, we have no way to * gracefully recover. @@ -67,13 +71,44 @@ static void dwarf_frame_alloc_regs(struct dwarf_frame *frame, BUG(); } - if (frame->regs) { - memcpy(regs, frame->regs, old_size); - kfree(frame->regs); + reg->number = reg_num; + reg->addr = 0; + reg->flags = 0; + + list_add(®->link, &frame->reg_list); + + return reg; +} + +static void dwarf_frame_free_regs(struct dwarf_frame *frame) +{ + struct dwarf_reg *reg, *n; + + list_for_each_entry_safe(reg, n, &frame->reg_list, link) { + list_del(®->link); + mempool_free(reg, dwarf_reg_pool); + } +} + +/** + * dwarf_frame_reg - return a DWARF register + * @frame: the DWARF frame to search in for @reg_num + * @reg_num: the register number to search for + * + * Lookup and return the dwarf reg @reg_num for this frame. Return + * NULL if @reg_num is an register invalid number. + */ +static struct dwarf_reg *dwarf_frame_reg(struct dwarf_frame *frame, + unsigned int reg_num) +{ + struct dwarf_reg *reg; + + list_for_each_entry(reg, &frame->reg_list, link) { + if (reg->number == reg_num) + return reg; } - frame->regs = regs; - frame->num_regs = num_regs; + return NULL; } /** @@ -347,6 +382,7 @@ static int dwarf_cfa_execute_insns(unsigned char *insn_start, unsigned char insn; unsigned char *current_insn; unsigned int count, delta, reg, expr_len, offset; + struct dwarf_reg *regp; current_insn = insn_start; @@ -369,9 +405,9 @@ static int dwarf_cfa_execute_insns(unsigned char *insn_start, count = dwarf_read_uleb128(current_insn, &offset); current_insn += count; offset *= cie->data_alignment_factor; - dwarf_frame_alloc_regs(frame, reg); - frame->regs[reg].addr = offset; - frame->regs[reg].flags |= DWARF_REG_OFFSET; + regp = dwarf_frame_alloc_reg(frame, reg); + regp->addr = offset; + regp->flags |= DWARF_REG_OFFSET; continue; /* NOTREACHED */ case DW_CFA_restore: @@ -453,17 +489,18 @@ static int dwarf_cfa_execute_insns(unsigned char *insn_start, count = dwarf_read_leb128(current_insn, &offset); current_insn += count; offset *= cie->data_alignment_factor; - dwarf_frame_alloc_regs(frame, reg); - frame->regs[reg].flags |= DWARF_REG_OFFSET; - frame->regs[reg].addr = offset; + regp = dwarf_frame_alloc_reg(frame, reg); + regp->flags |= DWARF_REG_OFFSET; + regp->addr = offset; break; case DW_CFA_val_offset: count = dwarf_read_uleb128(current_insn, ®); current_insn += count; count = dwarf_read_leb128(current_insn, &offset); offset *= cie->data_alignment_factor; - frame->regs[reg].flags |= DWARF_REG_OFFSET; - frame->regs[reg].addr = offset; + regp = dwarf_frame_alloc_reg(frame, reg); + regp->flags |= DWARF_REG_OFFSET; + regp->addr = offset; break; case DW_CFA_GNU_args_size: count = dwarf_read_uleb128(current_insn, &offset); @@ -474,9 +511,10 @@ static int dwarf_cfa_execute_insns(unsigned char *insn_start, current_insn += count; count = dwarf_read_uleb128(current_insn, &offset); offset *= cie->data_alignment_factor; - dwarf_frame_alloc_regs(frame, reg); - frame->regs[reg].flags |= DWARF_REG_OFFSET; - frame->regs[reg].addr = -offset; + + regp = dwarf_frame_alloc_reg(frame, reg); + regp->flags |= DWARF_REG_OFFSET; + regp->addr = -offset; break; default: pr_debug("unhandled DWARF instruction 0x%x\n", insn); @@ -502,8 +540,8 @@ struct dwarf_frame *dwarf_unwind_stack(unsigned long pc, struct dwarf_frame *frame; struct dwarf_cie *cie; struct dwarf_fde *fde; + struct dwarf_reg *reg; unsigned long addr; - int i, offset; /* * If this is the first invocation of this recursive function we @@ -516,11 +554,16 @@ struct dwarf_frame *dwarf_unwind_stack(unsigned long pc, if (!pc && !prev) pc = (unsigned long)current_text_addr(); - frame = kzalloc(sizeof(*frame), GFP_ATOMIC); - if (!frame) - return NULL; + frame = mempool_alloc(dwarf_frame_pool, GFP_ATOMIC); + if (!frame) { + printk(KERN_ERR "Unable to allocate a dwarf frame\n"); + BUG(); + } + INIT_LIST_HEAD(&frame->reg_list); + frame->flags = 0; frame->prev = prev; + frame->return_addr = 0; fde = dwarf_lookup_fde(pc); if (!fde) { @@ -540,7 +583,7 @@ struct dwarf_frame *dwarf_unwind_stack(unsigned long pc, * case above, which sucks because we could print a * warning here. */ - return NULL; + goto bail; } cie = dwarf_lookup_cie(fde->cie_pointer); @@ -560,10 +603,10 @@ struct dwarf_frame *dwarf_unwind_stack(unsigned long pc, switch (frame->flags) { case DWARF_FRAME_CFA_REG_OFFSET: if (prev) { - BUG_ON(!prev->regs[frame->cfa_register].flags); + reg = dwarf_frame_reg(prev, frame->cfa_register); + BUG_ON(!reg); - addr = prev->cfa; - addr += prev->regs[frame->cfa_register].addr; + addr = prev->cfa + reg->addr; frame->cfa = __raw_readl(addr); } else { @@ -584,23 +627,18 @@ struct dwarf_frame *dwarf_unwind_stack(unsigned long pc, } /* If we haven't seen the return address reg, we're screwed. */ - BUG_ON(!frame->regs[DWARF_ARCH_RA_REG].flags); - - for (i = 0; i <= frame->num_regs; i++) { - struct dwarf_reg *reg = &frame->regs[i]; - - if (!reg->flags) - continue; + reg = dwarf_frame_reg(frame, DWARF_ARCH_RA_REG); + BUG_ON(!reg); - offset = reg->addr; - offset += frame->cfa; - } - - addr = frame->cfa + frame->regs[DWARF_ARCH_RA_REG].addr; + addr = frame->cfa + reg->addr; frame->return_addr = __raw_readl(addr); - frame->next = dwarf_unwind_stack(frame->return_addr, frame); return frame; + +bail: + dwarf_frame_free_regs(frame); + mempool_free(frame, dwarf_frame_pool); + return NULL; } static int dwarf_parse_cie(void *entry, void *p, unsigned long len, @@ -770,14 +808,29 @@ static void dwarf_unwinder_dump(struct task_struct *task, struct pt_regs *regs, unsigned long *sp, const struct stacktrace_ops *ops, void *data) { - struct dwarf_frame *frame; + struct dwarf_frame *frame, *_frame; + unsigned long return_addr; + + _frame = NULL; + return_addr = 0; - frame = dwarf_unwind_stack(0, NULL); + while (1) { + frame = dwarf_unwind_stack(return_addr, _frame); + + if (_frame) { + dwarf_frame_free_regs(_frame); + mempool_free(_frame, dwarf_frame_pool); + } + + _frame = frame; + + if (!frame || !frame->return_addr) + break; - while (frame && frame->return_addr) { - ops->address(data, frame->return_addr, 1); - frame = frame->next; + return_addr = frame->return_addr; + ops->address(data, return_addr, 1); } + } static struct unwinder dwarf_unwinder = { @@ -801,6 +854,9 @@ static void dwarf_unwinder_cleanup(void) list_for_each_entry(fde, &dwarf_fde_list, link) kfree(fde); + + kmem_cache_destroy(dwarf_reg_cachep); + kmem_cache_destroy(dwarf_frame_cachep); } /** @@ -827,6 +883,21 @@ static int __init dwarf_unwinder_init(void) f_entries = 0; entry = &__start_eh_frame; + dwarf_frame_cachep = kmem_cache_create("dwarf_frames", + sizeof(struct dwarf_frame), 0, SLAB_PANIC, NULL); + dwarf_reg_cachep = kmem_cache_create("dwarf_regs", + sizeof(struct dwarf_reg), 0, SLAB_PANIC, NULL); + + dwarf_frame_pool = mempool_create(DWARF_FRAME_MIN_REQ, + mempool_alloc_slab, + mempool_free_slab, + dwarf_frame_cachep); + + dwarf_reg_pool = mempool_create(DWARF_REG_MIN_REQ, + mempool_alloc_slab, + mempool_free_slab, + dwarf_reg_cachep); + while ((char *)entry < __stop_eh_frame) { p = entry; -- cgit v1.2.3-70-g09d2 From 97efbbd5886e27b61c19c77d41f6491f5d96fbd0 Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Sun, 16 Aug 2009 15:56:35 +0100 Subject: sh: unwinder: Set the flags for DW_CFA_val_offset ops as DWARF_VAL_OFFSET The handling of DW_CFA_val_offset ops was incorrectly using the DWARF_REG_OFFSET flag but the register's value cannot be calculated using the DWARF_REG_OFFSET method. Create a new flag to indicate that a different method must be used to calculate the register's value even though there is no implementation for DWARF_VAL_OFFSET yet; it's mainly just a place holder. Signed-off-by: Matt Fleming --- arch/sh/include/asm/dwarf.h | 1 + arch/sh/kernel/dwarf.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'arch/sh/kernel') diff --git a/arch/sh/include/asm/dwarf.h b/arch/sh/include/asm/dwarf.h index a22fbe98303..8b0bcc08738 100644 --- a/arch/sh/include/asm/dwarf.h +++ b/arch/sh/include/asm/dwarf.h @@ -296,6 +296,7 @@ struct dwarf_reg { unsigned long addr; unsigned long flags; #define DWARF_REG_OFFSET (1 << 0) +#define DWARF_VAL_OFFSET (1 << 1) }; /* diff --git a/arch/sh/kernel/dwarf.c b/arch/sh/kernel/dwarf.c index e4810375207..d271d04adcc 100644 --- a/arch/sh/kernel/dwarf.c +++ b/arch/sh/kernel/dwarf.c @@ -11,6 +11,7 @@ * * TODO: * - DWARF64 doesn't work. + * - Registers with DWARF_VAL_OFFSET rules aren't handled properly. */ /* #define DEBUG */ @@ -499,7 +500,7 @@ static int dwarf_cfa_execute_insns(unsigned char *insn_start, count = dwarf_read_leb128(current_insn, &offset); offset *= cie->data_alignment_factor; regp = dwarf_frame_alloc_reg(frame, reg); - regp->flags |= DWARF_REG_OFFSET; + regp->flags |= DWARF_VAL_OFFSET; regp->addr = offset; break; case DW_CFA_GNU_args_size: -- cgit v1.2.3-70-g09d2 From b344e24a8e8ceda83d1285d22e3e5baf4f5e42d3 Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Sun, 16 Aug 2009 21:54:48 +0100 Subject: sh: unwinder: Introduce UNWINDER_BUG() and UNWINDER_BUG_ON() We can't assume that if we execute the unwinder code and the unwinder was already running that it has faulted. Clearly two kernel threads can invoke the unwinder at the same time and may be running simultaneously. The previous approach used BUG() and BUG_ON() in the unwinder code to detect whether the unwinder was incapable of unwinding the stack, and that the next available unwinder should be used instead. A better approach is to explicitly invoke a trap handler to switch unwinders when the current unwinder cannot continue. Signed-off-by: Matt Fleming --- arch/sh/include/asm/bug.h | 25 +++++++++++++++++++++++++ arch/sh/include/asm/system.h | 5 +++++ arch/sh/include/asm/unwinder.h | 6 ++++++ arch/sh/kernel/debugtraps.S | 6 +++++- arch/sh/kernel/dwarf.c | 38 +++++++++++++++++++++----------------- arch/sh/kernel/traps.c | 7 +++++-- arch/sh/kernel/traps_32.c | 1 + arch/sh/kernel/unwinder.c | 33 +++++++++++++++++++++++++++------ 8 files changed, 95 insertions(+), 26 deletions(-) (limited to 'arch/sh/kernel') diff --git a/arch/sh/include/asm/bug.h b/arch/sh/include/asm/bug.h index c0171804016..b7d9822fd6c 100644 --- a/arch/sh/include/asm/bug.h +++ b/arch/sh/include/asm/bug.h @@ -1,6 +1,7 @@ #ifndef __ASM_SH_BUG_H #define __ASM_SH_BUG_H +#define TRAPA_UNWINDER_BUG_OPCODE 0xc33b /* trapa #0x3b */ #define TRAPA_BUG_OPCODE 0xc33e /* trapa #0x3e */ #ifdef CONFIG_GENERIC_BUG @@ -72,6 +73,30 @@ do { \ unlikely(__ret_warn_on); \ }) +#define UNWINDER_BUG() \ +do { \ + __asm__ __volatile__ ( \ + "1:\t.short %O0\n" \ + _EMIT_BUG_ENTRY \ + : \ + : "n" (TRAPA_UNWINDER_BUG_OPCODE), \ + "i" (__FILE__), \ + "i" (__LINE__), "i" (0), \ + "i" (sizeof(struct bug_entry))); \ +} while (0) + +#define UNWINDER_BUG_ON(x) ({ \ + int __ret_unwinder_on = !!(x); \ + if (__builtin_constant_p(__ret_unwinder_on)) { \ + if (__ret_unwinder_on) \ + UNWINDER_BUG(); \ + } else { \ + if (unlikely(__ret_unwinder_on)) \ + UNWINDER_BUG(); \ + } \ + unlikely(__ret_unwinder_on); \ +}) + #endif /* CONFIG_GENERIC_BUG */ #include diff --git a/arch/sh/include/asm/system.h b/arch/sh/include/asm/system.h index ab79e1f4fbe..f9e2ceb94d9 100644 --- a/arch/sh/include/asm/system.h +++ b/arch/sh/include/asm/system.h @@ -181,6 +181,11 @@ BUILD_TRAP_HANDLER(breakpoint); BUILD_TRAP_HANDLER(singlestep); BUILD_TRAP_HANDLER(fpu_error); BUILD_TRAP_HANDLER(fpu_state_restore); +BUILD_TRAP_HANDLER(unwinder); + +#ifdef CONFIG_BUG +extern void handle_BUG(struct pt_regs *); +#endif #define arch_align_stack(x) (x) diff --git a/arch/sh/include/asm/unwinder.h b/arch/sh/include/asm/unwinder.h index 3dc551453e2..1e65c07b3e1 100644 --- a/arch/sh/include/asm/unwinder.h +++ b/arch/sh/include/asm/unwinder.h @@ -22,4 +22,10 @@ extern void stack_reader_dump(struct task_struct *, struct pt_regs *, unsigned long *, const struct stacktrace_ops *, void *); +/* + * Used by fault handling code to signal to the unwinder code that it + * should switch to a different unwinder. + */ +extern int unwinder_faulted; + #endif /* _LINUX_UNWINDER_H */ diff --git a/arch/sh/kernel/debugtraps.S b/arch/sh/kernel/debugtraps.S index 591741383ee..cb00e4a82d4 100644 --- a/arch/sh/kernel/debugtraps.S +++ b/arch/sh/kernel/debugtraps.S @@ -19,6 +19,10 @@ #if !defined(CONFIG_SH_STANDARD_BIOS) #define sh_bios_handler debug_trap_handler +#endif + +#if !defined(CONFIG_DWARF_UNWINDER) +#define unwinder_trap_handler debug_trap_handler #endif .data @@ -35,7 +39,7 @@ ENTRY(debug_trap_table) .long debug_trap_handler /* 0x38 */ .long debug_trap_handler /* 0x39 */ .long debug_trap_handler /* 0x3a */ - .long debug_trap_handler /* 0x3b */ + .long unwinder_trap_handler /* 0x3b */ .long breakpoint_trap_handler /* 0x3c */ .long singlestep_trap_handler /* 0x3d */ .long bug_trap_handler /* 0x3e */ diff --git a/arch/sh/kernel/dwarf.c b/arch/sh/kernel/dwarf.c index d271d04adcc..606ece37eb4 100644 --- a/arch/sh/kernel/dwarf.c +++ b/arch/sh/kernel/dwarf.c @@ -69,7 +69,7 @@ static struct dwarf_reg *dwarf_frame_alloc_reg(struct dwarf_frame *frame, * Let's just bomb hard here, we have no way to * gracefully recover. */ - BUG(); + UNWINDER_BUG(); } reg->number = reg_num; @@ -232,7 +232,7 @@ static int dwarf_read_encoded_value(char *addr, unsigned long *val, break; default: pr_debug("encoding=0x%x\n", (encoding & 0x70)); - BUG(); + UNWINDER_BUG(); } if ((encoding & 0x07) == 0x00) @@ -247,7 +247,7 @@ static int dwarf_read_encoded_value(char *addr, unsigned long *val, break; default: pr_debug("encoding=0x%x\n", encoding); - BUG(); + UNWINDER_BUG(); } return count; @@ -519,6 +519,7 @@ static int dwarf_cfa_execute_insns(unsigned char *insn_start, break; default: pr_debug("unhandled DWARF instruction 0x%x\n", insn); + UNWINDER_BUG(); break; } } @@ -535,8 +536,8 @@ static int dwarf_cfa_execute_insns(unsigned char *insn_start, * on the callstack. Each of the lower (older) stack frames are * linked via the "prev" member. */ -struct dwarf_frame *dwarf_unwind_stack(unsigned long pc, - struct dwarf_frame *prev) +struct dwarf_frame * dwarf_unwind_stack(unsigned long pc, + struct dwarf_frame *prev) { struct dwarf_frame *frame; struct dwarf_cie *cie; @@ -558,7 +559,7 @@ struct dwarf_frame *dwarf_unwind_stack(unsigned long pc, frame = mempool_alloc(dwarf_frame_pool, GFP_ATOMIC); if (!frame) { printk(KERN_ERR "Unable to allocate a dwarf frame\n"); - BUG(); + UNWINDER_BUG(); } INIT_LIST_HEAD(&frame->reg_list); @@ -605,7 +606,8 @@ struct dwarf_frame *dwarf_unwind_stack(unsigned long pc, case DWARF_FRAME_CFA_REG_OFFSET: if (prev) { reg = dwarf_frame_reg(prev, frame->cfa_register); - BUG_ON(!reg); + UNWINDER_BUG_ON(!reg); + UNWINDER_BUG_ON(reg->flags != DWARF_REG_OFFSET); addr = prev->cfa + reg->addr; frame->cfa = __raw_readl(addr); @@ -624,12 +626,13 @@ struct dwarf_frame *dwarf_unwind_stack(unsigned long pc, frame->cfa += frame->cfa_offset; break; default: - BUG(); + UNWINDER_BUG(); } /* If we haven't seen the return address reg, we're screwed. */ reg = dwarf_frame_reg(frame, DWARF_ARCH_RA_REG); - BUG_ON(!reg); + UNWINDER_BUG_ON(!reg); + UNWINDER_BUG_ON(reg->flags != DWARF_REG_OFFSET); addr = frame->cfa + reg->addr; frame->return_addr = __raw_readl(addr); @@ -664,7 +667,7 @@ static int dwarf_parse_cie(void *entry, void *p, unsigned long len, cie->cie_pointer = (unsigned long)entry; cie->version = *(char *)p++; - BUG_ON(cie->version != 1); + UNWINDER_BUG_ON(cie->version != 1); cie->augmentation = p; p += strlen(cie->augmentation) + 1; @@ -694,7 +697,7 @@ static int dwarf_parse_cie(void *entry, void *p, unsigned long len, count = dwarf_read_uleb128(p, &length); p += count; - BUG_ON((unsigned char *)p > end); + UNWINDER_BUG_ON((unsigned char *)p > end); cie->initial_instructions = p + length; cie->augmentation++; @@ -722,16 +725,16 @@ static int dwarf_parse_cie(void *entry, void *p, unsigned long len, * routine in the CIE * augmentation. */ - BUG(); + UNWINDER_BUG(); } else if (*cie->augmentation == 'S') { - BUG(); + UNWINDER_BUG(); } else { /* * Unknown augmentation. Assume * 'z' augmentation. */ p = cie->initial_instructions; - BUG_ON(!p); + UNWINDER_BUG_ON(!p); break; } } @@ -805,9 +808,11 @@ static int dwarf_parse_fde(void *entry, u32 entry_type, return 0; } -static void dwarf_unwinder_dump(struct task_struct *task, struct pt_regs *regs, +static void dwarf_unwinder_dump(struct task_struct *task, + struct pt_regs *regs, unsigned long *sp, - const struct stacktrace_ops *ops, void *data) + const struct stacktrace_ops *ops, + void *data) { struct dwarf_frame *frame, *_frame; unsigned long return_addr; @@ -831,7 +836,6 @@ static void dwarf_unwinder_dump(struct task_struct *task, struct pt_regs *regs, return_addr = frame->return_addr; ops->address(data, return_addr, 1); } - } static struct unwinder dwarf_unwinder = { diff --git a/arch/sh/kernel/traps.c b/arch/sh/kernel/traps.c index b3e0067db35..881b9a32b7d 100644 --- a/arch/sh/kernel/traps.c +++ b/arch/sh/kernel/traps.c @@ -8,7 +8,7 @@ #include #ifdef CONFIG_BUG -static void handle_BUG(struct pt_regs *regs) +void handle_BUG(struct pt_regs *regs) { enum bug_trap_type tt; tt = report_bug(regs->pc, regs); @@ -29,7 +29,10 @@ int is_valid_bugaddr(unsigned long addr) if (probe_kernel_address((insn_size_t *)addr, opcode)) return 0; - return opcode == TRAPA_BUG_OPCODE; + if (opcode == TRAPA_BUG_OPCODE || opcode == TRAPA_UNWINDER_BUG_OPCODE) + return 1; + + return 0; } #endif diff --git a/arch/sh/kernel/traps_32.c b/arch/sh/kernel/traps_32.c index 563426487c6..05a04b6df84 100644 --- a/arch/sh/kernel/traps_32.c +++ b/arch/sh/kernel/traps_32.c @@ -136,6 +136,7 @@ static void die_if_no_fixup(const char * str, struct pt_regs * regs, long err) regs->pc = fixup->fixup; return; } + die(str, regs, err); } } diff --git a/arch/sh/kernel/unwinder.c b/arch/sh/kernel/unwinder.c index 2b30fa28b44..b9c122abe25 100644 --- a/arch/sh/kernel/unwinder.c +++ b/arch/sh/kernel/unwinder.c @@ -53,8 +53,6 @@ static struct list_head unwinder_list = { static DEFINE_SPINLOCK(unwinder_lock); -static atomic_t unwinder_running = ATOMIC_INIT(0); - /** * select_unwinder - Select the best registered stack unwinder. * @@ -122,6 +120,8 @@ int unwinder_register(struct unwinder *u) return ret; } +int unwinder_faulted = 0; + /* * Unwind the call stack and pass information to the stacktrace_ops * functions. Also handle the case where we need to switch to a new @@ -144,19 +144,40 @@ void unwind_stack(struct task_struct *task, struct pt_regs *regs, * Hopefully this will give us a semi-reliable stacktrace so we * can diagnose why curr_unwinder->dump() faulted. */ - if (atomic_inc_return(&unwinder_running) != 1) { + if (unwinder_faulted) { spin_lock_irqsave(&unwinder_lock, flags); - if (!list_is_singular(&unwinder_list)) { + /* Make sure no one beat us to changing the unwinder */ + if (unwinder_faulted && !list_is_singular(&unwinder_list)) { list_del(&curr_unwinder->list); curr_unwinder = select_unwinder(); + + unwinder_faulted = 0; } spin_unlock_irqrestore(&unwinder_lock, flags); - atomic_dec(&unwinder_running); } curr_unwinder->dump(task, regs, sp, ops, data); +} + +/* + * Trap handler for UWINDER_BUG() statements. We must switch to the + * unwinder with the next highest rating. + */ +BUILD_TRAP_HANDLER(unwinder) +{ + insn_size_t insn; + TRAP_HANDLER_DECL; + + /* Rewind */ + regs->pc -= instruction_size(ctrl_inw(regs->pc - 4)); + insn = *(insn_size_t *)instruction_pointer(regs); + + /* Switch unwinders when unwind_stack() is called */ + unwinder_faulted = 1; - atomic_dec(&unwinder_running); +#ifdef CONFIG_BUG + handle_BUG(regs); +#endif } -- cgit v1.2.3-70-g09d2 From fe98dd31eb9fe055703e0171134b4d074558d4e9 Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Thu, 20 Aug 2009 17:00:21 +0100 Subject: sh: Setup the frame pointer in handle_interrupt When CONFIG_DWARF_UNWINDER is enabled setup r14 in handle_interrupt, so that we can figure out what function was running when we were interrupted. Signed-off-by: Matt Fleming --- arch/sh/kernel/cpu/sh3/entry.S | 2 ++ 1 file changed, 2 insertions(+) (limited to 'arch/sh/kernel') diff --git a/arch/sh/kernel/cpu/sh3/entry.S b/arch/sh/kernel/cpu/sh3/entry.S index 67ad6467c69..f89ec87e3fc 100644 --- a/arch/sh/kernel/cpu/sh3/entry.S +++ b/arch/sh/kernel/cpu/sh3/entry.S @@ -508,6 +508,8 @@ ENTRY(handle_interrupt) bsr save_regs ! needs original pr value in k3 mov #-1, k2 ! default vector kept in k2 + setup_frame_reg + ! Setup return address and jump to do_IRQ mov.l 4f, r9 ! fetch return address lds r9, pr ! put return address in pr -- cgit v1.2.3-70-g09d2 From 5480675dc60c7dda7146e506981b2b40a775cc1e Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Thu, 20 Aug 2009 19:42:34 +0100 Subject: sh: Fix bug calculating the end of the FDE instructions The 'end' member of struct dwarf_fde denotes one byte past the end of the CFA instruction stream for an FDE. The value of 'end' was being calcualted incorrectly, it was being set too high. This resulted in dwarf_cfa_execute_insns() interpreting data past the end of valid instructions, thus causing all sorts of weird crashes. Signed-off-by: Matt Fleming --- arch/sh/kernel/dwarf.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'arch/sh/kernel') diff --git a/arch/sh/kernel/dwarf.c b/arch/sh/kernel/dwarf.c index 606ece37eb4..e6f427cff5b 100644 --- a/arch/sh/kernel/dwarf.c +++ b/arch/sh/kernel/dwarf.c @@ -751,7 +751,8 @@ static int dwarf_parse_cie(void *entry, void *p, unsigned long len, } static int dwarf_parse_fde(void *entry, u32 entry_type, - void *start, unsigned long len) + void *start, unsigned long len, + unsigned char *end) { struct dwarf_fde *fde; struct dwarf_cie *cie; @@ -798,7 +799,7 @@ static int dwarf_parse_fde(void *entry, u32 entry_type, /* Call frame instructions. */ fde->instructions = p; - fde->end = start + len; + fde->end = end; /* Add to list. */ spin_lock_irqsave(&dwarf_fde_lock, flags); @@ -932,7 +933,7 @@ static int __init dwarf_unwinder_init(void) else c_entries++; } else { - err = dwarf_parse_fde(entry, entry_type, p, len); + err = dwarf_parse_fde(entry, entry_type, p, len, end); if (err < 0) goto out; else -- cgit v1.2.3-70-g09d2 From 5580e9044df9c0e87861739d8c527006ead92e52 Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Thu, 20 Aug 2009 19:53:49 +0100 Subject: sh: Handle the DWARF op, DW_CFA_undefined Allow a DWARF register to have an undefined value. When applied to the DWARF return address register this lets lets us label a function as having no direct caller, e.g. kernel_thread_helper(). Signed-off-by: Matt Fleming --- arch/sh/include/asm/dwarf.h | 3 +++ arch/sh/kernel/dwarf.c | 13 +++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 'arch/sh/kernel') diff --git a/arch/sh/include/asm/dwarf.h b/arch/sh/include/asm/dwarf.h index 8b0bcc08738..c367ed3373c 100644 --- a/arch/sh/include/asm/dwarf.h +++ b/arch/sh/include/asm/dwarf.h @@ -297,6 +297,7 @@ struct dwarf_reg { unsigned long flags; #define DWARF_REG_OFFSET (1 << 0) #define DWARF_VAL_OFFSET (1 << 1) +#define DWARF_UNDEFINED (1 << 2) }; /* @@ -370,6 +371,7 @@ extern struct dwarf_frame *dwarf_unwind_stack(unsigned long, #define CFI_DEF_CFA .cfi_def_cfa #define CFI_REGISTER .cfi_register #define CFI_REL_OFFSET .cfi_rel_offset +#define CFI_UNDEFINED .cfi_undefined #else @@ -383,6 +385,7 @@ extern struct dwarf_frame *dwarf_unwind_stack(unsigned long, #define CFI_DEF_CFA CFI_IGNORE #define CFI_REGISTER CFI_IGNORE #define CFI_REL_OFFSET CFI_IGNORE +#define CFI_UNDEFINED CFI_IGNORE #ifndef __ASSEMBLY__ static inline void dwarf_unwinder_init(void) diff --git a/arch/sh/kernel/dwarf.c b/arch/sh/kernel/dwarf.c index e6f427cff5b..577302f31e6 100644 --- a/arch/sh/kernel/dwarf.c +++ b/arch/sh/kernel/dwarf.c @@ -452,6 +452,8 @@ static int dwarf_cfa_execute_insns(unsigned char *insn_start, case DW_CFA_undefined: count = dwarf_read_uleb128(current_insn, ®); current_insn += count; + regp = dwarf_frame_alloc_reg(frame, reg); + regp->flags |= DWARF_UNDEFINED; break; case DW_CFA_def_cfa: count = dwarf_read_uleb128(current_insn, @@ -629,9 +631,16 @@ struct dwarf_frame * dwarf_unwind_stack(unsigned long pc, UNWINDER_BUG(); } - /* If we haven't seen the return address reg, we're screwed. */ reg = dwarf_frame_reg(frame, DWARF_ARCH_RA_REG); - UNWINDER_BUG_ON(!reg); + + /* + * If we haven't seen the return address register or the return + * address column is undefined then we must assume that this is + * the end of the callstack. + */ + if (!reg || reg->flags == DWARF_UNDEFINED) + goto bail; + UNWINDER_BUG_ON(reg->flags != DWARF_REG_OFFSET); addr = frame->cfa + reg->addr; -- cgit v1.2.3-70-g09d2