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 <matt@console-pimps.org>
diff --git a/arch/sh/kernel/debugtraps.S b/arch/sh/kernel/debugtraps.S
index 5917413..cb00e4a 100644
--- a/arch/sh/kernel/debugtraps.S
+++ b/arch/sh/kernel/debugtraps.S
@@ -21,6 +21,10 @@
#define sh_bios_handler debug_trap_handler
#endif
+#if !defined(CONFIG_DWARF_UNWINDER)
+#define unwinder_trap_handler debug_trap_handler
+#endif
+
.data
ENTRY(debug_trap_table)
@@ -35,7 +39,7 @@
.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 d271d04..606ece3 100644
--- a/arch/sh/kernel/dwarf.c
+++ b/arch/sh/kernel/dwarf.c
@@ -69,7 +69,7 @@
* Let's just bomb hard here, we have no way to
* gracefully recover.
*/
- BUG();
+ UNWINDER_BUG();
}
reg->number = reg_num;
@@ -232,7 +232,7 @@
break;
default:
pr_debug("encoding=0x%x\n", (encoding & 0x70));
- BUG();
+ UNWINDER_BUG();
}
if ((encoding & 0x07) == 0x00)
@@ -247,7 +247,7 @@
break;
default:
pr_debug("encoding=0x%x\n", encoding);
- BUG();
+ UNWINDER_BUG();
}
return count;
@@ -519,6 +519,7 @@
break;
default:
pr_debug("unhandled DWARF instruction 0x%x\n", insn);
+ UNWINDER_BUG();
break;
}
}
@@ -535,8 +536,8 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* 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 @@
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 @@
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 b3e0067..881b9a3 100644
--- a/arch/sh/kernel/traps.c
+++ b/arch/sh/kernel/traps.c
@@ -8,7 +8,7 @@
#include <asm/system.h>
#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 @@
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 5634264..05a04b6 100644
--- a/arch/sh/kernel/traps_32.c
+++ b/arch/sh/kernel/traps_32.c
@@ -136,6 +136,7 @@
regs->pc = fixup->fixup;
return;
}
+
die(str, regs, err);
}
}
diff --git a/arch/sh/kernel/unwinder.c b/arch/sh/kernel/unwinder.c
index 2b30fa2..b9c122a 100644
--- a/arch/sh/kernel/unwinder.c
+++ b/arch/sh/kernel/unwinder.c
@@ -53,8 +53,6 @@
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 @@
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 @@
* 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);
+}
- atomic_dec(&unwinder_running);
+/*
+ * 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;
+
+#ifdef CONFIG_BUG
+ handle_BUG(regs);
+#endif
}