This patch allows unwind_frame() to traverse from interrupt stack to task stack correctly.
A similar approach is taken to modify dump_backtrace_entry(), which expects to find struct pt_regs underneath any call to functions marked __exception. When on an irq_stack, the struct pt_regs is stored on the old task stack. c_backtrace() is also modified on same logic, when traversing from last IRQ frame, update fp with SVC mode fp. Co-developed-by: Vaneet Narang <v.nar...@samsung.com> Signed-off-by: Vaneet Narang <v.nar...@samsung.com> Signed-off-by: Maninder Singh <maninder...@samsung.com> --- arch/arm/include/asm/irq.h | 7 +++++++ arch/arm/kernel/stacktrace.c | 21 ++++++++++++++++++++ arch/arm/kernel/traps.c | 47 +++++++++++++++++++++++++++++++++++++++++--- arch/arm/lib/backtrace.S | 18 +++++++++++++++++ 4 files changed, 90 insertions(+), 3 deletions(-) diff --git a/arch/arm/include/asm/irq.h b/arch/arm/include/asm/irq.h index f3299ab..d4c66e9 100644 --- a/arch/arm/include/asm/irq.h +++ b/arch/arm/include/asm/irq.h @@ -30,6 +30,13 @@ #ifdef CONFIG_IRQ_STACK DECLARE_PER_CPU(unsigned long *, irq_stack_ptr); + +#define IRQ_STACK_BASE_PTR (unsigned long)(raw_cpu_read(irq_stack_ptr)) +#define IRQ_STACK_TOP_PTR (unsigned long)(raw_cpu_read(irq_stack_ptr) + IRQ_STACK_SIZE) + +#define IRQ_STACK_TO_TASK_FRAME(ptr) (*((unsigned long *)(ptr + 0x4))) +#define IRQ_STACK_TO_TASK_STACK(ptr) (*((unsigned long *)(ptr + 0x8))) + #endif extern void asm_do_IRQ(unsigned int, struct pt_regs *); diff --git a/arch/arm/kernel/stacktrace.c b/arch/arm/kernel/stacktrace.c index 76ea417..65b9634 100644 --- a/arch/arm/kernel/stacktrace.c +++ b/arch/arm/kernel/stacktrace.c @@ -7,6 +7,9 @@ #include <asm/sections.h> #include <asm/stacktrace.h> #include <asm/traps.h> +#ifdef CONFIG_IRQ_STACK +#include <asm/irq.h> +#endif #if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND) /* @@ -42,6 +45,13 @@ int notrace unwind_frame(struct stackframe *frame) { unsigned long high, low; unsigned long fp = frame->fp; +#ifdef CONFIG_IRQ_STACK + unsigned long irq_stack_base_p; + unsigned long irq_stack_p; + + irq_stack_base_p = IRQ_STACK_BASE_PTR; + irq_stack_p = irq_stack_base_p + IRQ_STACK_SIZE; +#endif /* only go to a higher address on the stack */ low = frame->sp; @@ -67,6 +77,17 @@ int notrace unwind_frame(struct stackframe *frame) frame->pc = *(unsigned long *)(fp - 4); #endif +#ifdef CONFIG_IRQ_STACK + /* + * Check whether we are going to walk through from interrupt stack + * to task stack. + * If we reach the end of the stack - and its an interrupt stack, + * read the original task stack pointer base + 4 of IRQ stack. + */ + if (frame->sp == irq_stack_p) + frame->sp = IRQ_STACK_TO_TASK_STACK(irq_stack_base_p); +#endif + return 0; } #endif diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index 17d5a78..36b0cda 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -65,8 +65,20 @@ static int __init user_debug_setup(char *str) void dump_backtrace_entry(unsigned long where, unsigned long from, unsigned long frame, const char *loglvl) { - unsigned long end = frame + 4 + sizeof(struct pt_regs); + unsigned long end; +#ifdef CONFIG_IRQ_STACK + unsigned long irq_stack_base_p; + unsigned long irq_stack_p; + + irq_stack_base_p = IRQ_STACK_BASE_PTR; + irq_stack_p = irq_stack_base_p + IRQ_STACK_SIZE; + + if (frame < irq_stack_p && (frame + sizeof(struct pt_regs)) > irq_stack_p) + frame = IRQ_STACK_TO_TASK_STACK(irq_stack_base_p) - 4; +#endif + + end = frame + 4 + sizeof(struct pt_regs); #ifdef CONFIG_KALLSYMS printk("%s[<%08lx>] (%ps) from [<%08lx>] (%pS)\n", loglvl, where, (void *)where, from, (void *)from); @@ -113,6 +125,35 @@ static int verify_stack(unsigned long sp) return 0; } + +#ifdef CONFIG_IRQ_STACK +static int fp_underflow(unsigned long fp, struct task_struct *tsk) +{ + unsigned long end = (unsigned long)end_of_stack(tsk); + unsigned long irq_stack_base_p; + unsigned long irq_stack_p; + + irq_stack_base_p = IRQ_STACK_BASE_PTR; + irq_stack_p = irq_stack_base_p + IRQ_STACK_SIZE; + + + if (fp > end && fp < end + THREAD_SIZE) + return 0; + + if (fp > irq_stack_base_p && fp < irq_stack_p) + return 0; + + return 1; +} +#else +static int fp_underflow(unsigned long fp, struct task_struct *tsk) +{ + if (fp < (unsigned long)end_of_stack(tsk)) + return 1; + + return 0; +} +#endif #endif /* @@ -238,7 +279,7 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk, } else if (verify_stack(fp)) { pr_cont("invalid frame pointer 0x%08x", fp); ok = 0; - } else if (fp < (unsigned long)end_of_stack(tsk)) + } else if (fp_underflow(fp, tsk)) pr_cont("frame pointer underflow"); pr_cont("\n"); @@ -292,7 +333,7 @@ static int __die(const char *str, int err, struct pt_regs *regs) if (!user_mode(regs) || in_interrupt()) { dump_mem(KERN_EMERG, "Stack: ", regs->ARM_sp, - THREAD_SIZE + (unsigned long)task_stack_page(tsk)); + THREAD_SIZE + (unsigned long)(regs->ARM_sp & ~(THREAD_SIZE - 1))); dump_backtrace(regs, tsk, KERN_EMERG); dump_instr(KERN_EMERG, regs); } diff --git a/arch/arm/lib/backtrace.S b/arch/arm/lib/backtrace.S index 872f658..1a8e645 100644 --- a/arch/arm/lib/backtrace.S +++ b/arch/arm/lib/backtrace.S @@ -9,6 +9,9 @@ #include <linux/kern_levels.h> #include <linux/linkage.h> #include <asm/assembler.h> +#ifdef CONFIG_IRQ_STACK +#include <asm/irq.h> +#endif .text @ fp is 0 or stack frame @@ -96,6 +99,21 @@ for_each_frame: tst frame, mask @ Check for address exceptions teq sv_fp, #0 @ zero saved fp means beq no_frame @ no further frames +#ifdef CONFIG_IRQ_STACK + /* + * check if it is swtiching from IRQ to SVC, + * then update frame accordingly. + */ + this_cpu_ptr irq_stack_ptr r2 r3 + ldr r3, [r2] + add r2, r3, #IRQ_STACK_SIZE + ldr r0, [frame, #-8] + cmp r2, r0 + ldreq sv_fp, [r3, #4] + moveq frame, sv_fp + beq for_each_frame +#endif + cmp sv_fp, frame @ next frame must be mov frame, sv_fp @ above the current frame bhi for_each_frame -- 1.9.1