summaryrefslogtreecommitdiff
path: root/kernel/trace
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-23 12:05:34 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-23 12:05:34 -1000
commitb8a7a86e48927472cc84463c1f038783bd52093f (patch)
tree872c24e7377dd1e7edb93024a96f641670105f27 /kernel/trace
parent9ec665e5e7a955f0f5637e420ac5075bef475e33 (diff)
parente1f339ff3b83857b93baf8b88186275a413d445e (diff)
Merge tag 'trace-ftrace-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace
Pull ftrace updates from Steven Rostedt: - Have the stacktrace option work with func-args The func_stack_trace option has it's own ftrace handler and so does the func-args option. When they are both set, neither would run. The function tracer itself would error out. Allow the stacktrace and function args to work together. - Avoid BTF lookup in atomic context The ftrace_dump() can call function graph tracing output from an atomic context (like an NMI watchdog going off). The BTF lookup is not safe to call from atomic context. Have btf_find_func_proto() return if in atomic context. - Build trace_btf.c when CONFIG_DEBUG_INFO_BTF is enabled New code is depending on trace_btf.c but that only gets built when BPF_SYSCALL is also enabled. As the new code doesn't depend on that, have trace_btf.c get built with just DEBUG_INFO_BTF set. - Have function graph return values have BTF processing Have the return values printed by the function graph tracer use BTF to print the values in better format. For example, if it's a char, short, signed or unsigned, print according to the value defined by BTF. - Add a "max" field to the function profiler stats The function profiler shows all the functions run in the kernel, the number of times they are called, and the total time they took up. Also add a "max" field which holds the longest time a function ran for. - Fix memory leak in module with all skipped functions On module load, the ftrace entries are recorded to keep track of what functions can be traced or not. Some functions may be skipped for various reasons (some defined by the architecture). If all functions in the module are skipped, its data page is not freed and can leak. Catch this case and free the empty page. - Allow architectures to reject functions from being traced Allow architectures to skip a function address by returning zero from ftrace_call_adjust(). - Initialize modname and offset in print_rec() The variables modname and offset are initialized by kallsyms_lookup(). If that fails, the values are not set, but the code continues to print other information. The modname and offset are used later, but if the kallsyms_lookup() failed, they will not be set. If a module is being loaded and unloaded at the same time as reading the available_filter_functions file, it can cause a fault due to the lookup failing and modname and offset being used uninitialized. * tag 'trace-ftrace-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ftrace: Initialize modname and offset in print_rec() ftrace: Let ftrace_call_adjust() reject a patch site ftrace: Do not leak a module's empty page group ftrace: Add max field to function profiler stats tracing: Update funcgraph-retval documentation fgraph: Enhance funcgraph-retval with BTF-based type-aware output ftrace: Build trace_btf.c when CONFIG_DEBUG_INFO_BTF is enabled tracing: Avoid BTF lookup in atomic context ftrace: Have stacktrace option work with func-args
Diffstat (limited to 'kernel/trace')
-rw-r--r--kernel/trace/Kconfig2
-rw-r--r--kernel/trace/Makefile2
-rw-r--r--kernel/trace/ftrace.c32
-rw-r--r--kernel/trace/trace_btf.c86
-rw-r--r--kernel/trace/trace_btf.h18
-rw-r--r--kernel/trace/trace_functions.c5
-rw-r--r--kernel/trace/trace_functions_graph.c56
7 files changed, 181 insertions, 20 deletions
diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig
index 0ab5916575a9..d8faeb659cfc 100644
--- a/kernel/trace/Kconfig
+++ b/kernel/trace/Kconfig
@@ -770,7 +770,7 @@ config FPROBE_EVENTS
config PROBE_EVENTS_BTF_ARGS
depends on HAVE_FUNCTION_ARG_ACCESS_API
depends on FPROBE_EVENTS || KPROBE_EVENTS
- depends on DEBUG_INFO_BTF && BPF_SYSCALL
+ depends on DEBUG_INFO_BTF
bool "Support BTF function arguments for probe events"
default y
help
diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile
index 3860bc53a4be..1f925cd1b9f8 100644
--- a/kernel/trace/Makefile
+++ b/kernel/trace/Makefile
@@ -119,7 +119,7 @@ obj-$(CONFIG_KGDB_KDB) += trace_kdb.o
endif
obj-$(CONFIG_DYNAMIC_EVENTS) += trace_dynevent.o
obj-$(CONFIG_PROBE_EVENTS) += trace_probe.o
-obj-$(CONFIG_PROBE_EVENTS_BTF_ARGS) += trace_btf.o
+obj-$(CONFIG_DEBUG_INFO_BTF) += trace_btf.o
obj-$(CONFIG_UPROBE_EVENTS) += trace_uprobe.o
obj-$(CONFIG_BOOTTIME_TRACING) += trace_boot.o
obj-$(CONFIG_FTRACE_RECORD_RECURSION) += trace_recursion_record.o
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 673a54fdf392..c7d74b33d36d 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -428,6 +428,7 @@ struct ftrace_profile {
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
unsigned long long time;
unsigned long long time_squared;
+ unsigned long long time_max;
#endif
};
@@ -530,9 +531,9 @@ static int function_stat_headers(struct seq_file *m)
{
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
seq_puts(m, " Function "
- "Hit Time Avg s^2\n"
+ "Hit Time Avg s^2 max\n"
" -------- "
- "--- ---- --- ---\n");
+ "--- ---- --- --- ---\n");
#else
seq_puts(m, " Function Hit\n"
" -------- ---\n");
@@ -613,6 +614,8 @@ static int function_stat_show(struct seq_file *m, void *v)
trace_print_graph_duration(avg, &s);
trace_seq_puts(&s, " ");
trace_print_graph_duration(stddev, &s);
+ trace_seq_puts(&s, " ");
+ trace_print_graph_duration(rec->time_max, &s);
trace_print_seq(m, &s);
#endif
seq_putc(m, '\n');
@@ -920,6 +923,8 @@ static void profile_graph_return(struct ftrace_graph_ret *trace,
if (rec) {
rec->time += calltime;
rec->time_squared += calltime * calltime;
+ if (calltime > rec->time_max)
+ rec->time_max = calltime;
}
}
@@ -4426,9 +4431,9 @@ subsys_initcall(ftrace_check_for_weak_functions);
static int print_rec(struct seq_file *m, unsigned long ip)
{
- unsigned long offset;
+ unsigned long offset = 0;
char str[KSYM_SYMBOL_LEN];
- char *modname;
+ char *modname = NULL;
const char *ret;
ret = kallsyms_lookup(ip, NULL, &offset, &modname, str);
@@ -7702,6 +7707,11 @@ static int ftrace_process_locs(struct module *mod,
}
addr = ftrace_call_adjust(addr);
+ /* The architecture may return 0 to reject a patch site */
+ if (!addr) {
+ skipped++;
+ continue;
+ }
end_offset = (pg->index+1) * sizeof(pg->records[0]);
if (end_offset > PAGE_SIZE << pg->order) {
@@ -7715,6 +7725,20 @@ static int ftrace_process_locs(struct module *mod,
rec->ip = addr;
}
+ /*
+ * ftrace_release_mod() finds a module's page groups by their first
+ * record. If every entry was skipped there is none, so unlink the
+ * new page groups and free them now.
+ */
+ if (mod && !start_pg->index) {
+ ftrace_pages->next = NULL;
+ mutex_unlock(&ftrace_lock);
+ /* Need to synchronize with ftrace_location_range() */
+ synchronize_rcu();
+ ftrace_free_pages(start_pg);
+ return 0;
+ }
+
if (pg->next) {
pg_unuse = pg->next;
pg->next = NULL;
diff --git a/kernel/trace/trace_btf.c b/kernel/trace/trace_btf.c
index ee7a04886bf6..adefd3969b1b 100644
--- a/kernel/trace/trace_btf.c
+++ b/kernel/trace/trace_btf.c
@@ -2,6 +2,7 @@
#include <linux/btf.h>
#include <linux/kernel.h>
#include <linux/slab.h>
+#include <linux/kallsyms.h>
#include "trace_btf.h"
@@ -15,6 +16,9 @@ const struct btf_type *btf_find_func_proto(const char *func_name, struct btf **b
const struct btf_type *t;
s32 id;
+ if (unlikely(in_atomic() || irqs_disabled()))
+ return NULL;
+
id = bpf_find_btf_id(func_name, BTF_KIND_FUNC, btf_p);
if (id < 0)
return NULL;
@@ -123,3 +127,85 @@ out:
return member;
}
+void btf_trim_retval(unsigned long func, unsigned long *retval, bool *print_retval,
+ int *fmt, bool hex)
+{
+ const struct btf_type *t;
+ char name[KSYM_NAME_LEN];
+ struct btf *btf;
+ u32 v, msb;
+ bool signed_type;
+ int kind;
+
+ if (lookup_symbol_name(func, name))
+ return;
+
+ t = btf_find_func_proto(name, &btf);
+ if (IS_ERR_OR_NULL(t))
+ return;
+
+ t = btf_type_skip_modifiers(btf, t->type, NULL);
+ kind = t ? BTF_INFO_KIND(t->info) : BTF_KIND_UNKN;
+ switch (kind) {
+ case BTF_KIND_UNKN:
+ *print_retval = false;
+ break;
+ case BTF_KIND_STRUCT:
+ case BTF_KIND_UNION:
+ case BTF_KIND_ENUM:
+ case BTF_KIND_ENUM64:
+ if (kind == BTF_KIND_STRUCT || kind == BTF_KIND_UNION) {
+ *fmt = RETVAL_FMT_HEX;
+ signed_type = false;
+ } else {
+ *fmt = RETVAL_FMT_DEC;
+ signed_type = btf_type_kflag(t);
+ if (!signed_type)
+ *fmt |= RETVAL_FMT_UNSIGNED;
+ }
+
+ if (t->size > sizeof(unsigned long)) {
+ *fmt |= RETVAL_FMT_TRUNC;
+ msb = BITS_PER_LONG - 1;
+ } else {
+ msb = min_t(u32, BITS_PER_BYTE * t->size - 1,
+ BITS_PER_LONG - 1);
+ *retval &= GENMASK(msb, 0);
+ }
+ if (signed_type && t->size && !hex)
+ *retval = sign_extend64(*retval, msb);
+ break;
+ case BTF_KIND_INT:
+ v = *(u32 *)(t + 1);
+ signed_type = false;
+ if (BTF_INT_ENCODING(v) == BTF_INT_BOOL) {
+ *fmt = RETVAL_FMT_BOOL;
+ msb = 0;
+ } else {
+ signed_type = BTF_INT_ENCODING(v) & BTF_INT_SIGNED;
+ if (signed_type)
+ *fmt = RETVAL_FMT_DEC;
+ else
+ *fmt = RETVAL_FMT_HEX;
+
+ if (t->size > sizeof(unsigned long)) {
+ *fmt |= RETVAL_FMT_TRUNC;
+ msb = BITS_PER_LONG - 1;
+ } else {
+ msb = min_t(u32, BTF_INT_BITS(v) - 1,
+ BITS_PER_LONG - 1);
+ }
+ }
+ *retval &= GENMASK(msb, 0);
+ if (signed_type && !hex)
+ *retval = sign_extend64(*retval, msb);
+ break;
+ default:
+ *fmt = RETVAL_FMT_HEX;
+ break;
+ }
+
+ if (*print_retval)
+ *fmt |= RETVAL_FMT_BTF;
+ btf_put(btf);
+}
diff --git a/kernel/trace/trace_btf.h b/kernel/trace/trace_btf.h
index 4bd26bceae23..20cbc3d4ae0e 100644
--- a/kernel/trace/trace_btf.h
+++ b/kernel/trace/trace_btf.h
@@ -1,6 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/btf.h>
+enum {
+ RETVAL_FMT_HEX = BIT(0),
+ RETVAL_FMT_DEC = BIT(1),
+ RETVAL_FMT_BOOL = BIT(2),
+ RETVAL_FMT_TRUNC = BIT(3),
+ RETVAL_FMT_BTF = BIT(4),
+ RETVAL_FMT_UNSIGNED = BIT(5),
+};
+
const struct btf_type *btf_find_func_proto(const char *func_name,
struct btf **btf_p);
const struct btf_param *btf_get_func_param(const struct btf_type *func_proto,
@@ -10,3 +19,12 @@ const struct btf_member *btf_find_struct_member(struct btf *btf,
const char *member_name,
u32 *anon_offset,
const struct btf_type **member_type);
+#ifdef CONFIG_DEBUG_INFO_BTF
+void btf_trim_retval(unsigned long func, unsigned long *retval, bool *print_retval,
+ int *fmt, bool hex);
+#else
+static inline void btf_trim_retval(unsigned long func, unsigned long *retval,
+ bool *print_retval, int *fmt, bool hex)
+{
+}
+#endif
diff --git a/kernel/trace/trace_functions.c b/kernel/trace/trace_functions.c
index c879d43a5fbb..acaba990aaaa 100644
--- a/kernel/trace/trace_functions.c
+++ b/kernel/trace/trace_functions.c
@@ -121,6 +121,7 @@ static ftrace_func_t select_trace_function(u32 flags_val)
case TRACE_FUNC_OPT_ARGS:
return function_args_trace_call;
case TRACE_FUNC_OPT_STACK:
+ case TRACE_FUNC_OPT_STACK | TRACE_FUNC_OPT_ARGS:
return function_stack_trace_call;
case TRACE_FUNC_OPT_NO_REPEATS:
return function_no_repeats_trace_call;
@@ -295,7 +296,9 @@ function_stack_trace_call(unsigned long ip, unsigned long parent_ip,
if (likely(disabled == 1)) {
trace_ctx = tracing_gen_ctx_flags(flags);
- trace_function(tr, ip, parent_ip, trace_ctx, NULL);
+ if (!(tr->current_trace_flags->val & TRACE_FUNC_OPT_ARGS))
+ fregs = NULL;
+ trace_function(tr, ip, parent_ip, trace_ctx, fregs);
#ifdef CONFIG_UNWINDER_FRAME_POINTER
if (ftrace_pids_enabled(op))
skip++;
diff --git a/kernel/trace/trace_functions_graph.c b/kernel/trace/trace_functions_graph.c
index c5befd0c5b9a..78a608315dd3 100644
--- a/kernel/trace/trace_functions_graph.c
+++ b/kernel/trace/trace_functions_graph.c
@@ -15,6 +15,7 @@
#include "trace.h"
#include "trace_output.h"
+#include "trace_btf.h"
/* When set, irq functions might be ignored */
static int ftrace_graph_skip_irqs;
@@ -876,9 +877,10 @@ static void print_graph_retval(struct trace_seq *s, struct ftrace_graph_ent_entr
{
unsigned long err_code = 0;
unsigned long retval = 0;
+ bool hex_format;
bool print_retaddr = false;
bool print_retval = false;
- bool hex_format = !!(opt_flags & TRACE_GRAPH_PRINT_RETVAL_HEX);
+ int retval_fmt = 0;
#ifdef CONFIG_FUNCTION_GRAPH_RETVAL
retval = graph_ret->retval;
@@ -889,17 +891,38 @@ static void print_graph_retval(struct trace_seq *s, struct ftrace_graph_ent_entr
print_retaddr = !!(opt_flags & TRACE_GRAPH_PRINT_RETADDR);
#endif
- if (print_retval && retval && !hex_format) {
- /* Check if the return value matches the negative format */
- if (IS_ENABLED(CONFIG_64BIT) && (retval & BIT(31)) &&
- (((u64)retval) >> 32) == 0) {
- err_code = sign_extend64(retval, 31);
- } else {
- err_code = retval;
+ if (print_retval) {
+ int fmt = RETVAL_FMT_HEX;
+
+ hex_format = !!(opt_flags & TRACE_GRAPH_PRINT_RETVAL_HEX);
+ btf_trim_retval((unsigned long)func, &retval, &print_retval, &fmt,
+ hex_format);
+ if (print_retval) {
+ if (hex_format)
+ retval_fmt = RETVAL_FMT_HEX;
+
+ if (retval && retval_fmt != RETVAL_FMT_HEX &&
+ !(fmt & RETVAL_FMT_BTF)) {
+ /* Check if the return value matches the negative format */
+ if (IS_ENABLED(CONFIG_64BIT) && (retval & BIT(31)) &&
+ (((u64)retval) >> 32) == 0) {
+ err_code = sign_extend64(retval, 31);
+ } else {
+ err_code = retval;
+ }
+
+ if (!IS_ERR_VALUE(err_code))
+ err_code = 0;
+ }
+
+ if (retval_fmt == RETVAL_FMT_HEX) {
+ retval_fmt |= (fmt & RETVAL_FMT_TRUNC);
+ } else {
+ if (err_code && fmt & RETVAL_FMT_HEX)
+ fmt = (fmt & ~RETVAL_FMT_HEX) | RETVAL_FMT_DEC;
+ retval_fmt = fmt;
+ }
}
-
- if (!IS_ERR_VALUE(err_code))
- err_code = 0;
}
if (entry) {
@@ -926,10 +949,17 @@ static void print_graph_retval(struct trace_seq *s, struct ftrace_graph_ent_entr
trace_flags, false);
if (print_retval) {
- if (hex_format || (err_code == 0))
+ if (retval_fmt & RETVAL_FMT_HEX)
trace_seq_printf(s, " ret=0x%lx", retval);
+ else if (retval_fmt & RETVAL_FMT_BOOL)
+ trace_seq_printf(s, " ret=%s", retval ? "true" : "false");
+ else if (retval_fmt & RETVAL_FMT_UNSIGNED)
+ trace_seq_printf(s, " ret=%lu", retval);
else
- trace_seq_printf(s, " ret=%ld", err_code);
+ trace_seq_printf(s, " ret=%ld", err_code ?: retval);
+
+ if (retval_fmt & RETVAL_FMT_TRUNC)
+ trace_seq_printf(s, "(trunc)");
}
if (!entry || print_retval || print_retaddr)