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

ftrace: Introduce FTRACE_OPS_FL_JMP

For now, the "nop" will be replaced with a "call" instruction when a
function is hooked by the ftrace. However, sometimes the "call" can break
the RSB and introduce extra overhead. Therefore, introduce the flag
FTRACE_OPS_FL_JMP, which indicate that the ftrace_ops should be called
with a "jmp" instead of "call". For now, it is only used by the direct
call case.

When a direct ftrace_ops is marked with FTRACE_OPS_FL_JMP, the last bit of
the ops->direct_call will be set to 1. Therefore, we can tell if we should
use "jmp" for the callback in ftrace_call_replace().

Signed-off-by: Menglong Dong <dongml2@chinatelecom.cn>
Acked-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Link: https://lore.kernel.org/r/20251118123639.688444-2-dongml2@chinatelecom.cn
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Menglong Dong and committed by
Alexei Starovoitov
25e4e356 fad80400

+61 -1
+33
include/linux/ftrace.h
··· 359 359 FTRACE_OPS_FL_DIRECT = BIT(17), 360 360 FTRACE_OPS_FL_SUBOP = BIT(18), 361 361 FTRACE_OPS_FL_GRAPH = BIT(19), 362 + FTRACE_OPS_FL_JMP = BIT(20), 362 363 }; 363 364 364 365 #ifndef CONFIG_DYNAMIC_FTRACE_WITH_ARGS ··· 577 576 static inline void arch_ftrace_set_direct_caller(struct ftrace_regs *fregs, 578 577 unsigned long addr) { } 579 578 #endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */ 579 + 580 + #ifdef CONFIG_DYNAMIC_FTRACE_WITH_JMP 581 + static inline bool ftrace_is_jmp(unsigned long addr) 582 + { 583 + return addr & 1; 584 + } 585 + 586 + static inline unsigned long ftrace_jmp_set(unsigned long addr) 587 + { 588 + return addr | 1UL; 589 + } 590 + 591 + static inline unsigned long ftrace_jmp_get(unsigned long addr) 592 + { 593 + return addr & ~1UL; 594 + } 595 + #else 596 + static inline bool ftrace_is_jmp(unsigned long addr) 597 + { 598 + return false; 599 + } 600 + 601 + static inline unsigned long ftrace_jmp_set(unsigned long addr) 602 + { 603 + return addr; 604 + } 605 + 606 + static inline unsigned long ftrace_jmp_get(unsigned long addr) 607 + { 608 + return addr; 609 + } 610 + #endif /* CONFIG_DYNAMIC_FTRACE_WITH_JMP */ 580 611 581 612 #ifdef CONFIG_STACK_TRACER 582 613
+12
kernel/trace/Kconfig
··· 80 80 If the architecture generates __patchable_function_entries sections 81 81 but does not want them included in the ftrace locations. 82 82 83 + config HAVE_DYNAMIC_FTRACE_WITH_JMP 84 + bool 85 + help 86 + If the architecture supports to replace the __fentry__ with a 87 + "jmp" instruction. 88 + 83 89 config HAVE_SYSCALL_TRACEPOINTS 84 90 bool 85 91 help ··· 335 329 def_bool y 336 330 depends on DYNAMIC_FTRACE 337 331 depends on HAVE_DYNAMIC_FTRACE_WITH_ARGS 332 + 333 + config DYNAMIC_FTRACE_WITH_JMP 334 + def_bool y 335 + depends on DYNAMIC_FTRACE 336 + depends on DYNAMIC_FTRACE_WITH_DIRECT_CALLS 337 + depends on HAVE_DYNAMIC_FTRACE_WITH_JMP 338 338 339 339 config FPROBE 340 340 bool "Kernel Function Probe (fprobe)"
+16 -1
kernel/trace/ftrace.c
··· 5951 5951 for (i = 0; i < size; i++) { 5952 5952 hlist_for_each_entry(entry, &hash->buckets[i], hlist) { 5953 5953 del = __ftrace_lookup_ip(direct_functions, entry->ip); 5954 - if (del && del->direct == addr) { 5954 + if (del && ftrace_jmp_get(del->direct) == 5955 + ftrace_jmp_get(addr)) { 5955 5956 remove_hash_entry(direct_functions, del); 5956 5957 kfree(del); 5957 5958 } ··· 6017 6016 if (ftrace_hash_empty(hash)) 6018 6017 return -EINVAL; 6019 6018 6019 + /* This is a "raw" address, and this should never happen. */ 6020 + if (WARN_ON_ONCE(ftrace_is_jmp(addr))) 6021 + return -EINVAL; 6022 + 6020 6023 mutex_lock(&direct_mutex); 6024 + 6025 + if (ops->flags & FTRACE_OPS_FL_JMP) 6026 + addr = ftrace_jmp_set(addr); 6021 6027 6022 6028 /* Make sure requested entries are not already registered.. */ 6023 6029 size = 1 << hash->size_bits; ··· 6145 6137 int err; 6146 6138 6147 6139 lockdep_assert_held_once(&direct_mutex); 6140 + 6141 + /* This is a "raw" address, and this should never happen. */ 6142 + if (WARN_ON_ONCE(ftrace_is_jmp(addr))) 6143 + return -EINVAL; 6144 + 6145 + if (ops->flags & FTRACE_OPS_FL_JMP) 6146 + addr = ftrace_jmp_set(addr); 6148 6147 6149 6148 /* Enable the tmp_ops to have the same functions as the direct ops */ 6150 6149 ftrace_ops_init(&tmp_ops);