kprobes: Fix stacktrace with kretprobes on x86
Subsystems (18)
Masami Hiramatsu
maintainerv6 cover kprobes: Fix stacktrace with kretprobes on x86
Hello,
Here is the 6th version of the series to fix the stacktrace with kretprobe on x86.
The previous version is;
https://lore.kernel.org/bpf/161676170650.330141.6214727134265514123.stgit@devnote2/
This version is rebased on the latest tip tree and add some patches for improving stacktrace[13/13].
Changes from v5:
[02/13]:
- Use dereference_symbol_descriptor() instead of dereference_function_descriptor()
[04/13]:
- Replace BUG_ON() with WARN_ON_ONCE() in __kretprobe_trampoline_handler().
[13/13]:
- Add a new patch to fix return address in earlier stage.
With this series, unwinder can unwind stack correctly from ftrace as below;
# cd /sys/kernel/debug/tracing
# echo > trace
# echo 1 > options/sym-offset
# echo r vfs_read >> kprobe_events
# echo r full_proxy_read >> kprobe_events
# echo traceoff:1 > events/kprobes/r_vfs_read_0/trigger
# echo stacktrace:1 > events/kprobes/r_full_proxy_read_0/trigger
# echo 1 > events/kprobes/enable
# cat /sys/kernel/debug/kprobes/list
ffffffff8133b740 r full_proxy_read+0x0 [FTRACE]
ffffffff812560b0 r vfs_read+0x0 [FTRACE]
# echo 0 > events/kprobes/enable
# cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 3/3 #P:8
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
<...>-134 [007] ...1 16.185877: r_full_proxy_read_0: (vfs_read+0x98/0x180 <- full_proxy_read)
<...>-134 [007] ...1 16.185901: <stack trace>
=> kretprobe_trace_func+0x209/0x300
=> kretprobe_dispatcher+0x4a/0x70
=> __kretprobe_trampoline_handler+0xd4/0x170
=> trampoline_handler+0x43/0x60
=> kretprobe_trampoline+0x2a/0x50
=> vfs_read+0x98/0x180
=> ksys_read+0x5f/0xe0
=> do_syscall_64+0x37/0x90
=> entry_SYSCALL_64_after_hwframe+0x44/0xae
<...>-134 [007] ...1 16.185902: r_vfs_read_0: (ksys_read+0x5f/0xe0 <- vfs_read)This shows the double return probes (vfs_read and full_proxy_read) on the stack correctly unwinded. (vfs_read will return to ksys_read+0x5f and full_proxy_read will return to vfs_read+0x98)
This actually changes the kretprobe behavisor a bit, now the instraction pointer in the pt_regs passed to kretprobe user handler is correctly set the real return address. So user handlers can get it via instruction_pointer() API.
You can also get this series from
git://git.kernel.org/pub/scm/linux/kernel/git/mhiramat/linux.git kprobes/kretprobe-stackfix-v6
Thank you,
---
Josh Poimboeuf (1):
x86/kprobes: Add UNWIND_HINT_FUNC on kretprobe_trampoline code
Masami Hiramatsu (12):
ia64: kprobes: Fix to pass correct trampoline address to the handler
kprobes: treewide: Replace arch_deref_entry_point() with dereference_symbol_descriptor()
kprobes: treewide: Remove trampoline_address from kretprobe_trampoline_handler()
kprobes: Add kretprobe_find_ret_addr() for searching return address
ARC: Add instruction_pointer_set() API
ia64: Add instruction_pointer_set() API
arm: kprobes: Make a space for regs->ARM_pc at kretprobe_trampoline
kprobes: Setup instruction pointer in __kretprobe_trampoline_handler x86/kprobes: Push a fake return address at kretprobe_trampoline x86/unwind: Recover kretprobe trampoline entry
tracing: Show kretprobe unknown indicator only for kretprobe_trampoline x86/kprobes: Fixup return address in generic trampoline handler
arch/arc/include/asm/ptrace.h | 5 ++ arch/arc/kernel/kprobes.c | 2 - arch/arm/probes/kprobes/core.c | 5 +- arch/arm64/kernel/probes/kprobes.c | 3 - arch/csky/kernel/probes/kprobes.c | 2 - arch/ia64/include/asm/ptrace.h | 5 ++ arch/ia64/kernel/kprobes.c | 15 ++--- arch/mips/kernel/kprobes.c | 3 - arch/parisc/kernel/kprobes.c | 4 + arch/powerpc/kernel/kprobes.c | 13 ---- arch/riscv/kernel/probes/kprobes.c | 2 - arch/s390/kernel/kprobes.c | 2 - arch/sh/kernel/kprobes.c | 2 - arch/sparc/kernel/kprobes.c | 2 - arch/x86/include/asm/kprobes.h | 1 arch/x86/include/asm/unwind.h | 23 +++++++ arch/x86/include/asm/unwind_hints.h | 5 ++ arch/x86/kernel/kprobes/core.c | 53 +++++++++++++++-- arch/x86/kernel/unwind_frame.c | 4 + arch/x86/kernel/unwind_guess.c | 3 - arch/x86/kernel/unwind_orc.c | 19 +++++- include/linux/kprobes.h | 41 +++++++++++-- kernel/kprobes.c | 108 +++++++++++++++++++++++++---------- kernel/trace/trace_output.c | 17 +----- lib/error-inject.c | 3 + 25 files changed, 237 insertions(+), 105 deletions(-)
--
Masami Hiramatsu (Linaro) <[email protected]>
Masami Hiramatsu
maintainerPATCH v6 1/13 ia64: kprobes: Fix to pass correct trampoline address to the handler
Commit e792ff804f49 ("ia64: kprobes: Use generic kretprobe trampoline handler") missed to pass the wrong trampoline address (it passes the descriptor address instead of function entry address).
This fixes it to pass correct trampoline address to __kretprobe_trampoline_handler(). This also changes to use correct symbol dereference function to get the function address from the kretprobe_trampoline.
---
Changes in v5:
- Fix a compile error typo.
--- arch/ia64/kernel/kprobes.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-)
arch/ia64/kernel/kprobes.c +5 -3
@@ -398,7 +398,8 @@ static void kretprobe_trampoline(void)int __kprobes trampoline_probe_handler (struct kprobe *p,struct pt_regs *regs) { - regs->cr_iip =__kretprobe_trampoline_handler (regs, kretprobe_trampoline,NULL ); + regs->cr_iip =__kretprobe_trampoline_handler (regs, +dereference_function_descriptor (kretprobe_trampoline),NULL );/* * By returning a non-zero value, we are telling * kprobe_handler() that we don't want the post_handler
@@ -414,7 +415,7 @@ void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri, ri->fp =NULL ;/* Replace the return addr with trampoline addr */ - regs->b0 = ((struct fnptr *)kretprobe_trampoline)->ip; + regs->b0 = (unsigned long )dereference_function_descriptor (kretprobe_trampoline); }/* Check the instruction in the slot is break */
@@ -918,14 +919,14 @@ static struct kprobe trampoline_p = {int __init arch_init_kprobes (void ) { trampoline_p.addr - (kprobe_opcode_t *)((struct fnptr *)kretprobe_trampoline)->ip; +dereference_function_descriptor (kretprobe_trampoline);return register_kprobe (&trampoline_p); }int __kprobes arch_trampoline_kprobe (struct kprobe *p) {if (p->addr = - (kprobe_opcode_t *)((struct fnptr *)kretprobe_trampoline)->ip) +dereference_function_descriptor (kretprobe_trampoline))return 1 ;return 0 ;
Masami Hiramatsu
maintainerPATCH v6 2/13 kprobes: treewide: Replace arch_deref_entry_point() with dereference_symbol_descriptor()
In reply to Masami HiramatsuReplace arch_deref_entry_point() with dereference_symbol_descriptor() because those are doing same thing.
---
Changes in v6:
- Use dereference_symbol_descriptor() so that it can handle address in modules correctly.
--- arch/ia64/kernel/kprobes.c | 5 ----- arch/powerpc/kernel/kprobes.c | 11 ----------- include/linux/kprobes.h | 1 - kernel/kprobes.c | 7 +------ lib/error-inject.c | 3 ++- 5 files changed, 3 insertions(+), 24 deletions(-)
arch/ia64/kernel/kprobes.c +0 -5
@@ -907,11 +907,6 @@ int __kprobes kprobe_exceptions_notify(struct notifier_block *self,return ret; } -unsigned long arch_deref_entry_point (void *entry) -{ -return ((struct fnptr *)entry)->ip; -} -static struct kprobe trampoline_p = { .pre_handler = trampoline_probe_handler };
arch/powerpc/kernel/kprobes.c +0 -11
@@ -539,17 +539,6 @@ int kprobe_fault_handler(struct pt_regs *regs, int trapnr) }NOKPROBE_SYMBOL (kprobe_fault_handler); -unsigned long arch_deref_entry_point (void *entry) -{ -#ifdef PPC64_ELF_ABI_v1 -if (!kernel_text_address ((unsigned long )entry)) -return ppc_global_function_entry (entry); - else -#endif -return (unsigned long )entry; -} -NOKPROBE_SYMBOL (arch_deref_entry_point); -static struct kprobe trampoline_p = { .addr = (kprobe_opcode_t *) &kretprobe_trampoline, .pre_handler = trampoline_probe_handler
include/linux/kprobes.h +0 -1
@@ -390,7 +390,6 @@ int register_kprobe(struct kprobe *p);void unregister_kprobe (struct kprobe *p);int register_kprobes (struct kprobe **kps,int num);void unregister_kprobes (struct kprobe **kps,int num); -unsigned long arch_deref_entry_point (void *);int register_kretprobe (struct kretprobe *rp);void unregister_kretprobe (struct kretprobe *rp);
kernel/kprobes.c +1 -6
@@ -1856,11 +1856,6 @@ static struct notifier_block kprobe_exceptions_nb = { .priority = 0x7fffffff /* we need to be notified first */ }; -unsigned long __weak arch_deref_entry_point (void *entry) -{ -return (unsigned long )entry; -} -#ifdef CONFIG_KRETPROBES unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,
@@ -2324,7 +2319,7 @@ static int __init populate_kprobe_blacklist(unsigned long *start,int ret;for (iter = start; iter < end; iter++) { - entry =arch_deref_entry_point ((void *)*iter); + entry = (unsigned long )dereference_symbol_descriptor ((void *)*iter); ret =kprobe_add_ksym_blacklist (entry);if (ret = -EINVAL )continue ;
lib/error-inject.c +2 -1
@@ -8,6 +8,7 @@#include <linux/mutex.h> #include <linux/list.h> #include <linux/slab.h> +#include <asm/sections.h> /* Whitelist of symbols that can be overridden for error injection. */ static LIST_HEAD (error_injection_list);
@@ -64,7 +65,7 @@ static void populate_error_injection_list(struct error_injection_entry *start,mutex_lock (&ei_mutex);for (iter = start; iter < end; iter++) { - entry =arch_deref_entry_point ((void *)iter->addr); + entry = (unsigned long )dereference_symbol_descriptor ((void *)iter->addr);if (!kernel_text_address (entry) || !kallsyms_lookup_size_offset (entry, &size, &offset)) {
Masami Hiramatsu
maintainerPATCH v6 3/13 kprobes: treewide: Remove trampoline_address from kretprobe_trampoline_handler()
In reply to Masami HiramatsuRemove trampoline_address from kretprobe_trampoline_handler(). Instead of passing the address, kretprobe_trampoline_handler() can use new kretprobe_trampoline_addr().
---
Changes in v3:
- Remove wrong kretprobe_trampoline declaration from
arch/x86/include/asm/kprobes.h. Changes in v2: - Remove arch_deref_entry_point() from comment. --- arch/arc/kernel/kprobes.c | 2 +- arch/arm/probes/kprobes/core.c | 3 +-- arch/arm64/kernel/probes/kprobes.c | 3 +-- arch/csky/kernel/probes/kprobes.c | 2 +- arch/ia64/kernel/kprobes.c | 5 ++--- arch/mips/kernel/kprobes.c | 3 +-- arch/parisc/kernel/kprobes.c | 4 ++-- arch/powerpc/kernel/kprobes.c | 2 +- arch/riscv/kernel/probes/kprobes.c | 2 +- arch/s390/kernel/kprobes.c | 2 +- arch/sh/kernel/kprobes.c | 2 +- arch/sparc/kernel/kprobes.c | 2 +- arch/x86/include/asm/kprobes.h | 1 - arch/x86/kernel/kprobes/core.c | 2 +- include/linux/kprobes.h | 18 +++++++++++++----- kernel/kprobes.c | 3 +-- 16 files changed, 29 insertions(+), 27 deletions(-)
arch/arc/kernel/kprobes.c +1 -1
@@ -397,7 +397,7 @@ void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,static int __kprobes trampoline_probe_handler (struct kprobe *p,struct pt_regs *regs) { - regs->ret =__kretprobe_trampoline_handler (regs, &kretprobe_trampoline,NULL ); + regs->ret =__kretprobe_trampoline_handler (regs,NULL );/* By returning a non zero value, we are telling the kprobe handler * that we don't want the post_handler to run
arch/arm/probes/kprobes/core.c +1 -2
@@ -413,8 +413,7 @@ void __naked __kprobes kretprobe_trampoline(void)/* Called from kretprobe_trampoline */ static __used __kprobes void *trampoline_handler (struct pt_regs *regs) { -return (void *)kretprobe_trampoline_handler (regs, &kretprobe_trampoline, - (void *)regs->ARM_fp); +return (void *)kretprobe_trampoline_handler (regs, (void *)regs->ARM_fp); } void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
arch/arm64/kernel/probes/kprobes.c +1 -2
@@ -413,8 +413,7 @@ int __init arch_populate_kprobe_blacklist(void)void __kprobes __used *trampoline_probe_handler (struct pt_regs *regs) { -return (void *)kretprobe_trampoline_handler (regs, &kretprobe_trampoline, - (void *)kernel_stack_pointer (regs)); +return (void *)kretprobe_trampoline_handler (regs, (void *)kernel_stack_pointer (regs)); } void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
arch/csky/kernel/probes/kprobes.c +1 -1
@@ -404,7 +404,7 @@ int __init arch_populate_kprobe_blacklist(void)void __kprobes __used *trampoline_probe_handler (struct pt_regs *regs) { -return (void *)kretprobe_trampoline_handler (regs, &kretprobe_trampoline,NULL ); +return (void *)kretprobe_trampoline_handler (regs,NULL ); } void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
arch/ia64/kernel/kprobes.c +2 -3
@@ -392,14 +392,13 @@ static void __kprobes set_current_kprobe(struct kprobe *p,__this_cpu_write (current_kprobe, p); } -static void kretprobe_trampoline (void ) +void kretprobe_trampoline (void ) { }int __kprobes trampoline_probe_handler (struct kprobe *p,struct pt_regs *regs) { - regs->cr_iip =__kretprobe_trampoline_handler (regs, -dereference_function_descriptor (kretprobe_trampoline),NULL ); + regs->cr_iip =__kretprobe_trampoline_handler (regs,NULL );/* * By returning a non-zero value, we are telling * kprobe_handler() that we don't want the post_handler
arch/mips/kernel/kprobes.c +1 -2
@@ -489,8 +489,7 @@ void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,static int __kprobes trampoline_probe_handler (struct kprobe *p,struct pt_regs *regs) { -instruction_pointer (regs) =__kretprobe_trampoline_handler (regs, - kretprobe_trampoline,NULL ); +instruction_pointer (regs) =__kretprobe_trampoline_handler (regs,NULL );/* * By returning a non-zero value, we are telling * kprobe_handler() that we don't want the post_handler
arch/parisc/kernel/kprobes.c +2 -2
@@ -175,7 +175,7 @@ int __kprobes parisc_kprobe_ss_handler(struct pt_regs *regs)return 1 ; } -static inline void kretprobe_trampoline (void ) +void kretprobe_trampoline (void ) { asmvolatile ("nop" ); asmvolatile ("nop" );
@@ -193,7 +193,7 @@ static int __kprobes trampoline_probe_handler(struct kprobe *p, {unsigned long orig_ret_address; - orig_ret_address =__kretprobe_trampoline_handler (regs, trampoline_p.addr,NULL ); + orig_ret_address =__kretprobe_trampoline_handler (regs,NULL );instruction_pointer_set (regs, orig_ret_address);return 1 ;
arch/powerpc/kernel/kprobes.c +1 -1
@@ -399,7 +399,7 @@ static int trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs) {unsigned long orig_ret_address; - orig_ret_address =__kretprobe_trampoline_handler (regs, &kretprobe_trampoline,NULL ); + orig_ret_address =__kretprobe_trampoline_handler (regs,NULL );/* * We get here through one of two paths: * 1. by taking a trap -> kprobe_handler() -> here
arch/riscv/kernel/probes/kprobes.c +1 -1
@@ -385,7 +385,7 @@ int __init arch_populate_kprobe_blacklist(void)void __kprobes __used *trampoline_probe_handler (struct pt_regs *regs) { -return (void *)kretprobe_trampoline_handler (regs, &kretprobe_trampoline,NULL ); +return (void *)kretprobe_trampoline_handler (regs,NULL ); } void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
arch/s390/kernel/kprobes.c +1 -1
@@ -351,7 +351,7 @@ static void __used kretprobe_trampoline_holder(void)*/ static int trampoline_probe_handler (struct kprobe *p,struct pt_regs *regs) { - regs->psw.addr =__kretprobe_trampoline_handler (regs, &kretprobe_trampoline,NULL ); + regs->psw.addr =__kretprobe_trampoline_handler (regs,NULL );/* * By returning a non-zero value, we are telling * kprobe_handler() that we don't want the post_handler
arch/sh/kernel/kprobes.c +1 -1
@@ -303,7 +303,7 @@ static void __used kretprobe_trampoline_holder(void)*/ int __kprobes trampoline_probe_handler (struct kprobe *p,struct pt_regs *regs) { - regs->pc =__kretprobe_trampoline_handler (regs, &kretprobe_trampoline,NULL ); + regs->pc =__kretprobe_trampoline_handler (regs,NULL );return 1 ; }
arch/sparc/kernel/kprobes.c +1 -1
@@ -468,7 +468,7 @@ static int __kprobes trampoline_probe_handler(struct kprobe *p, {unsigned long orig_ret_address =0 ; - orig_ret_address =__kretprobe_trampoline_handler (regs, &kretprobe_trampoline,NULL ); + orig_ret_address =__kretprobe_trampoline_handler (regs,NULL ); regs->tpc = orig_ret_address; regs->tnpc = orig_ret_address +4 ;
arch/x86/include/asm/kprobes.h +0 -1
@@ -49,7 +49,6 @@ extern __visible kprobe_opcode_t optprobe_template_end[];extern const int kretprobe_blacklist_size;void arch_remove_kprobe (struct kprobe *p); -asmlinkage void kretprobe_trampoline(void);extern void arch_kprobe_override_function (struct pt_regs *regs);
arch/x86/kernel/kprobes/core.c +1 -1
@@ -1070,7 +1070,7 @@ __used __visible void *trampoline_handler(struct pt_regs *regs) regs->ip = (unsigned long )&kretprobe_trampoline; regs->orig_ax = ~0UL ; -return (void *)kretprobe_trampoline_handler (regs, &kretprobe_trampoline, ®s->sp); +return (void *)kretprobe_trampoline_handler (regs, ®s->sp); }NOKPROBE_SYMBOL (trampoline_handler);
include/linux/kprobes.h +13 -5
@@ -205,15 +205,23 @@ extern void arch_prepare_kretprobe(struct kretprobe_instance *ri, struct pt_regs *regs);extern int arch_trampoline_kprobe (struct kprobe *p); +void kretprobe_trampoline (void ); +/* +* Since some architecture uses structured function pointer, +* use dereference_function_descriptor() to get real function address. +*/ +static nokprobe_inline void *kretprobe_trampoline_addr(void) +{ +return dereference_function_descriptor (kretprobe_trampoline); +} +/* If the trampoline handler called from a kprobe, use this version */ unsigned long __kretprobe_trampoline_handler (struct pt_regs *regs, -void *trampoline_address, -void *frame_pointer); +void *frame_pointer);static nokprobe_inline unsigned long kretprobe_trampoline_handler (struct pt_regs *regs, -void *trampoline_address, -void *frame_pointer) +void *frame_pointer) {unsigned long ret;/*
@@ -222,7 +230,7 @@ unsigned long kretprobe_trampoline_handler(struct pt_regs *regs,* be running at this point. */ kprobe_busy_begin (); - ret =__kretprobe_trampoline_handler (regs, trampoline_address, frame_pointer); + ret =__kretprobe_trampoline_handler (regs, frame_pointer);kprobe_busy_end ();return ret;
kernel/kprobes.c +1 -2
@@ -1859,7 +1859,6 @@ static struct notifier_block kprobe_exceptions_nb = {#ifdef CONFIG_KRETPROBES unsigned long __kretprobe_trampoline_handler (struct pt_regs *regs, -void *trampoline_address,void *frame_pointer) {kprobe_opcode_t *correct_ret_addr =NULL ;
@@ -1874,7 +1873,7 @@ unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,BUG_ON (ri->fp != frame_pointer); -if (ri->ret_addr != trampoline_address) { +if (ri->ret_addr !=kretprobe_trampoline_addr ()) { correct_ret_addr = ri->ret_addr;/* * This is the real return address. Any other
Masami Hiramatsu
maintainerPATCH v6 4/13 kprobes: Add kretprobe_find_ret_addr() for searching return address
In reply to Masami HiramatsuAdd kretprobe_find_ret_addr() for searching correct return address from kretprobe instance list.
---
Changes in v6:
- Replace BUG_ON() with WARN_ON_ONCE() in __kretprobe_trampoline_handler().
Changes in v3:
- Remove generic stacktrace fixup. Instead, it should be solved in each unwinder. This just provide the generic interface.
Changes in v2:
- Add is_kretprobe_trampoline() for checking address outside of kretprobe_find_ret_addr()
- Remove unneeded addr from kretprobe_find_ret_addr()
- Rename fixup_kretprobe_tramp_addr() to fixup_kretprobe_trampoline()
--- include/linux/kprobes.h | 22 +++++++++++ kernel/kprobes.c | 91 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 87 insertions(+), 26 deletions(-)
include/linux/kprobes.h +22 -0
@@ -215,6 +215,14 @@ static nokprobe_inline void *kretprobe_trampoline_addr(void)return dereference_function_descriptor (kretprobe_trampoline); } +static nokprobe_inline bool is_kretprobe_trampoline(unsigned long addr) +{ +return (void *)addr =kretprobe_trampoline_addr (); +} + +unsigned long kretprobe_find_ret_addr (struct task_struct *tsk,void *fp, +struct llist_node **cur); +/* If the trampoline handler called from a kprobe, use this version */ unsigned long __kretprobe_trampoline_handler (struct pt_regs *regs,void *frame_pointer);
@@ -514,6 +522,20 @@ static inline bool is_kprobe_optinsn_slot(unsigned long addr) }#endif +#if !defined(CONFIG_KRETPROBES ) +static nokprobe_inline bool is_kretprobe_trampoline(unsigned long addr) +{ +return false; +} + +static nokprobe_inline +unsigned long kretprobe_find_ret_addr (struct task_struct *tsk,void *fp, +struct llist_node **cur) +{ +return 0 ; +} +#endif +/* Returns true if kprobes handled the fault */ static nokprobe_inline bool kprobe_page_fault(struct pt_regs *regs,unsigned int trap)
kernel/kprobes.c +65 -26
116 lines not on this page · raw message
Masami Hiramatsu
maintainerPATCH v6 5/13 x86/kprobes: Add UNWIND_HINT_FUNC on kretprobe_trampoline code
In reply to Masami HiramatsuFrom: Josh Poimboeuf <[email protected]>
Add UNWIND_HINT_FUNC on kretporbe_trampoline code so that ORC information is generated on the kretprobe_trampoline correctly.
Note that when the CONFIG_FRAME_POINTER=y, since the kretprobe_trampoline skips updating frame pointer, the stack frame of the kretprobe_trampoline seems non-standard. So this marks it is STACK_FRAME_NON_STANDARD() and undefine UNWIND_HINT_FUNC. Anyway, with the frame pointer, FP unwinder can unwind the stack frame correctly without that hint.
---
Changes in v4:
- Apply UNWIND_HINT_FUNC only if CONFIG_FRAME_POINTER=n.
--- arch/x86/include/asm/unwind_hints.h | 5 +++++ arch/x86/kernel/kprobes/core.c | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-)
arch/x86/include/asm/unwind_hints.h +5 -0
@@ -52,6 +52,11 @@ UNWIND_HINT sp_reg=ORC_REG_SP sp_offset=8 type=UNWIND_HINT_TYPE_FUNC .endm +#else + +#define UNWIND_HINT_FUNC \ + UNWIND_HINT(ORC_REG_SP, 8, UNWIND_HINT_TYPE_FUNC, 0) +#endif /* __ASSEMBLY__ */ #endif /* _ASM_X86_UNWIND_HINTS_H */
arch/x86/kernel/kprobes/core.c +15 -2
@@ -1019,6 +1019,19 @@ int kprobe_int3_handler(struct pt_regs *regs) }NOKPROBE_SYMBOL (kprobe_int3_handler); +#ifdef CONFIG_FRAME_POINTER +/* +* kretprobe_trampoline skips updating frame pointer. The frame pointer +* saved in trampoline_handler points to the real caller function's +* frame pointer. Thus the kretprobe_trampoline doesn't seems to have a +* standard stack frame with CONFIG_FRAME_POINTER=y. +* Let's mark it non-standard function. Anyway, FP unwinder can correctly +* unwind without the hint. +*/ +STACK_FRAME_NON_STANDARD (kretprobe_trampoline); +#undef UNWIND_HINT_FUNC +#define UNWIND_HINT_FUNC +#endif /* * When a retprobed function returns, this code saves registers and * calls trampoline_handler() runs, which calls the kretprobe's handler.
@@ -1031,6 +1044,7 @@ asm(/* We don't bother saving the ss register */ #ifdef CONFIG_X86_64 " pushq %rsp\n" +UNWIND_HINT_FUNC " pushfq\n" SAVE_REGS_STRING " movq %rsp, %rdi\n"
@@ -1041,6 +1055,7 @@ asm(" popfq\n" #else" pushl %esp\n" +UNWIND_HINT_FUNC " pushfl\n" SAVE_REGS_STRING " movl %esp, %eax\n"
@@ -1054,8 +1069,6 @@ asm(".size kretprobe_trampoline, .-kretprobe_trampoline\n" );NOKPROBE_SYMBOL (kretprobe_trampoline); -STACK_FRAME_NON_STANDARD (kretprobe_trampoline); -/* * Called from kretprobe_trampoline
Masami Hiramatsu
maintainerPATCH v6 6/13 ARC: Add instruction_pointer_set() API
In reply to Masami HiramatsuAdd instruction_pointer_set() API for arc.
--- arch/arc/include/asm/ptrace.h | 5 +++++ 1 file changed, 5 insertions(+)
arch/arc/include/asm/ptrace.h +5 -0
@@ -149,6 +149,11 @@ static inline long regs_return_value(struct pt_regs *regs)return (long )regs->r0; } +static inline void instruction_pointer_set (struct pt_regs *regs, +unsigned long val) +{ +instruction_pointer (regs) = val; +}#endif /* !__ASSEMBLY__ */ #endif /* __ASM_PTRACE_H */
Masami Hiramatsu
maintainerPATCH v6 7/13 ia64: Add instruction_pointer_set() API
In reply to Masami HiramatsuAdd instruction_pointer_set() API for ia64.
---
Changes in v4:
- Make the API macro for avoiding a build error.
--- arch/ia64/include/asm/ptrace.h | 5 +++++ 1 file changed, 5 insertions(+)
arch/ia64/include/asm/ptrace.h +5 -0
@@ -51,6 +51,11 @@* the canonical representation by adding to instruction pointer. */ # define instruction_pointer (regs) ((regs)->cr_iip + ia64_psr(regs)->ri) +# define instruction_pointer_set (regs, val) \ + ({ \ + ia64_psr(regs)->ri = (val & 0xf); \ + regs->cr_iip = (val & ~0xfULL); \ + })static inline unsigned long user_stack_pointer (struct pt_regs *regs) {
Masami Hiramatsu
maintainerPATCH v6 8/13 arm: kprobes: Make a space for regs->ARM_pc at kretprobe_trampoline
In reply to Masami HiramatsuChange kretprobe_trampoline to make a space for regs->ARM_pc so that kretprobe_trampoline_handler can call instruction_pointer_set() safely.
--- arch/arm/probes/kprobes/core.c | 2 ++ 1 file changed, 2 insertions(+)
arch/arm/probes/kprobes/core.c +2 -0
@@ -397,11 +397,13 @@ int __kprobes kprobe_exceptions_notify(struct notifier_block *self,void __naked __kprobes kretprobe_trampoline (void ) { __asm__ __volatile__ ( + "sub sp, sp, #16 \n\t" "stmdb sp!, {r0 - r11} \n\t" "mov r0, sp \n\t" "bl trampoline_handler \n\t" "mov lr, r0 \n\t" "ldmia sp!, {r0 - r11} \n\t" + "add sp, sp, #16 \n\t" #ifdef CONFIG_THUMB2_KERNEL "bx lr \n\t" #else
Masami Hiramatsu
maintainerPATCH v6 9/13 kprobes: Setup instruction pointer in __kretprobe_trampoline_handler
In reply to Masami HiramatsuTo simplify the stacktrace with pt_regs from kretprobe handler, set the correct return address to the instruction pointer in the pt_regs before calling kretprobe handlers.
---
Changes in v3:
- Cast the correct_ret_addr to unsigned long.
--- kernel/kprobes.c | 3 +++ 1 file changed, 3 insertions(+)
kernel/kprobes.c +3 -0
@@ -1914,6 +1914,9 @@ unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,BUG_ON (1 ); } +/* Set the instruction pointer to the correct address */ +instruction_pointer_set (regs, (unsigned long )correct_ret_addr); +/* Run them. */ first = current->kretprobe_instances.first;while (first) {
Masami Hiramatsu
maintainerPATCH v6 10/13 x86/kprobes: Push a fake return address at kretprobe_trampoline
In reply to Masami HiramatsuThis changes x86/kretprobe stack frame on kretprobe_trampoline a bit, which now push the kretprobe_trampoline as a fake return address at the bottom of the stack frame. With this fix, the ORC unwinder will see the kretprobe_trampoline as a return address.
--- arch/x86/kernel/kprobes/core.c | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-)
arch/x86/kernel/kprobes/core.c +22 -9
@@ -1041,28 +1041,31 @@ asm( ".global kretprobe_trampoline\n" ".type kretprobe_trampoline, @function\n" "kretprobe_trampoline:\n" -/* We don't bother saving the ss register */ #ifdef CONFIG_X86_64 -" pushq %rsp\n" + /* Push fake return address to tell the unwinder it's a kretprobe */ + " pushq $kretprobe_trampoline\n" UNWIND_HINT_FUNC + /* Save the sp-8, this will be fixed later */ + " pushq %rsp\n" " pushfq\n" SAVE_REGS_STRING " movq %rsp, %rdi\n" " call trampoline_handler\n" -/* Replace saved sp with true return address. */ -" movq %rax, 19*8(%rsp)\n" RESTORE_REGS_STRING + " addq $8, %rsp\n" " popfq\n" #else - " pushl %esp\n" +/* Push fake return address to tell the unwinder it's a kretprobe */ +" pushl $kretprobe_trampoline\n" UNWIND_HINT_FUNC +/* Save the sp-4, this will be fixed later */ +" pushl %esp\n" " pushfl\n" SAVE_REGS_STRING " movl %esp, %eax\n" " call trampoline_handler\n" -/* Replace saved sp with true return address. */ -" movl %eax, 15*4(%esp)\n" RESTORE_REGS_STRING +" addl $4, %esp\n" " popfl\n" #endif " ret\n"
@@ -1073,8 +1076,10 @@ NOKPROBE_SYMBOL(kretprobe_trampoline);/* * Called from kretprobe_trampoline */ -__used __visible void *trampoline_handler(struct pt_regs *regs) +__used __visible void trampoline_handler(struct pt_regs *regs) { +unsigned long *frame_pointer; +/* fixup registers */ regs->cs = __KERNEL_CS;#ifdef CONFIG_X86_32
@@ -1082,8 +1087,16 @@ __used __visible void *trampoline_handler(struct pt_regs *regs)#endif regs->ip = (unsigned long )&kretprobe_trampoline; regs->orig_ax = ~0UL ; + regs->sp +=sizeof (long ); + frame_pointer = ((unsigned long *)®s->sp) +1 ; -return (void *)kretprobe_trampoline_handler (regs, ®s->sp); +/* Replace fake return address with real one. */ + *frame_pointer =kretprobe_trampoline_handler (regs, frame_pointer); +/* +* Move flags to sp so that kretprobe_trapmoline can return +* right after popf. +*/ + regs->sp = regs->flags; }NOKPROBE_SYMBOL (trampoline_handler);
Masami Hiramatsu
maintainerPATCH v6 11/13 x86/unwind: Recover kretprobe trampoline entry
In reply to Masami HiramatsuSince the kretprobe replaces the function return address with the kretprobe_trampoline on the stack, x86 unwinders can not continue the stack unwinding at that point, or record kretprobe_trampoline instead of correct return address.
To fix this issue, find the correct return address from task's kretprobe_instances as like as function-graph tracer does.
With this fix, the unwinder can correctly unwind the stack from kretprobe event on x86, as below.
<...>-135 [003] ...1 6.722338: r_full_proxy_read_0: (vfs_read+0xab/0x1a0 <- full_proxy_read)
<...>-135 [003] ...1 6.722377: <stack trace> => kretprobe_trace_func+0x209/0x2f0
=> kretprobe_dispatcher+0x4a/0x70
=> __kretprobe_trampoline_handler+0xca/0x150
=> trampoline_handler+0x44/0x70
=> kretprobe_trampoline+0x2a/0x50
=> vfs_read+0xab/0x1a0
=> ksys_read+0x5f/0xe0
=> do_syscall_64+0x33/0x40
=> entry_SYSCALL_64_after_hwframe+0x44/0xae
---
Changes in v5:
- Fix the case of interrupt happens on kretprobe_trampoline+0.
Changes in v3:
- Split out the kretprobe side patch
- Fix build error when CONFIG_KRETPROBES=n.
Changes in v2:
- Remove kretprobe wrapper functions from unwind_orc.c
- Do not fixup state->ip when unwinding with regs because kretprobe fixup instruction pointer before calling handler.
--- arch/x86/include/asm/unwind.h | 23 +++++++++++++++++++++++ arch/x86/kernel/unwind_frame.c | 4 ++-- arch/x86/kernel/unwind_guess.c | 3 +-- arch/x86/kernel/unwind_orc.c | 19 +++++++++++++++---- 4 files changed, 41 insertions(+), 8 deletions(-)
arch/x86/include/asm/unwind.h +23 -0
@@ -4,6 +4,7 @@#include <linux/sched.h> #include <linux/ftrace.h> +#include <linux/kprobes.h> #include <asm/ptrace.h> #include <asm/stacktrace.h>
@@ -15,6 +16,7 @@ struct unwind_state {unsigned long stack_mask;struct task_struct *task;int graph_idx; +struct llist_node *kr_cur;bool error;#if defined(CONFIG_UNWINDER_ORC )bool signal, full_regs;
@@ -99,6 +101,27 @@ void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size, void *orc, size_t orc_size) {}#endif +static inline +unsigned long unwind_recover_kretprobe (struct unwind_state *state, +unsigned long addr,unsigned long *addr_p) +{ +return is_kretprobe_trampoline (addr) ? +kretprobe_find_ret_addr (state->task, addr_p, &state->kr_cur) : + addr; +} + +/* Recover the return address modified by instrumentation (e.g. kretprobe) */ +static inline +unsigned long unwind_recover_ret_addr (struct unwind_state *state, +unsigned long addr,unsigned long *addr_p) +{ +unsigned long ret; + + ret =ftrace_graph_ret_addr (state->task, &state->graph_idx, + addr, addr_p); +return unwind_recover_kretprobe (state, ret, addr_p); +} +/* * This disables KASAN checking when reading a value from another task's stack, * since the other task could be running on another CPU and could have poisoned
arch/x86/kernel/unwind_frame.c +2 -2
@@ -3,6 +3,7 @@#include <linux/sched/task.h> #include <linux/sched/task_stack.h> #include <linux/interrupt.h> +#include <linux/kprobes.h> #include <asm/sections.h> #include <asm/ptrace.h> #include <asm/bitops.h>
@@ -240,8 +241,7 @@ static bool update_stack_state(struct unwind_state *state,else { addr_p =unwind_get_return_address_ptr (state); addr =READ_ONCE_TASK_STACK (state->task, *addr_p); - state->ip =ftrace_graph_ret_addr (state->task, &state->graph_idx, - addr, addr_p); + state->ip =unwind_recover_ret_addr (state, addr, addr_p); }/* Save the original stack pointer for unwind_dump(): */
arch/x86/kernel/unwind_guess.c +1 -2
@@ -15,8 +15,7 @@ unsigned long unwind_get_return_address(struct unwind_state *state) addr =READ_ONCE_NOCHECK (*state->sp); -return ftrace_graph_ret_addr (state->task, &state->graph_idx, - addr, state->sp); +return unwind_recover_ret_addr (state, addr, state->sp); }EXPORT_SYMBOL_GPL (unwind_get_return_address);
arch/x86/kernel/unwind_orc.c +15 -4
@@ -2,6 +2,7 @@#include <linux/objtool.h> #include <linux/module.h> #include <linux/sort.h> +#include <linux/kprobes.h> #include <asm/ptrace.h> #include <asm/stacktrace.h> #include <asm/unwind.h>
@@ -534,9 +535,8 @@ bool unwind_next_frame(struct unwind_state *state)if (!deref_stack_reg (state, ip_p, &state->ip)) goto err; - state->ip =ftrace_graph_ret_addr (state->task, &state->graph_idx, - state->ip, (void *)ip_p); - + state->ip =unwind_recover_ret_addr (state, state->ip, + (unsigned long *)ip_p); state->sp = sp; state->regs =NULL ; state->prev_regs =NULL ;
@@ -549,7 +549,15 @@ bool unwind_next_frame(struct unwind_state *state) (void *)orig_ip); goto err; } - +/* +* There is a small chance to interrupt at the entry of +* kretprobe_trampoline where the ORC info doesn't exist. +* That point is right after the RET to kretprobe_trampoline +* which was modified return address. So the @addr_p must +* be right before the regs->sp. +*/ + state->ip =unwind_recover_kretprobe (state, state->ip, + (unsigned long *)(state->sp -sizeof (long ))); state->regs = (struct pt_regs *)sp; state->prev_regs =NULL ; state->full_regs = true;
@@ -562,6 +570,9 @@ bool unwind_next_frame(struct unwind_state *state) (void *)orig_ip); goto err; } +/* See UNWIND_HINT_TYPE_REGS case comment. */ + state->ip =unwind_recover_kretprobe (state, state->ip, + (unsigned long *)(state->sp -sizeof (long )));if (state->full_regs) state->prev_regs = state->regs;
Masami Hiramatsu
maintainerPATCH v6 12/13 tracing: Show kretprobe unknown indicator only for kretprobe_trampoline
In reply to Masami Hiramatsuftrace shows "[unknown/kretprobe'd]" indicator all addresses in the kretprobe_trampoline, but the modified address by kretprobe should be only kretprobe_trampoline+0.
--- kernel/trace/trace_output.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-)
kernel/trace/trace_output.c +4 -13
@@ -8,6 +8,7 @@#include <linux/module.h> #include <linux/mutex.h> #include <linux/ftrace.h> +#include <linux/kprobes.h> #include <linux/sched/clock.h> #include <linux/sched/mm.h>
@@ -346,22 +347,12 @@ int trace_output_call(struct trace_iterator *iter, char *name, char *fmt, ...) }EXPORT_SYMBOL_GPL (trace_output_call); -#ifdef CONFIG_KRETPROBES -static inline const char *kretprobed (const char *name) +static inline const char *kretprobed (const char *name,unsigned long addr) { -static const char tramp_name[] ="kretprobe_trampoline" ; -int size =sizeof (tramp_name); - -if (strncmp (tramp_name, name, size) =0 ) +if (is_kretprobe_trampoline (addr))return "[unknown/kretprobe'd]" ;return name; } -#else -static inline const char *kretprobed (const char *name) -{ -return name; -} -#endif /* CONFIG_KRETPROBES */ void trace_seq_print_sym (struct trace_seq *s,unsigned long address,bool offset)
@@ -374,7 +365,7 @@ trace_seq_print_sym(struct trace_seq *s, unsigned long address, bool offset)sprint_symbol (str, address); elsekallsyms_lookup (address ,NULL ,NULL ,NULL ,str ); - name =kretprobed (str); + name =kretprobed (str, address);if (name &&strlen (name)) {trace_seq_puts (s, name);
Masami Hiramatsu
maintainerPATCH v6 13/13 x86/kprobes: Fixup return address in generic trampoline handler
In reply to Masami HiramatsuIn x86, kretprobe trampoline address on the stack frame will be replaced with the real return address after returning from trampoline_handler. Before fixing the return address, the real return address can be found in the current->kretprobe_instances.
However, since there is a window between updating the current->kretprobe_instances and fixing the address on the stack, if an interrupt caused at that timing and the interrupt handler does stacktrace, it may fail to unwind because it can not get the correct return address from current->kretprobe_instances.
This will minimize that window by fixing the return address right before updating current->kretprobe_instances.
--- arch/x86/kernel/kprobes/core.c | 15 +++++++++++++-- kernel/kprobes.c | 8 ++++++++ 2 files changed, 21 insertions(+), 2 deletions(-)
arch/x86/kernel/kprobes/core.c +13 -2
@@ -1032,6 +1032,7 @@ STACK_FRAME_NON_STANDARD(kretprobe_trampoline);#undef UNWIND_HINT_FUNC#define UNWIND_HINT_FUNC #endif +/* * When a retprobed function returns, this code saves registers and * calls trampoline_handler() runs, which calls the kretprobe's handler.
@@ -1073,6 +1074,17 @@ asm( );NOKPROBE_SYMBOL (kretprobe_trampoline); +void arch_kretprobe_fixup_return (struct pt_regs *regs, +unsigned long correct_ret_addr) +{ +unsigned long *frame_pointer; + + frame_pointer = ((unsigned long *)®s->sp) +1 ; + +/* Replace fake return address with real one. */ + *frame_pointer = correct_ret_addr; +} +/* * Called from kretprobe_trampoline */
@@ -1090,8 +1102,7 @@ __used __visible void trampoline_handler(struct pt_regs *regs) regs->sp +=sizeof (long ); frame_pointer = ((unsigned long *)®s->sp) +1 ; -/* Replace fake return address with real one. */ - *frame_pointer =kretprobe_trampoline_handler (regs, frame_pointer); +kretprobe_trampoline_handler (regs, frame_pointer);/* * Move flags to sp so that kretprobe_trapmoline can return * right after popf.
kernel/kprobes.c +8 -0
@@ -1899,6 +1899,12 @@ unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp, }NOKPROBE_SYMBOL (kretprobe_find_ret_addr); +void __weak arch_kretprobe_fixup_return (struct pt_regs *regs, +unsigned long correct_ret_addr) +{ +/* Do nothing by default. */ +} +unsigned long __kretprobe_trampoline_handler (struct pt_regs *regs,void *frame_pointer) {
@@ -1940,6 +1946,8 @@ unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs, first = first->next; } +arch_kretprobe_fixup_return (regs, (unsigned long )correct_ret_addr); +/* Unlink all nodes for this frame. */ first = current->kretprobe_instances.first; current->kretprobe_instances.first = node->next;
Quoted 69 lines · Masami Hiramatsu
Hello,
Here is the 6th version of the series to fix the stacktrace with kretprobe on x86.
The previous version is;
https://lore.kernel.org/bpf/161676170650.330141.6214727134265514123.stgit@devnote2/
This version is rebased on the latest tip tree and add some patches for improving stacktrace[13/13].
Changes from v5:
[02/13]:
- Usedereference_symbol_descriptor()instead ofdereference_function_descriptor()
[04/13]:
- ReplaceBUG_ON()withWARN_ON_ONCE()in__kretprobe_trampoline_handler().
[13/13]:
- Add a new patch to fix return address in earlier stage.With this series, unwinder can unwind stack correctly from ftrace as below;
# cd /sys/kernel/debug/tracing # echo > trace # echo 1 > options/sym-offset # echo r vfs_read >> kprobe_events # echo r full_proxy_read >> kprobe_events # echo traceoff:1 > events/kprobes/r_vfs_read_0/trigger # echo stacktrace:1 > events/kprobes/r_full_proxy_read_0/trigger # echo 1 > events/kprobes/enable # cat /sys/kernel/debug/kprobes/list ffffffff8133b740 r full_proxy_read+0x0 [FTRACE] ffffffff812560b0 r vfs_read+0x0 [FTRACE] # echo 0 > events/kprobes/enable # cat trace # tracer: nop # # entries-in-buffer/entries-written: 3/3 #P:8 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | <...>-134 [007] ...1 16.185877: r_full_proxy_read_0: (vfs_read+0x98/0x180 <- full_proxy_read) <...>-134 [007] ...1 16.185901: <stack trace> => kretprobe_trace_func+0x209/0x300 => kretprobe_dispatcher+0x4a/0x70 => __kretprobe_trampoline_handler+0xd4/0x170 => trampoline_handler+0x43/0x60 => kretprobe_trampoline+0x2a/0x50 => vfs_read+0x98/0x180 => ksys_read+0x5f/0xe0 => do_syscall_64+0x37/0x90 => entry_SYSCALL_64_after_hwframe+0x44/0xae <...>-134 [007] ...1 16.185902: r_vfs_read_0: (ksys_read+0x5f/0xe0 <- vfs_read)This shows the double return probes (
vfs_readandfull_proxy_read) on the stack correctly unwinded. (vfs_readwill return toksys_read+0x5fandfull_proxy_readwill return tovfs_read+0x98)This actually changes the kretprobe behavisor a bit, now the instraction pointer in the
pt_regspassed to kretprobe user handler is correctly set the real return address. So user handlers can get it viainstruction_pointer()API.
Quoted 8 lines
You can also get this series from
git://git.kernel.org/pub/scm/linux/kernel/git/mhiramat/linux.gitkprobes/kretprobe-stackfix-v6Thank you,
---
Thanks for following up on this! I've applied this patch set on top of bpf-next and tested with my local BPF-based tool that uses stack traces in kretprobes heavily. It all works now and I'm getting meaningful and correctly looking stacktraces. Thanks a lot!
Quoted 47 lines
Josh Poimboeuf (1):
x86/kprobes: AddUNWIND_HINT_FUNConkretprobe_trampolinecodeMasami Hiramatsu (12):
ia64: kprobes: Fix to pass correct trampoline address to the handler
kprobes: treewide: Replacearch_deref_entry_point()withdereference_symbol_descriptor()
kprobes: treewide: Removetrampoline_addressfromkretprobe_trampoline_handler()
kprobes: Addkretprobe_find_ret_addr()for searching return address
ARC: Addinstruction_pointer_set()API
ia64: Addinstruction_pointer_set()API
arm: kprobes: Make a space forregs->ARM_pcatkretprobe_trampoline
kprobes: Setup instruction pointer in__kretprobe_trampoline_handlerx86/kprobes: Push a fake return address atkretprobe_trampolinex86/unwind: Recover kretprobe trampoline entry
tracing: Show kretprobe unknown indicator only forkretprobe_trampolinex86/kprobes: Fixup return address in generic trampoline handlerarch/arc/include/asm/ptrace.h | 5 ++ arch/arc/kernel/kprobes.c | 2 - arch/arm/probes/kprobes/core.c | 5 +- arch/arm64/kernel/probes/kprobes.c | 3 - arch/csky/kernel/probes/kprobes.c | 2 - arch/ia64/include/asm/ptrace.h | 5 ++ arch/ia64/kernel/kprobes.c | 15 ++--- arch/mips/kernel/kprobes.c | 3 - arch/parisc/kernel/kprobes.c | 4 + arch/powerpc/kernel/kprobes.c | 13 ---- arch/riscv/kernel/probes/kprobes.c | 2 - arch/s390/kernel/kprobes.c | 2 - arch/sh/kernel/kprobes.c | 2 - arch/sparc/kernel/kprobes.c | 2 - arch/x86/include/asm/kprobes.h | 1 arch/x86/include/asm/unwind.h | 23 +++++++ arch/x86/include/asm/unwind_hints.h | 5 ++ arch/x86/kernel/kprobes/core.c | 53 +++++++++++++++-- arch/x86/kernel/unwind_frame.c | 4 + arch/x86/kernel/unwind_guess.c | 3 - arch/x86/kernel/unwind_orc.c | 19 +++++- include/linux/kprobes.h | 41 +++++++++++-- kernel/kprobes.c | 108 +++++++++++++++++++++++++---------- kernel/trace/trace_output.c | 17 +----- lib/error-inject.c | 3 + 25 files changed, 237 insertions(+), 105 deletions(-)--
Masami Hiramatsu (Linaro) <[email protected]>
Masami Hiramatsu
maintainerQuoted 79 lines · On Wed, 26 May 2021 10:39:57 -0700 Andrii Nakryiko <[email protected]>…
Quoted 69 lines · Masami Hiramatsu
Hello,
Here is the 6th version of the series to fix the stacktrace with kretprobe on x86.
The previous version is;
https://lore.kernel.org/bpf/161676170650.330141.6214727134265514123.stgit@devnote2/
This version is rebased on the latest tip tree and add some patches for improving stacktrace[13/13].
Changes from v5:
[02/13]:
- Usedereference_symbol_descriptor()instead ofdereference_function_descriptor()
[04/13]:
- ReplaceBUG_ON()withWARN_ON_ONCE()in__kretprobe_trampoline_handler().
[13/13]:
- Add a new patch to fix return address in earlier stage.With this series, unwinder can unwind stack correctly from ftrace as below;
# cd /sys/kernel/debug/tracing # echo > trace # echo 1 > options/sym-offset # echo r vfs_read >> kprobe_events # echo r full_proxy_read >> kprobe_events # echo traceoff:1 > events/kprobes/r_vfs_read_0/trigger # echo stacktrace:1 > events/kprobes/r_full_proxy_read_0/trigger # echo 1 > events/kprobes/enable # cat /sys/kernel/debug/kprobes/list ffffffff8133b740 r full_proxy_read+0x0 [FTRACE] ffffffff812560b0 r vfs_read+0x0 [FTRACE] # echo 0 > events/kprobes/enable # cat trace # tracer: nop # # entries-in-buffer/entries-written: 3/3 #P:8 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | <...>-134 [007] ...1 16.185877: r_full_proxy_read_0: (vfs_read+0x98/0x180 <- full_proxy_read) <...>-134 [007] ...1 16.185901: <stack trace> => kretprobe_trace_func+0x209/0x300 => kretprobe_dispatcher+0x4a/0x70 => __kretprobe_trampoline_handler+0xd4/0x170 => trampoline_handler+0x43/0x60 => kretprobe_trampoline+0x2a/0x50 => vfs_read+0x98/0x180 => ksys_read+0x5f/0xe0 => do_syscall_64+0x37/0x90 => entry_SYSCALL_64_after_hwframe+0x44/0xae <...>-134 [007] ...1 16.185902: r_vfs_read_0: (ksys_read+0x5f/0xe0 <- vfs_read)This shows the double return probes (
vfs_readandfull_proxy_read) on the stack correctly unwinded. (vfs_readwill return toksys_read+0x5fandfull_proxy_readwill return tovfs_read+0x98)This actually changes the kretprobe behavisor a bit, now the instraction pointer in the
pt_regspassed to kretprobe user handler is correctly set the real return address. So user handlers can get it viainstruction_pointer()API.Quoted 8 lines
You can also get this series from
git://git.kernel.org/pub/scm/linux/kernel/git/mhiramat/linux.gitkprobes/kretprobe-stackfix-v6Thank you,
---
Quoted 6 lines
Thanks for following up on this! I've applied this patch set on top of bpf-next and tested with my local BPF-based tool that uses stack traces in kretprobes heavily. It all works now and I'm getting meaningful and correctly looking stacktraces. Thanks a lot!
Tested-by Andrii Nakryik <[email protected]>
Thanks for testing! I got a minor warning issue on [13/13] from kernel test bot, which can be fixed by adding a prototype. So I will update it.
Thank you!
Signature · 1 line
Masami Hiramatsu <[email protected]>