ARM: Recover kretprobe modified return address in stacktrace
authorMasami Hiramatsu <mhiramat@kernel.org>
Thu, 21 Oct 2021 00:55:35 +0000 (09:55 +0900)
committerSteven Rostedt (VMware) <rostedt@goodmis.org>
Fri, 22 Oct 2021 16:16:53 +0000 (12:16 -0400)
Since the kretprobe replaces the function return address with
the kretprobe_trampoline on the stack, arm unwinder shows it
instead of the correct return address.

This finds the correct return address from the per-task
kretprobe_instances list and verify it is in between the
caller fp and callee fp.

Note that this supports both GCC and clang if CONFIG_FRAME_POINTER=y
and CONFIG_ARM_UNWIND=n. For the ARM unwinder, this is still
not working correctly.

Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
arch/arm/Kconfig
arch/arm/include/asm/stacktrace.h
arch/arm/kernel/return_address.c
arch/arm/kernel/stacktrace.c

index fc19642..bb4f187 100644 (file)
@@ -3,6 +3,7 @@ config ARM
        bool
        default y
        select ARCH_32BIT_OFF_T
+       select ARCH_CORRECT_STACKTRACE_ON_KRETPROBE if HAVE_KRETPROBES && FRAME_POINTER && !ARM_UNWIND
        select ARCH_HAS_BINFMT_FLAT
        select ARCH_HAS_DEBUG_VIRTUAL if MMU
        select ARCH_HAS_DMA_WRITE_COMBINE if !ARM_DMA_MEM_BUFFERABLE
index 2d76a2e..8f54f9a 100644 (file)
@@ -3,6 +3,7 @@
 #define __ASM_STACKTRACE_H
 
 #include <asm/ptrace.h>
+#include <linux/llist.h>
 
 struct stackframe {
        /*
@@ -13,6 +14,10 @@ struct stackframe {
        unsigned long sp;
        unsigned long lr;
        unsigned long pc;
+#ifdef CONFIG_KRETPROBES
+       struct llist_node *kr_cur;
+       struct task_struct *tsk;
+#endif
 };
 
 static __always_inline
@@ -22,6 +27,10 @@ void arm_get_current_stackframe(struct pt_regs *regs, struct stackframe *frame)
                frame->sp = regs->ARM_sp;
                frame->lr = regs->ARM_lr;
                frame->pc = regs->ARM_pc;
+#ifdef CONFIG_KRETPROBES
+               frame->kr_cur = NULL;
+               frame->tsk = current;
+#endif
 }
 
 extern int unwind_frame(struct stackframe *frame);
index 7b42ac0..00c1157 100644 (file)
@@ -42,6 +42,10 @@ void *return_address(unsigned int level)
        frame.sp = current_stack_pointer;
        frame.lr = (unsigned long)__builtin_return_address(0);
        frame.pc = (unsigned long)return_address;
+#ifdef CONFIG_KRETPROBES
+       frame.kr_cur = NULL;
+       frame.tsk = current;
+#endif
 
        walk_stackframe(&frame, save_return_addr, &data);
 
index db798ea..75e9055 100644 (file)
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 #include <linux/export.h>
+#include <linux/kprobes.h>
 #include <linux/sched.h>
 #include <linux/sched/debug.h>
 #include <linux/stacktrace.h>
@@ -65,6 +66,11 @@ int notrace unwind_frame(struct stackframe *frame)
        frame->sp = *(unsigned long *)(fp - 8);
        frame->pc = *(unsigned long *)(fp - 4);
 #endif
+#ifdef CONFIG_KRETPROBES
+       if (is_kretprobe_trampoline(frame->pc))
+               frame->pc = kretprobe_find_ret_addr(frame->tsk,
+                                       (void *)frame->fp, &frame->kr_cur);
+#endif
 
        return 0;
 }
@@ -156,6 +162,10 @@ static noinline void __save_stack_trace(struct task_struct *tsk,
                frame.lr = (unsigned long)__builtin_return_address(0);
                frame.pc = (unsigned long)__save_stack_trace;
        }
+#ifdef CONFIG_KRETPROBES
+       frame.kr_cur = NULL;
+       frame.tsk = tsk;
+#endif
 
        walk_stackframe(&frame, save_trace, &data);
 }
@@ -173,6 +183,10 @@ void save_stack_trace_regs(struct pt_regs *regs, struct stack_trace *trace)
        frame.sp = regs->ARM_sp;
        frame.lr = regs->ARM_lr;
        frame.pc = regs->ARM_pc;
+#ifdef CONFIG_KRETPROBES
+       frame.kr_cur = NULL;
+       frame.tsk = current;
+#endif
 
        walk_stackframe(&frame, save_trace, &data);
 }