Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

unwind_user/x86: Teach FP unwind about start of function

When userspace is interrupted at the start of a function, before we
get a chance to complete the frame, unwind will miss one caller.

X86 has a uprobe specific fixup for this, add bits to the generic
unwinder to support this.

Suggested-by: Jens Remus <jremus@linux.ibm.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20251024145156.GM4068168@noisy.programming.kicks-ass.net

+84 -49
-40
arch/x86/events/core.c
··· 2845 2845 return get_desc_base(desc); 2846 2846 } 2847 2847 2848 - #ifdef CONFIG_UPROBES 2849 - /* 2850 - * Heuristic-based check if uprobe is installed at the function entry. 2851 - * 2852 - * Under assumption of user code being compiled with frame pointers, 2853 - * `push %rbp/%ebp` is a good indicator that we indeed are. 2854 - * 2855 - * Similarly, `endbr64` (assuming 64-bit mode) is also a common pattern. 2856 - * If we get this wrong, captured stack trace might have one extra bogus 2857 - * entry, but the rest of stack trace will still be meaningful. 2858 - */ 2859 - static bool is_uprobe_at_func_entry(struct pt_regs *regs) 2860 - { 2861 - struct arch_uprobe *auprobe; 2862 - 2863 - if (!current->utask) 2864 - return false; 2865 - 2866 - auprobe = current->utask->auprobe; 2867 - if (!auprobe) 2868 - return false; 2869 - 2870 - /* push %rbp/%ebp */ 2871 - if (auprobe->insn[0] == 0x55) 2872 - return true; 2873 - 2874 - /* endbr64 (64-bit only) */ 2875 - if (user_64bit_mode(regs) && is_endbr((u32 *)auprobe->insn)) 2876 - return true; 2877 - 2878 - return false; 2879 - } 2880 - 2881 - #else 2882 - static bool is_uprobe_at_func_entry(struct pt_regs *regs) 2883 - { 2884 - return false; 2885 - } 2886 - #endif /* CONFIG_UPROBES */ 2887 - 2888 2848 #ifdef CONFIG_IA32_EMULATION 2889 2849 2890 2850 #include <linux/compat.h>
+12
arch/x86/include/asm/unwind_user.h
··· 3 3 #define _ASM_X86_UNWIND_USER_H 4 4 5 5 #include <asm/ptrace.h> 6 + #include <asm/uprobes.h> 6 7 7 8 #define ARCH_INIT_USER_FP_FRAME(ws) \ 8 9 .cfa_off = 2*(ws), \ 9 10 .ra_off = -1*(ws), \ 10 11 .fp_off = -2*(ws), \ 11 12 .use_fp = true, 13 + 14 + #define ARCH_INIT_USER_FP_ENTRY_FRAME(ws) \ 15 + .cfa_off = 1*(ws), \ 16 + .ra_off = -1*(ws), \ 17 + .fp_off = 0, \ 18 + .use_fp = false, 12 19 13 20 static inline int unwind_user_word_size(struct pt_regs *regs) 14 21 { ··· 27 20 return sizeof(int); 28 21 #endif 29 22 return sizeof(long); 23 + } 24 + 25 + static inline bool unwind_user_at_function_start(struct pt_regs *regs) 26 + { 27 + return is_uprobe_at_func_entry(regs); 30 28 } 31 29 32 30 #endif /* _ASM_X86_UNWIND_USER_H */
+9
arch/x86/include/asm/uprobes.h
··· 62 62 unsigned int saved_tf; 63 63 }; 64 64 65 + #ifdef CONFIG_UPROBES 66 + extern bool is_uprobe_at_func_entry(struct pt_regs *regs); 67 + #else 68 + static bool is_uprobe_at_func_entry(struct pt_regs *regs) 69 + { 70 + return false; 71 + } 72 + #endif /* CONFIG_UPROBES */ 73 + 65 74 #endif /* _ASM_UPROBES_H */
+32
arch/x86/kernel/uprobes.c
··· 1791 1791 else 1792 1792 return regs->sp <= ret->stack; 1793 1793 } 1794 + 1795 + /* 1796 + * Heuristic-based check if uprobe is installed at the function entry. 1797 + * 1798 + * Under assumption of user code being compiled with frame pointers, 1799 + * `push %rbp/%ebp` is a good indicator that we indeed are. 1800 + * 1801 + * Similarly, `endbr64` (assuming 64-bit mode) is also a common pattern. 1802 + * If we get this wrong, captured stack trace might have one extra bogus 1803 + * entry, but the rest of stack trace will still be meaningful. 1804 + */ 1805 + bool is_uprobe_at_func_entry(struct pt_regs *regs) 1806 + { 1807 + struct arch_uprobe *auprobe; 1808 + 1809 + if (!current->utask) 1810 + return false; 1811 + 1812 + auprobe = current->utask->auprobe; 1813 + if (!auprobe) 1814 + return false; 1815 + 1816 + /* push %rbp/%ebp */ 1817 + if (auprobe->insn[0] == 0x55) 1818 + return true; 1819 + 1820 + /* endbr64 (64-bit only) */ 1821 + if (user_64bit_mode(regs) && is_endbr((u32 *)auprobe->insn)) 1822 + return true; 1823 + 1824 + return false; 1825 + }
+1
include/linux/unwind_user_types.h
··· 39 39 unsigned int ws; 40 40 enum unwind_user_type current_type; 41 41 unsigned int available_types; 42 + bool topmost; 42 43 bool done; 43 44 }; 44 45
+30 -9
kernel/unwind/user.c
··· 26 26 return get_user(*word, addr); 27 27 } 28 28 29 - static int unwind_user_next_fp(struct unwind_user_state *state) 29 + static int unwind_user_next_common(struct unwind_user_state *state, 30 + const struct unwind_user_frame *frame) 30 31 { 31 - const struct unwind_user_frame frame = { 32 - ARCH_INIT_USER_FP_FRAME(state->ws) 33 - }; 34 32 unsigned long cfa, fp, ra; 35 33 36 - if (frame.use_fp) { 34 + if (frame->use_fp) { 37 35 if (state->fp < state->sp) 38 36 return -EINVAL; 39 37 cfa = state->fp; ··· 40 42 } 41 43 42 44 /* Get the Canonical Frame Address (CFA) */ 43 - cfa += frame.cfa_off; 45 + cfa += frame->cfa_off; 44 46 45 47 /* stack going in wrong direction? */ 46 48 if (cfa <= state->sp) ··· 51 53 return -EINVAL; 52 54 53 55 /* Find the Return Address (RA) */ 54 - if (get_user_word(&ra, cfa, frame.ra_off, state->ws)) 56 + if (get_user_word(&ra, cfa, frame->ra_off, state->ws)) 55 57 return -EINVAL; 56 58 57 - if (frame.fp_off && get_user_word(&fp, cfa, frame.fp_off, state->ws)) 59 + if (frame->fp_off && get_user_word(&fp, cfa, frame->fp_off, state->ws)) 58 60 return -EINVAL; 59 61 60 62 state->ip = ra; 61 63 state->sp = cfa; 62 - if (frame.fp_off) 64 + if (frame->fp_off) 63 65 state->fp = fp; 66 + state->topmost = false; 64 67 return 0; 68 + } 69 + 70 + static int unwind_user_next_fp(struct unwind_user_state *state) 71 + { 72 + #ifdef CONFIG_HAVE_UNWIND_USER_FP 73 + struct pt_regs *regs = task_pt_regs(current); 74 + 75 + if (state->topmost && unwind_user_at_function_start(regs)) { 76 + const struct unwind_user_frame fp_entry_frame = { 77 + ARCH_INIT_USER_FP_ENTRY_FRAME(state->ws) 78 + }; 79 + return unwind_user_next_common(state, &fp_entry_frame); 80 + } 81 + 82 + const struct unwind_user_frame fp_frame = { 83 + ARCH_INIT_USER_FP_FRAME(state->ws) 84 + }; 85 + return unwind_user_next_common(state, &fp_frame); 86 + #else 87 + return -EINVAL; 88 + #endif 65 89 } 66 90 67 91 static int unwind_user_next(struct unwind_user_state *state) ··· 138 118 state->done = true; 139 119 return -EINVAL; 140 120 } 121 + state->topmost = true; 141 122 142 123 return 0; 143 124 }