diff --git a/arch/arm64/include/asm/stacktrace.h b/arch/arm64/include/asm/stacktrace.h index eb29b1fe8255eb23d0cf2e9e588dd19ddee5061b..dda36f917263d0de26a1a186fe25451307d48e05 100644 --- a/arch/arm64/include/asm/stacktrace.h +++ b/arch/arm64/include/asm/stacktrace.h @@ -171,4 +171,7 @@ static inline void start_backtrace(struct stackframe *frame, frame->prev_type = STACK_TYPE_UNKNOWN; } +#ifdef CONFIG_PREEMPTION +extern void preempt_schedule_irq_ret_addr(void); +#endif #endif /* __ASM_STACKTRACE_H */ diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S index eb4ba83083979e5f637ea0926b3768716fa0484a..1290f36c83713a65c22d7f61dc839e763a9e10cb 100644 --- a/arch/arm64/kernel/entry.S +++ b/arch/arm64/kernel/entry.S @@ -523,6 +523,8 @@ alternative_else_nop_endif #endif cbnz x24, 1f // preempt count != 0 || NMI return path bl arm64_preempt_schedule_irq // irq en/disable is done inside +.global preempt_schedule_irq_ret_addr +preempt_schedule_irq_ret_addr: 1: #endif diff --git a/arch/arm64/kernel/stacktrace.c b/arch/arm64/kernel/stacktrace.c index 2073a3a7fe75127e19b5e3ee41ea46569667cef7..93ac3c74fb415bcd9b4cf2abe096acc77d061675 100644 --- a/arch/arm64/kernel/stacktrace.c +++ b/arch/arm64/kernel/stacktrace.c @@ -129,6 +129,72 @@ void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame, if (!fn(data, frame->pc)) break; +#ifdef CONFIG_PREEMPTION + /* + * Suppose existing call chain: P() -> A() -> B(), B() don't construct stack + * frame in which fp and lr are saved for call stack unwinding, then if task1 + * is interrupted as running at address 'B2' and then preempted by task2, + * and task2 unwind the call stack of task1, it expect to see P->A->B, but + * actually P->B, A disappeared! + * + * A(): + * A1: stp fp, lr, ... <-- suppose fp_P and lr_P saved + * A2: mov fp, sp <-- suppose fp_A saved in 'fp' register + * A3: bl B <-- call to B() + * A4: mov ... <-- 'A4' saved in 'lr' register + * + * B(): + * B1: mov ... + * B2: mov ... <-- interrupt comes, then run into el1_irq() + * B3: mov ... <-- 'B3' is saved in 'elr_el1' register + * + * el1_irq(): + * ... <-- save registers then construct stack frame + * Cm: bl arm64_preempt_schedule_irq <-- Can be preempted here + * Cn: ... + * + * In this case, at the time interrupt comes, the address 'A4' will be saved + * In 'lr' register, then in interrupt entry, 'lr' register will be saved in + * Stack memory as struct pt_regs. + * + * See following stack memory layout, as call stack unwinding, if address + * 'Cn' is found , we know that fp_C is point to pt_regs.stackframe[0], + * then we can found the 'A4' in pt_regs.regs[30], then we can know that + * B() is currently called by A(). + * + * Stack memory (High address downto Low address): + * + * + * |-----------------| + * | lr_P | + * |-----------------| + * | fp_P | + * -> |-----------------| + * | | ... | + * | |-----------------| + * | | B3 | + * | |-----------------| + * -- | fp_A | + * -> |-----------------| <-- pt_regs.stackframe[0] + * | | | + * | | X0... fp lr(A4) | <-- pt_regs.regs[] + * | |-----------------| + * | | ... | + * | |-----------------| + * | | Cn | <-- 'Cn' is return address of + * | |-----------------| arm64_preempt_schedule_irq() + * -- | fp_C | + * |-----------------| + * + */ + if (frame->pc == (unsigned long)preempt_schedule_irq_ret_addr) { + struct pt_regs *reg = container_of((u64 *)frame->fp, + struct pt_regs, stackframe[0]); + + if (!fn(data, reg->regs[30])) + break; + } +#endif ret = unwind_frame(tsk, frame); if (ret < 0) break;