Skip to content

Commit 7d7b720

Browse files
madvenka786willdeacon
authored andcommitted
arm64: Implement stack trace termination record
Reliable stacktracing requires that we identify when a stacktrace is terminated early. We can do this by ensuring all tasks have a final frame record at a known location on their task stack, and checking that this is the final frame record in the chain. We'd like to use task_pt_regs(task)->stackframe as the final frame record, as this is already setup upon exception entry from EL0. For kernel tasks we need to consistently reserve the pt_regs and point x29 at this, which we can do with small changes to __primary_switched, __secondary_switched, and copy_process(). Since the final frame record must be at a specific location, we must create the final frame record in __primary_switched and __secondary_switched rather than leaving this to start_kernel and secondary_start_kernel. Thus, __primary_switched and __secondary_switched will now show up in stacktraces for the idle tasks. Since the final frame record is now identified by its location rather than by its contents, we identify it at the start of unwind_frame(), before we read any values from it. External debuggers may terminate the stack trace when FP == 0. In the pt_regs->stackframe, the PC is 0 as well. So, stack traces taken in the debugger may print an extra record 0x0 at the end. While this is not pretty, this does not do any harm. This is a small price to pay for having reliable stack trace termination in the kernel. That said, gdb does not show the extra record probably because it uses DWARF and not frame pointers for stack traces. Signed-off-by: Madhavan T. Venkataraman <madvenka@linux.microsoft.com> Reviewed-by: Mark Brown <broonie@kernel.org> [Mark: rebase, use ASM_BUG(), update comments, update commit message] Signed-off-by: Mark Rutland <mark.rutland@arm.com> Link: https://lore.kernel.org/r/20210510110026.18061-1-mark.rutland@arm.com Signed-off-by: Will Deacon <will@kernel.org>
1 parent c468154 commit 7d7b720

4 files changed

Lines changed: 32 additions & 16 deletions

File tree

arch/arm64/kernel/entry.S

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ alternative_else_nop_endif
285285
stp lr, x21, [sp, #S_LR]
286286

287287
/*
288-
* For exceptions from EL0, create a terminal frame record.
288+
* For exceptions from EL0, create a final frame record.
289289
* For exceptions from EL1, create a synthetic frame record so the
290290
* interrupted code shows up in the backtrace.
291291
*/

arch/arm64/kernel/head.S

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <asm/asm_pointer_auth.h>
1717
#include <asm/assembler.h>
1818
#include <asm/boot.h>
19+
#include <asm/bug.h>
1920
#include <asm/ptrace.h>
2021
#include <asm/asm-offsets.h>
2122
#include <asm/cache.h>
@@ -393,6 +394,18 @@ SYM_FUNC_START_LOCAL(__create_page_tables)
393394
ret x28
394395
SYM_FUNC_END(__create_page_tables)
395396

397+
/*
398+
* Create a final frame record at task_pt_regs(current)->stackframe, so
399+
* that the unwinder can identify the final frame record of any task by
400+
* its location in the task stack. We reserve the entire pt_regs space
401+
* for consistency with user tasks and kthreads.
402+
*/
403+
.macro setup_final_frame
404+
sub sp, sp, #PT_REGS_SIZE
405+
stp xzr, xzr, [sp, #S_STACKFRAME]
406+
add x29, sp, #S_STACKFRAME
407+
.endm
408+
396409
/*
397410
* The following fragment of code is executed with the MMU enabled.
398411
*
@@ -447,9 +460,9 @@ SYM_FUNC_START_LOCAL(__primary_switched)
447460
#endif
448461
bl switch_to_vhe // Prefer VHE if possible
449462
add sp, sp, #16
450-
mov x29, #0
451-
mov x30, #0
452-
b start_kernel
463+
setup_final_frame
464+
bl start_kernel
465+
ASM_BUG()
453466
SYM_FUNC_END(__primary_switched)
454467

455468
.pushsection ".rodata", "a"
@@ -639,14 +652,14 @@ SYM_FUNC_START_LOCAL(__secondary_switched)
639652
cbz x2, __secondary_too_slow
640653
msr sp_el0, x2
641654
scs_load x2, x3
642-
mov x29, #0
643-
mov x30, #0
655+
setup_final_frame
644656

645657
#ifdef CONFIG_ARM64_PTR_AUTH
646658
ptrauth_keys_init_cpu x2, x3, x4, x5
647659
#endif
648660

649-
b secondary_start_kernel
661+
bl secondary_start_kernel
662+
ASM_BUG()
650663
SYM_FUNC_END(__secondary_switched)
651664

652665
SYM_FUNC_START_LOCAL(__secondary_too_slow)

arch/arm64/kernel/process.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,11 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start,
435435
}
436436
p->thread.cpu_context.pc = (unsigned long)ret_from_fork;
437437
p->thread.cpu_context.sp = (unsigned long)childregs;
438+
/*
439+
* For the benefit of the unwinder, set up childregs->stackframe
440+
* as the final frame for the new task.
441+
*/
442+
p->thread.cpu_context.fp = (unsigned long)childregs->stackframe;
438443

439444
ptrace_hw_copy_thread(p);
440445

arch/arm64/kernel/stacktrace.c

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,16 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
6868
unsigned long fp = frame->fp;
6969
struct stack_info info;
7070

71-
if (fp & 0xf)
72-
return -EINVAL;
73-
7471
if (!tsk)
7572
tsk = current;
7673

74+
/* Final frame; nothing to unwind */
75+
if (fp == (unsigned long)task_pt_regs(tsk)->stackframe)
76+
return -ENOENT;
77+
78+
if (fp & 0xf)
79+
return -EINVAL;
80+
7781
if (!on_accessible_stack(tsk, fp, &info))
7882
return -EINVAL;
7983

@@ -128,12 +132,6 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
128132

129133
frame->pc = ptrauth_strip_insn_pac(frame->pc);
130134

131-
/*
132-
* This is a terminal record, so we have finished unwinding.
133-
*/
134-
if (!frame->fp && !frame->pc)
135-
return -ENOENT;
136-
137135
return 0;
138136
}
139137
NOKPROBE_SYMBOL(unwind_frame);

0 commit comments

Comments
 (0)