diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 14:06:14 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 14:06:14 -0700 |
| commit | 081e5bf2a9d941da60cd70c97c5a704b29e47f7f (patch) | |
| tree | b3c0df90abf9b3e64ec54358a778d46b20a95568 | |
| parent | 00d66b29a66ce18e417a8436076c629d03186a27 (diff) | |
| parent | ae70b04ab9c7f6162a8c0fdd18a62a945c133142 (diff) | |
Merge tag 'trace-v7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace
Pull tracing updates from Steven Rostedt:
- Expose btf_ids to trace events
In order to allow BPF programs to attach to system call trace events
(which are actually pseudo trace events built on top of raw_syscall
events), expose the BTF ID of the events. This will allow BPF
programs better precision in attaching to events.
- Use "u64" to assign to hist_field->type
Instead of using kstrdup("u64", GFP_KERNEL) to assign the
hist_field->type, just point it to "u64" instead. The
hist_field->type is freed via kfree_const().
- Replace kmalloc()/strcpy() with kstrdup() for trace_printk
Instead of having two calls to copy the module format string, just
use kstrdup().
- Use __free() in trace event histograms and triggres where possible
- Use seq_buf in trace event code instead of strcat()
Instead of calculating the size of the buffer to use and filling it
with strcat(), use the seq_buf infrastructure that takes care of
making sure not to overflow the string size.
- Reject invalid preemptirq_delay_test CPU affinity
The preempt_delay_test module can take an invalid CPU affinity mask
and create confusing output. Simply have the module reject invalid
affinity masks.
- Prevent division by zero in ftrace_ops sample module code
If the ftrace_ops sample module code receives the module parameter
nr_function_calls set to zero, it can cause a division by zero error.
- Warn when an event dereferences a parameter in TP_printk()
On boot up and module load, the trace event TP_printk() is scanned
for possible bugs. As the TP_printk() code is executed when the user
reads the "trace" file and processes the data written when the
trace_event executed, the data it reads can be literally days old.
The scan currently checks for dereferencing printk formats like
"%pI6". But it does not check if the parameters themselves have a
dereference like:
TP_printk("offset %08x: value %08x",
(u32)(__entry->addr - __entry->edma->membase), __entry->value)
__entry represents the pointer to the event on the ring buffer. The
__entry->edma->membase is dereferencing a pointer on the ring buffer
to find membase, but the __entry->edma may no longer be a valid
pointer.
Warn on this case too.
- Replace some strcpy() with strscpy()
- Clean up mmiotrace events to use assign_type() macro
The assign_type() macro makes sure the event type is indeed the type
that is being parsed. The mmiotrace trace was written before that
macro was created so it just simply typecasted the pointer.
Replace the typecasting with the macro.
- Have the ENUM processing to numbers only process what is added
The code that converts ENUMs to their numbers in the trace events
scanned all events to do the processing. This was true when a module
was loaded too. That is, instead of processing just the events for
the module, it processed *all* events. Even the builtin ones that
were processed at boot up.
Add a check for the event->module matching mod if it is a module
before processing it.
* tag 'trace-v7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (21 commits)
tracing: Have trace_event_update_all() only handle module that is loading
tracing: Cleanup event_enable_trigger_parse() by using __free()
tracing: Report every TP_printk double dereference
tracing/mmiotrace: Use trace_assign_type() in mmio_print_mark()
tracing: Make per-template BTF id lists file-local
tracing: Use seq_buf for string concatenation
tracing: Use strscpy() instead of strcpy() in trace_sched_switch
tracing: Warn when an event dereferences a pointer in TP_printk()
samples/ftrace: Prevent division by zero when nr_function_calls is zero
tracing: Reject invalid preemptirq_delay_test CPU affinity
fgraph: Use trace_seq_putc() in print_graph_return()
tracing/user_events: Replace a seq_printf() call by seq_puts() in user_seq_show()
tracing/user_events: Use seq_putc() in two functions
tracing: Bound histogram expression strings with seq_buf
tracing: Return ERR_PTR() from expr_str()
tracing: Use __free() for expr_str() buffer
kernel/trace/trace_printk: Use kstrdup() instead of kmalloc() and strcpy()
tracing: Point constant hist field type to string literal
selftests/bpf: Add test for tracepoint btf_ids tracefs file
tracing: Expose tracepoint BTF ids via tracefs
...
| -rw-r--r-- | include/linux/btf.h | 2 | ||||
| -rw-r--r-- | include/linux/trace_events.h | 9 | ||||
| -rw-r--r-- | include/trace/trace_events.h | 23 | ||||
| -rw-r--r-- | kernel/bpf/btf.c | 4 | ||||
| -rw-r--r-- | kernel/trace/preemptirq_delay_test.c | 9 | ||||
| -rw-r--r-- | kernel/trace/trace.c | 2 | ||||
| -rw-r--r-- | kernel/trace/trace.h | 4 | ||||
| -rw-r--r-- | kernel/trace/trace_events.c | 139 | ||||
| -rw-r--r-- | kernel/trace/trace_events_hist.c | 96 | ||||
| -rw-r--r-- | kernel/trace/trace_events_trigger.c | 16 | ||||
| -rw-r--r-- | kernel/trace/trace_events_user.c | 10 | ||||
| -rw-r--r-- | kernel/trace/trace_functions_graph.c | 2 | ||||
| -rw-r--r-- | kernel/trace/trace_mmiotrace.c | 7 | ||||
| -rw-r--r-- | kernel/trace/trace_printk.c | 3 | ||||
| -rw-r--r-- | kernel/trace/trace_sched_switch.c | 6 | ||||
| -rw-r--r-- | kernel/trace/trace_syscalls.c | 18 | ||||
| -rw-r--r-- | samples/ftrace/ftrace-ops.c | 2 | ||||
| -rw-r--r-- | tools/testing/selftests/bpf/prog_tests/tp_btf_ids.c | 132 |
18 files changed, 408 insertions, 76 deletions
diff --git a/include/linux/btf.h b/include/linux/btf.h index c09b7994de4e..8f8d3f7a8929 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -235,6 +235,8 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec); bool btf_type_is_void(const struct btf_type *t); s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind); s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p); +struct btf *btf_get_module_btf(const struct module *module); +__u32 btf_relocate_id(const struct btf *btf, __u32 id); const struct btf_type *btf_type_skip_modifiers(const struct btf *btf, u32 id, u32 *res_id); const struct btf_type *btf_type_resolve_ptr(const struct btf *btf, diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 308c76b57d13..5cbd09c8be8d 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -298,6 +298,15 @@ struct trace_event_class { struct list_head *(*get_fields)(struct trace_event_call *); struct list_head fields; int (*raw_init)(struct trace_event_call *); +#ifdef CONFIG_BPF_EVENTS + /* + * Per-template BTF ids set by DECLARE_EVENT_CLASS via BTF_ID() and + * patched by resolve_btfids at link time. NULL for handcrafted classes. + * [0] FUNC __bpf_trace_<template> + * [1] STRUCT trace_event_raw_<template> + */ + const u32 *btf_ids; +#endif }; extern int trace_event_reg(struct trace_event_call *event, diff --git a/include/trace/trace_events.h b/include/trace/trace_events.h index fbc07d353be6..93011f800d0f 100644 --- a/include/trace/trace_events.h +++ b/include/trace/trace_events.h @@ -19,6 +19,7 @@ */ #include <linux/trace_events.h> +#include <linux/btf_ids.h> #ifndef TRACE_SYSTEM_VAR #define TRACE_SYSTEM_VAR TRACE_SYSTEM @@ -397,6 +398,26 @@ static inline notrace int trace_event_get_offsets_##call( \ #define _TRACE_PERF_INIT(call) #endif /* CONFIG_PERF_EVENTS */ +#if defined(CONFIG_BPF_EVENTS) && defined(CONFIG_DEBUG_INFO_BTF) +/* + * Per-template BTF id list, populated at link time by resolve_btfids: + * [0] FUNC __bpf_trace_<call> (the BPF dispatcher) + * [1] STRUCT trace_event_raw_<call> (the ring-buffer record) + * Exposed via the events/<sys>/<name>/btf_ids tracefs file. + */ +#define _TRACE_BTF_IDS_DECLARE(call) \ + BTF_ID_LIST(__bpf_trace_btf_ids_##call) \ + BTF_ID(func, __bpf_trace_##call) \ + BTF_ID(struct, trace_event_raw_##call) + +#define _TRACE_BTF_IDS_INIT(call) \ + .btf_ids = __bpf_trace_btf_ids_##call, + +#else +#define _TRACE_BTF_IDS_DECLARE(call) +#define _TRACE_BTF_IDS_INIT(call) +#endif /* CONFIG_BPF_EVENTS && CONFIG_DEBUG_INFO_BTF */ + #include "stages/stage6_event_callback.h" @@ -474,6 +495,7 @@ static inline void ftrace_test_probe_##call(void) \ #undef DECLARE_EVENT_CLASS #define DECLARE_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ _TRACE_PERF_PROTO(call, PARAMS(proto)); \ +_TRACE_BTF_IDS_DECLARE(call) \ static char print_fmt_##call[] = print; \ static struct trace_event_class __used __refdata event_class_##call = { \ .system = TRACE_SYSTEM_STRING, \ @@ -483,6 +505,7 @@ static struct trace_event_class __used __refdata event_class_##call = { \ .probe = trace_event_raw_event_##call, \ .reg = trace_event_reg, \ _TRACE_PERF_INIT(call) \ + _TRACE_BTF_IDS_INIT(call) \ }; #undef DECLARE_EVENT_SYSCALL_CLASS diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 7e25051dc1ea..f56437e626c9 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -6467,7 +6467,7 @@ err_out: * split BTF ids will need to be mapped to actual base/split ids for * BTF now that it has been relocated. */ -static __u32 btf_relocate_id(const struct btf *btf, __u32 id) +__u32 btf_relocate_id(const struct btf *btf, __u32 id) { if (!btf->base_btf || !btf->base_id_map) return id; @@ -8623,7 +8623,7 @@ struct module *btf_try_get_module(const struct btf *btf) /* Returns struct btf corresponding to the struct module. * This function can return NULL or ERR_PTR. */ -static struct btf *btf_get_module_btf(const struct module *module) +struct btf *btf_get_module_btf(const struct module *module) { #ifdef CONFIG_DEBUG_INFO_BTF_MODULES struct btf_module *btf_mod, *tmp; diff --git a/kernel/trace/preemptirq_delay_test.c b/kernel/trace/preemptirq_delay_test.c index acb0c971a408..69e5238737ed 100644 --- a/kernel/trace/preemptirq_delay_test.c +++ b/kernel/trace/preemptirq_delay_test.c @@ -6,6 +6,7 @@ */ #include <linux/trace_clock.h> +#include <linux/cpumask.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/irq.h> @@ -123,6 +124,13 @@ static int preemptirq_delay_run(void *data) return -ENOMEM; if (cpu_affinity > -1) { + unsigned int cpu = cpu_affinity; + + if (cpu >= nr_cpu_ids || !cpu_possible(cpu)) { + pr_err("cpu_affinity:%d, invalid CPU\n", cpu_affinity); + goto out; + } + cpumask_clear(cpu_mask); cpumask_set_cpu(cpu_affinity, cpu_mask); if (set_cpus_allowed_ptr(current, cpu_mask)) @@ -132,6 +140,7 @@ static int preemptirq_delay_run(void *data) for (i = 0; i < s; i++) (testfuncs[i])(i); +out: complete(&done); set_current_state(TASK_INTERRUPTIBLE); diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 395238b2b715..89dc1c0ebb90 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4669,7 +4669,7 @@ trace_event_update_with_eval_map(struct module *mod, map = start; - trace_event_update_all(map, len); + trace_event_update_all(map, len, mod); if (len <= 0) return; diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index bf77331f56a4..74a7a50d1e78 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -2285,13 +2285,13 @@ static inline const char *get_syscall_name(int syscall) #ifdef CONFIG_EVENT_TRACING void trace_event_init(void); -void trace_event_update_all(struct trace_eval_map **map, int len); +void trace_event_update_all(struct trace_eval_map **map, int len, struct module *mod); /* Used from boot time tracer */ extern int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set); extern int trigger_process_regex(struct trace_event_file *file, char *buff); #else static inline void __init trace_event_init(void) { } -static inline void trace_event_update_all(struct trace_eval_map **map, int len) { } +static inline void trace_event_update_all(struct trace_eval_map **map, int len, struct module *mod) { } #endif #ifdef CONFIG_TRACER_SNAPSHOT diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 3650d84d4f16..7ce862c92a85 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -14,6 +14,7 @@ #include <linux/workqueue.h> #include <linux/security.h> #include <linux/spinlock.h> +#include <linux/seq_buf.h> #include <linux/kthread.h> #include <linux/tracefs.h> #include <linux/uaccess.h> @@ -22,6 +23,7 @@ #include <linux/sort.h> #include <linux/slab.h> #include <linux/delay.h> +#include <linux/btf.h> #include <trace/events/sched.h> #include <trace/syscall.h> @@ -400,6 +402,33 @@ static bool process_string(const char *fmt, int len, struct trace_event_call *ca return true; } +static void test_double_dereference(const char *str, int len, + struct trace_event_call *call) +{ + const char *ptr; + const char *end = str + len; + + ptr = strstr(str, "REC->"); + + while (ptr && ptr < end) { + + ptr += 5; + for (; ptr < end; ptr++) { + if (ptr[0] == '-' && ptr[1] == '>') { + pr_warn("TRACE EVENT ERROR: Event %s has double dereference in TP_printk: %.*s\n", + trace_event_name(call), len, str); + WARN_ONCE(1, "Event %s has double dereference in TP_printk: %.*s\n", + trace_event_name(call), len, str); + return; + } + if (!isalnum(*ptr) && *ptr != '_') + break; + } + + ptr = strstr(ptr, "REC->"); + } +} + static void handle_dereference_arg(const char *arg_str, u64 string_flags, int len, u64 *dereference_flags, int arg, struct trace_event_call *call) @@ -459,12 +488,6 @@ static void test_event_printk(struct trace_event_call *call) if (in_quote) { arg = 0; first = false; - /* - * If there was no %p* uses - * the fmt is OK. - */ - if (!dereference_flags) - return; } } if (in_quote) { @@ -576,6 +599,8 @@ static void test_event_printk(struct trace_event_call *call) continue; } + test_double_dereference(fmt + start_arg, e - start_arg, call); + if (dereference_flags & (1ULL << arg)) { handle_dereference_arg(fmt + start_arg, string_flags, e - start_arg, @@ -589,6 +614,8 @@ static void test_event_printk(struct trace_event_call *call) } } + test_double_dereference(fmt + start_arg, i - start_arg, call); + if (dereference_flags & (1ULL << arg)) { handle_dereference_arg(fmt + start_arg, string_flags, i - start_arg, @@ -2202,6 +2229,61 @@ event_id_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) } #endif +#ifdef CONFIG_BPF_EVENTS +static ssize_t +event_btf_ids_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) +{ + struct trace_event_file *file; + struct trace_event_call *call; + const struct btf_type *t; + struct module *mod = NULL; + u32 raw_id = 0, tp_id = 0, obj_id = 0; + const u32 *ids; + struct btf *btf; + char buf[128]; + int len; + + /* Module unload could free call->class and ids[] mid-read. */ + scoped_guard(mutex, &event_mutex) { + file = event_file_file(filp); + if (!file) + return -ENODEV; + + call = file->event_call; + ids = call->class->btf_ids; + if (!ids) + return -ENOENT; + if (!(call->flags & TRACE_EVENT_FL_DYNAMIC)) + mod = (struct module *)call->module; + + btf = btf_get_module_btf(mod); + if (IS_ERR_OR_NULL(btf)) + return -ENOENT; + + /* Module-local ids in ids[] need base+local relocation. */ + tp_id = btf_relocate_id(btf, ids[1]); + + /* + * Without FL_TRACEPOINT the dispatcher is shared (e.g. all + * per-syscall events fan out from __bpf_trace_sys_enter), so + * raw_btf_id has no per-event attach point — report 0. + */ + if (call->flags & TRACE_EVENT_FL_TRACEPOINT) { + t = btf_type_by_id(btf, btf_relocate_id(btf, ids[0])); + raw_id = t ? t->type : 0; + } + obj_id = btf_obj_id(btf); + btf_put(btf); + } + + len = scnprintf(buf, sizeof(buf), + "btf_obj_id: %u\nraw_btf_id: %u\ntp_btf_id: %u\n", + obj_id, raw_id, tp_id); + + return simple_read_from_buffer(ubuf, cnt, ppos, buf, len); +} +#endif + static ssize_t event_filter_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) @@ -2702,6 +2784,13 @@ static const struct file_operations ftrace_event_id_fops = { }; #endif +#ifdef CONFIG_BPF_EVENTS +static const struct file_operations ftrace_event_btf_ids_fops = { + .read = event_btf_ids_read, + .llseek = default_llseek, +}; +#endif + static const struct file_operations ftrace_event_filter_fops = { .open = tracing_open_file_tr, .read = event_filter_read, @@ -3095,6 +3184,14 @@ static int event_callback(const char *name, umode_t *mode, void **data, } #endif +#ifdef CONFIG_BPF_EVENTS + if (call->class->btf_ids && strcmp(name, "btf_ids") == 0) { + *mode = TRACE_MODE_READ; + *fops = &ftrace_event_btf_ids_fops; + return 1; + } +#endif + #ifdef CONFIG_HIST_TRIGGERS if (strcmp(name, "hist") == 0) { *mode = TRACE_MODE_READ; @@ -3149,7 +3246,14 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file) .callback = event_callback, }, #endif -#define NR_RO_EVENT_ENTRIES (1 + IS_ENABLED(CONFIG_PERF_EVENTS)) +#ifdef CONFIG_BPF_EVENTS + { + .name = "btf_ids", + .callback = event_callback, + }, +#endif +#define NR_RO_EVENT_ENTRIES (1 + IS_ENABLED(CONFIG_PERF_EVENTS) + \ + IS_ENABLED(CONFIG_BPF_EVENTS)) /* Readonly files must be above this line and counted by NR_RO_EVENT_ENTRIES. */ { .name = "enable", @@ -3557,7 +3661,7 @@ static void update_event_fields(struct trace_event_call *call, } /* Update all events for replacing eval and sanitizing */ -void trace_event_update_all(struct trace_eval_map **map, int len) +void trace_event_update_all(struct trace_eval_map **map, int len, struct module *mod) { struct trace_event_call *call, *p; const char *last_system = NULL; @@ -3569,6 +3673,10 @@ void trace_event_update_all(struct trace_eval_map **map, int len) mutex_lock(&event_mutex); down_write(&trace_event_sem); list_for_each_entry_safe(call, p, &ftrace_events, list) { + + if (mod && call->module != mod) + continue; + /* events are usually grouped together with systems */ if (!last_system || call->class->system != last_system) { first = true; @@ -4505,13 +4613,20 @@ extern struct trace_event_call *__start_ftrace_events[]; extern struct trace_event_call *__stop_ftrace_events[]; static char bootup_event_buf[COMMAND_LINE_SIZE] __initdata; +static struct seq_buf bootup_event_seq __initdata = { + .buffer = bootup_event_buf, + .size = sizeof(bootup_event_buf), +}; static __init int setup_trace_event(char *str) { - if (bootup_event_buf[0] != '\0') - strlcat(bootup_event_buf, ",", COMMAND_LINE_SIZE); + if (seq_buf_used(&bootup_event_seq) > 0) + seq_buf_puts(&bootup_event_seq, ","); + + seq_buf_puts(&bootup_event_seq, str); - strlcat(bootup_event_buf, str, COMMAND_LINE_SIZE); + if (seq_buf_has_overflowed(&bootup_event_seq)) + return -ENOMEM; trace_set_ring_buffer_expanded(NULL); disable_tracing_selftest("running event tracing"); @@ -4770,6 +4885,7 @@ static __init int event_trace_enable(void) */ __trace_early_add_events(tr); + seq_buf_str(&bootup_event_seq); early_enable_events(tr, bootup_event_buf, false); trace_printk_start_comm(); @@ -4798,6 +4914,7 @@ static __init int event_trace_enable_again(void) if (!tr) return -ENODEV; + seq_buf_str(&bootup_event_seq); early_enable_events(tr, bootup_event_buf, true); return 0; diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 58d28cd1afa3..893bd8b0e48a 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -94,7 +94,6 @@ typedef u64 (*hist_field_fn_t) (struct hist_field *field, #define HIST_FIELD_OPERANDS_MAX 2 #define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX) #define HIST_ACTIONS_MAX 8 -#define HIST_CONST_DIGITS_MAX 21 #define HIST_DIV_SHIFT 20 /* For optimizing division by constants */ enum field_op_id { @@ -1733,86 +1732,94 @@ static const char *get_hist_field_flags(struct hist_field *hist_field) return flags_str; } -static void expr_field_str(struct hist_field *field, char *expr) +static bool expr_field_str(struct hist_field *field, struct seq_buf *s) { + const char *field_name; + if (field->flags & HIST_FIELD_FL_VAR_REF) { if (!field->system) - strcat(expr, "$"); - } else if (field->flags & HIST_FIELD_FL_CONST) { - char str[HIST_CONST_DIGITS_MAX]; + seq_buf_putc(s, '$'); + } else if (field->flags & HIST_FIELD_FL_CONST) + seq_buf_printf(s, "%llu", field->constant); - snprintf(str, HIST_CONST_DIGITS_MAX, "%llu", field->constant); - strcat(expr, str); - } + field_name = hist_field_name(field, 0); + if (!field_name) + return false; - strcat(expr, hist_field_name(field, 0)); + seq_buf_puts(s, field_name); if (field->flags && !(field->flags & HIST_FIELD_FL_VAR_REF)) { const char *flags_str = get_hist_field_flags(field); - if (flags_str) { - strcat(expr, "."); - strcat(expr, flags_str); - } + if (flags_str) + seq_buf_printf(s, ".%s", flags_str); } + + return !seq_buf_has_overflowed(s); } static char *expr_str(struct hist_field *field, unsigned int level) { - char *expr; + char *expr __free(kfree) = NULL; + struct seq_buf s; if (level > 1) - return NULL; + return ERR_PTR(-EINVAL); expr = kzalloc(MAX_FILTER_STR_VAL, GFP_KERNEL); if (!expr) - return NULL; + return ERR_PTR(-ENOMEM); + + seq_buf_init(&s, expr, MAX_FILTER_STR_VAL); if (!field->operands[0]) { - expr_field_str(field, expr); - return expr; + if (!expr_field_str(field, &s)) + return ERR_PTR(-E2BIG); + + return_ptr(expr); } if (field->operator == FIELD_OP_UNARY_MINUS) { char *subexpr; - strcat(expr, "-("); subexpr = expr_str(field->operands[0], ++level); - if (!subexpr) { - kfree(expr); - return NULL; - } - strcat(expr, subexpr); - strcat(expr, ")"); + if (IS_ERR(subexpr)) + return subexpr; + seq_buf_printf(&s, "-(%s)", subexpr); kfree(subexpr); - return expr; + if (seq_buf_has_overflowed(&s)) + return ERR_PTR(-E2BIG); + + return_ptr(expr); } - expr_field_str(field->operands[0], expr); + if (!expr_field_str(field->operands[0], &s)) + return ERR_PTR(-E2BIG); switch (field->operator) { case FIELD_OP_MINUS: - strcat(expr, "-"); + seq_buf_putc(&s, '-'); break; case FIELD_OP_PLUS: - strcat(expr, "+"); + seq_buf_putc(&s, '+'); break; case FIELD_OP_DIV: - strcat(expr, "/"); + seq_buf_putc(&s, '/'); break; case FIELD_OP_MULT: - strcat(expr, "*"); + seq_buf_putc(&s, '*'); break; default: - kfree(expr); - return NULL; + return ERR_PTR(-EINVAL); } - expr_field_str(field->operands[1], expr); + if (seq_buf_has_overflowed(&s) || + !expr_field_str(field->operands[1], &s)) + return ERR_PTR(-E2BIG); - return expr; + return_ptr(expr); } /* @@ -1984,9 +1991,7 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data, if (flags & HIST_FIELD_FL_CONST) { hist_field->fn_num = HIST_FIELD_FN_CONST; hist_field->size = sizeof(u64); - hist_field->type = kstrdup("u64", GFP_KERNEL); - if (!hist_field->type) - goto free; + hist_field->type = "u64"; goto out; } @@ -2626,6 +2631,11 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data, expr->is_signed = operand1->is_signed; expr->operator = FIELD_OP_UNARY_MINUS; expr->name = expr_str(expr, 0); + if (IS_ERR(expr->name)) { + ret = PTR_ERR(expr->name); + expr->name = NULL; + goto free; + } expr->type = kstrdup_const(operand1->type, GFP_KERNEL); if (!expr->type) { ret = -ENOMEM; @@ -2838,6 +2848,11 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, destroy_hist_field(operand1, 0); expr->name = expr_str(expr, 0); + if (IS_ERR(expr->name)) { + ret = PTR_ERR(expr->name); + expr->name = NULL; + goto free_expr; + } } else { /* The operand sizes should be the same, so just pick one */ expr->size = operand1->size; @@ -2851,6 +2866,11 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, } expr->name = expr_str(expr, 0); + if (IS_ERR(expr->name)) { + ret = PTR_ERR(expr->name); + expr->name = NULL; + goto free_expr; + } } return expr; diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c index ad83419cb420..149300cc5e8a 100644 --- a/kernel/trace/trace_events_trigger.c +++ b/kernel/trace/trace_events_trigger.c @@ -1753,7 +1753,7 @@ int event_enable_trigger_parse(struct event_command *cmd_ops, char *glob, char *cmd, char *param_and_filter) { struct trace_event_file *event_enable_file; - struct enable_trigger_data *enable_data; + struct enable_trigger_data *enable_data __free(kfree) = NULL; struct event_trigger_data *trigger_data; struct trace_array *tr = file->tr; char *param, *filter; @@ -1803,17 +1803,13 @@ int event_enable_trigger_parse(struct event_command *cmd_ops, enable_data->file = event_enable_file; trigger_data = trigger_data_alloc(cmd_ops, cmd, param, enable_data); - if (!trigger_data) { - kfree(enable_data); + if (!trigger_data) return ret; - } if (remove) { event_trigger_unregister(cmd_ops, file, glob+1, trigger_data); kfree(trigger_data); - kfree(enable_data); - ret = 0; - return ret; + return 0; } /* Up the trigger_data count to make sure nothing frees it on failure */ @@ -1842,7 +1838,12 @@ int event_enable_trigger_parse(struct event_command *cmd_ops, if (ret) goto out_disable; + /* It's now safe to free the reference taken earlier */ event_trigger_free(trigger_data); + + /* The enabled_data is assigned to trigger_data->private_data */ + retain_and_null_ptr(enable_data); + return ret; out_disable: trace_event_enable_disable(event_enable_file, 0, 1); @@ -1851,7 +1852,6 @@ int event_enable_trigger_parse(struct event_command *cmd_ops, out_free: event_trigger_reset_filter(cmd_ops, trigger_data); event_trigger_free(trigger_data); - kfree(enable_data); return ret; } diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 8c82ecb735f4..2bbc89d4a266 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -1838,7 +1838,7 @@ static int user_event_show(struct seq_file *m, struct dyn_event *ev) list_for_each_entry_reverse(field, head, link) { if (depth == 0) - seq_puts(m, " "); + seq_putc(m, ' '); else seq_puts(m, "; "); @@ -1850,7 +1850,7 @@ static int user_event_show(struct seq_file *m, struct dyn_event *ev) depth++; } - seq_puts(m, "\n"); + seq_putc(m, '\n'); return 0; } @@ -2806,7 +2806,7 @@ static int user_seq_show(struct seq_file *m, void *p) hash_for_each(group->register_table, i, user, node) { status = user->status; - seq_printf(m, "%s", EVENT_TP_NAME(user)); + seq_puts(m, EVENT_TP_NAME(user)); if (status != 0) { seq_puts(m, " # Used by"); @@ -2819,13 +2819,13 @@ static int user_seq_show(struct seq_file *m, void *p) busy++; } - seq_puts(m, "\n"); + seq_putc(m, '\n'); active++; } mutex_unlock(&group->reg_mutex); - seq_puts(m, "\n"); + seq_putc(m, '\n'); seq_printf(m, "Active: %d\n", active); seq_printf(m, "Busy: %d\n", busy); diff --git a/kernel/trace/trace_functions_graph.c b/kernel/trace/trace_functions_graph.c index 0d2d3a2ea7dd..ff7cb1a76b95 100644 --- a/kernel/trace/trace_functions_graph.c +++ b/kernel/trace/trace_functions_graph.c @@ -1349,7 +1349,7 @@ print_graph_return(struct ftrace_graph_ret_entry *retentry, struct trace_seq *s, * that if the funcgraph-tail option is enabled. */ if (func_match && !(flags & TRACE_GRAPH_PRINT_TAIL)) - trace_seq_puts(s, "}"); + trace_seq_putc(s, '}'); else trace_seq_printf(s, "} /* %ps */", (void *)func); } diff --git a/kernel/trace/trace_mmiotrace.c b/kernel/trace/trace_mmiotrace.c index df8692c2dea8..77120d467e11 100644 --- a/kernel/trace/trace_mmiotrace.c +++ b/kernel/trace/trace_mmiotrace.c @@ -244,13 +244,16 @@ static enum print_line_t mmio_print_map(struct trace_iterator *iter) static enum print_line_t mmio_print_mark(struct trace_iterator *iter) { struct trace_entry *entry = iter->ent; - struct print_entry *print = (struct print_entry *)entry; - const char *msg = print->buf; + struct print_entry *print; + const char *msg; struct trace_seq *s = &iter->seq; unsigned long long t = ns2usecs(iter->ts); unsigned long usec_rem = do_div(t, USEC_PER_SEC); unsigned secs = (unsigned long)t; + trace_assign_type(print, entry); + msg = print->buf; + /* The trailing newline must be in the message. */ trace_seq_printf(s, "MARK %u.%06lu %s", secs, usec_rem, msg); diff --git a/kernel/trace/trace_printk.c b/kernel/trace/trace_printk.c index 3ea17af60169..98171a2398e4 100644 --- a/kernel/trace/trace_printk.c +++ b/kernel/trace/trace_printk.c @@ -71,10 +71,9 @@ void hold_module_trace_bprintk_format(const char **start, const char **end) fmt = NULL; tb_fmt = kmalloc_obj(*tb_fmt); if (tb_fmt) { - fmt = kmalloc(strlen(*iter) + 1, GFP_KERNEL); + fmt = kstrdup(*iter, GFP_KERNEL); if (fmt) { list_add_tail(&tb_fmt->list, &trace_bprintk_fmt_list); - strcpy(fmt, *iter); tb_fmt->fmt = fmt; } else kfree(tb_fmt); diff --git a/kernel/trace/trace_sched_switch.c b/kernel/trace/trace_sched_switch.c index e9f0ff962660..1bd351c1fbfb 100644 --- a/kernel/trace/trace_sched_switch.c +++ b/kernel/trace/trace_sched_switch.c @@ -286,12 +286,12 @@ static void __trace_find_cmdline(int pid, char comm[]) int tpid; if (!pid) { - strcpy(comm, "<idle>"); + strscpy(comm, "<idle>", TASK_COMM_LEN); return; } if (WARN_ON_ONCE(pid < 0)) { - strcpy(comm, "<XXX>"); + strscpy(comm, "<XXX>", TASK_COMM_LEN); return; } @@ -304,7 +304,7 @@ static void __trace_find_cmdline(int pid, char comm[]) return; } } - strcpy(comm, "<...>"); + strscpy(comm, "<...>", TASK_COMM_LEN); } void trace_find_cmdline(int pid, char comm[]) diff --git a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c index 8a4f3c75e39f..e35744049e3f 100644 --- a/kernel/trace/trace_syscalls.c +++ b/kernel/trace/trace_syscalls.c @@ -9,6 +9,7 @@ #include <linux/ftrace.h> #include <linux/perf_event.h> #include <linux/xarray.h> +#include <linux/btf_ids.h> #include <asm/syscall.h> #include "trace_output.h" @@ -1303,12 +1304,26 @@ struct trace_event_functions exit_syscall_print_funcs = { .trace = print_syscall_exit, }; +#if defined(CONFIG_BPF_EVENTS) && defined(CONFIG_DEBUG_INFO_BTF) +/* BTF id lists for the shared sys_enter/sys_exit dispatcher tracepoints. */ +BTF_ID_LIST(syscall_enter_btf_ids) +BTF_ID(func, __bpf_trace_sys_enter) +BTF_ID(struct, trace_event_raw_sys_enter) + +BTF_ID_LIST(syscall_exit_btf_ids) +BTF_ID(func, __bpf_trace_sys_exit) +BTF_ID(struct, trace_event_raw_sys_exit) +#endif + struct trace_event_class __refdata event_class_syscall_enter = { .system = "syscalls", .reg = syscall_enter_register, .fields_array = syscall_enter_fields_array, .get_fields = syscall_get_enter_fields, .raw_init = init_syscall_trace, +#if defined(CONFIG_BPF_EVENTS) && defined(CONFIG_DEBUG_INFO_BTF) + .btf_ids = syscall_enter_btf_ids, +#endif }; struct trace_event_class __refdata event_class_syscall_exit = { @@ -1321,6 +1336,9 @@ struct trace_event_class __refdata event_class_syscall_exit = { }, .fields = LIST_HEAD_INIT(event_class_syscall_exit.fields), .raw_init = init_syscall_trace, +#if defined(CONFIG_BPF_EVENTS) && defined(CONFIG_DEBUG_INFO_BTF) + .btf_ids = syscall_exit_btf_ids, +#endif }; unsigned long __init __weak arch_syscall_addr(int nr) diff --git a/samples/ftrace/ftrace-ops.c b/samples/ftrace/ftrace-ops.c index 152ffc1a30b6..8cae38d8b879 100644 --- a/samples/ftrace/ftrace-ops.c +++ b/samples/ftrace/ftrace-ops.c @@ -223,7 +223,7 @@ static int __init ftrace_ops_sample_init(void) pr_info("Attempted %u calls to %ps in %lluns (%lluns / call)\n", nr_function_calls, tracee_relevant, - period, div_u64(period, nr_function_calls)); + period, nr_function_calls ? div_u64(period, nr_function_calls) : -1LL); if (persist) return 0; diff --git a/tools/testing/selftests/bpf/prog_tests/tp_btf_ids.c b/tools/testing/selftests/bpf/prog_tests/tp_btf_ids.c new file mode 100644 index 000000000000..c0e7e11e71b8 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tp_btf_ids.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <test_progs.h> +#include <bpf/btf.h> + +#define TRACEFS "/sys/kernel/tracing" +#define DEBUGFS_TRACING "/sys/kernel/debug/tracing" +#define EVENT_SUBPATH "events/bpf_testmod/bpf_testmod_test_read/btf_ids" + +struct btf_ids_info { + __u32 obj_id; + __u32 raw_id; + __u32 tp_id; +}; + +static const char *btf_ids_path(char *buf, size_t sz) +{ + if (access(TRACEFS "/trace", F_OK) == 0) + snprintf(buf, sz, "%s/%s", TRACEFS, EVENT_SUBPATH); + else + snprintf(buf, sz, "%s/%s", DEBUGFS_TRACING, EVENT_SUBPATH); + return buf; +} + +static int read_btf_ids(struct btf_ids_info *info) +{ + char path[256], buf[256]; + int fd, n; + + fd = open(btf_ids_path(path, sizeof(path)), O_RDONLY); + if (fd < 0) + return -errno; + + n = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (n <= 0) + return -EIO; + buf[n] = '\0'; + + if (sscanf(buf, + "btf_obj_id: %u\nraw_btf_id: %u\ntp_btf_id: %u\n", + &info->obj_id, &info->raw_id, &info->tp_id) != 3) + return -EINVAL; + return 0; +} + +static const char *param_name(struct btf *btf, const struct btf_param *p) +{ + return btf__name_by_offset(btf, p->name_off); +} + +static const char *member_name(struct btf *btf, const struct btf_member *m) +{ + return btf__name_by_offset(btf, m->name_off); +} + +void test_tp_btf_ids(void) +{ + const struct btf_type *proto_t, *rec_t; + const struct btf_param *params; + const struct btf_member *members; + struct btf_ids_info info; + struct btf *vmlinux_btf, *btf; + const char *name; + int err; + + if (!env.has_testmod) { + test__skip(); + return; + } + + err = read_btf_ids(&info); + if (!ASSERT_OK(err, "read btf_ids")) + return; + + ASSERT_GT(info.obj_id, 0, "obj_id non-zero"); + ASSERT_GT(info.raw_id, 0, "raw_id non-zero"); + ASSERT_GT(info.tp_id, 0, "tp_id non-zero"); + + vmlinux_btf = btf__load_vmlinux_btf(); + if (!ASSERT_OK_PTR(vmlinux_btf, "load vmlinux BTF")) + return; + + /* Module BTF is split BTF; load with vmlinux as base. */ + btf = btf__load_from_kernel_by_id_split(info.obj_id, vmlinux_btf); + if (!ASSERT_OK_PTR(btf, "load module BTF")) { + btf__free(vmlinux_btf); + return; + } + + /* + * raw_btf_id should be the FUNC_PROTO of __bpf_trace_<call>: + * void *__data, struct task_struct *task, + * struct bpf_testmod_test_read_ctx *ctx + */ + proto_t = btf__type_by_id(btf, info.raw_id); + if (!ASSERT_OK_PTR(proto_t, "raw type_by_id")) + goto out; + if (!ASSERT_TRUE(btf_is_func_proto(proto_t), "raw is FUNC_PROTO")) + goto out; + if (!ASSERT_EQ(btf_vlen(proto_t), 3, "func_proto arg count")) + goto out; + + params = btf_params(proto_t); + ASSERT_STREQ(param_name(btf, ¶ms[0]), "__data", "arg0 name"); + ASSERT_STREQ(param_name(btf, ¶ms[1]), "task", "arg1 name"); + ASSERT_STREQ(param_name(btf, ¶ms[2]), "ctx", "arg2 name"); + + /* + * tp_btf_id should be STRUCT trace_event_raw_<call> with the + * fields declared by TP_STRUCT__entry plus the common header. + */ + rec_t = btf__type_by_id(btf, info.tp_id); + if (!ASSERT_OK_PTR(rec_t, "tp type_by_id")) + goto out; + if (!ASSERT_TRUE(btf_is_struct(rec_t), "tp is STRUCT")) + goto out; + name = btf__name_by_offset(btf, rec_t->name_off); + ASSERT_STREQ(name, "trace_event_raw_bpf_testmod_test_read", + "tp struct name"); + if (!ASSERT_GE(btf_vlen(rec_t), 5, "tp struct field count")) + goto out; + + members = btf_members(rec_t); + ASSERT_STREQ(member_name(btf, &members[0]), "ent", "field0 name"); + ASSERT_STREQ(member_name(btf, &members[1]), "pid", "field1 name"); + ASSERT_STREQ(member_name(btf, &members[2]), "comm", "field2 name"); + ASSERT_STREQ(member_name(btf, &members[3]), "off", "field3 name"); + ASSERT_STREQ(member_name(btf, &members[4]), "len", "field4 name"); +out: + btf__free(btf); + btf__free(vmlinux_btf); +} |
