* [PATCH perf-tools-next v6 1/5] perf trace: Fix error checking in btf_struct_scnprintf()
2026-08-24 13:31 [PATCH perf-tools-next v6 0/5] perf trace: Symbolise kernel virtual addresses and function pointers Aaron Tomlin
@ 2026-08-24 13:31 ` Aaron Tomlin
2026-08-24 13:31 ` [PATCH perf-tools-next v6 2/5] perf trace: Introduce kernel symbol beautifier for virtual addresses Aaron Tomlin
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Aaron Tomlin @ 2026-08-24 13:31 UTC (permalink / raw)
To: peterz, mingo, acme, namhyung
Cc: mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter,
james.clark, howardchu95, atomlin, neelx, chjohnst, sean, steve,
rishil1999, linux-perf-users, linux-kernel
btf_dump__dump_type_data() returns the positive number of bytes dumped on
success, or a negative error code (e.g., -EINVAL) on failure.
Currently, btf_struct_scnprintf() checks if btf_dump__dump_type_data()
returns 0. When a negative error code is returned on failure, this check
evaluates to false, causing the function to proceed down the success path
and advance arg->augmented.args past the unprinted struct data before
returning 0.
Consequently, when syscall__scnprintf_args() falls back to the default
argument beautifier upon receiving 0 from trace__btf_scnprintf(), the
fallback beautifier or subsequent arguments read from an erroneously
advanced augmented arguments buffer.
Fix this by checking for '<= 0' ensuring that on any failure or 0-byte
dump, 0 is returned without modifying the augmented arguments pointer.
Fixes: cb32035214b9 ("perf trace: Pretty print augmented struct args using BTF")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
tools/perf/builtin-trace.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index c3c7f1f85c53..1bc39f674f11 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -1066,7 +1066,9 @@ static size_t btf_struct_scnprintf(const struct btf_type *type, struct btf *btf,
return 0;
/* pretty print the struct data here */
- if (btf_dump__dump_type_data(btf_dump, type_id, arg->augmented.args->value, type->size, &dump_data_opts) == 0)
+ if (btf_dump__dump_type_data(btf_dump, type_id,
+ arg->augmented.args->value,
+ type->size, &dump_data_opts) <= 0)
return 0;
consumed = sizeof(*augmented_arg) + augmented_arg->size;
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH perf-tools-next v6 2/5] perf trace: Introduce kernel symbol beautifier for virtual addresses
2026-08-24 13:31 [PATCH perf-tools-next v6 0/5] perf trace: Symbolise kernel virtual addresses and function pointers Aaron Tomlin
2026-08-24 13:31 ` [PATCH perf-tools-next v6 1/5] perf trace: Fix error checking in btf_struct_scnprintf() Aaron Tomlin
@ 2026-08-24 13:31 ` Aaron Tomlin
2026-08-24 13:31 ` [PATCH perf-tools-next v6 3/5] perf trace: Auto-assign kernel symbol beautifier to function pointer fields Aaron Tomlin
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Aaron Tomlin @ 2026-08-24 13:31 UTC (permalink / raw)
To: peterz, mingo, acme, namhyung
Cc: mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter,
james.clark, howardchu95, atomlin, neelx, chjohnst, sean, steve,
rishil1999, linux-perf-users, linux-kernel
Currently, when 'perf trace' formats tracepoint payloads or system call
arguments containing raw kernel virtual addresses (e.g., a work item
function pointer work_func_t in workqueue:workqueue_execute_start),
it prints them as raw hexadecimal values (e.g., 0xffffffff81234567).
This impairs readability when tracing kernel execution flows.
Introduce a dedicated kernel symbol beautifier,
syscall_arg__scnprintf_ksym (i.e., SCA_KSYM), to resolve kernel
virtual addresses to human-readable symbol names and offsets
(e.g., "flush_to_ldisc").
The beautifier looks up the virtual address in the machine kernel maps via
machine__find_kernel_symbol(). If a valid kernel symbol is found, the
symbol name and offset are printed without requiring --libtraceevent; if
the address is zero, "NULL" is rendered; otherwise, it gracefully falls
back to hexadecimal formatting.
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
tools/perf/builtin-trace.c | 29 +++++++++++++++++++++++++++++
tools/perf/trace/beauty/beauty.h | 3 +++
2 files changed, 32 insertions(+)
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index 1bc39f674f11..effb00117bff 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -750,6 +750,35 @@ size_t syscall_arg__scnprintf_ptr(char *bf, size_t size, struct syscall_arg *arg
return syscall_arg__scnprintf_hex(bf, size, arg);
}
+size_t syscall_arg__scnprintf_ksym(char *bf, size_t size, struct syscall_arg *arg)
+{
+ if (arg->val == 0)
+ return scnprintf(bf, size, "NULL");
+
+ if (arg->trace && arg->trace->host) {
+ struct map *map = NULL;
+ struct symbol *sym = machine__find_kernel_symbol(arg->trace->host,
+ arg->val, &map);
+
+ if (sym) {
+ u64 start = map__unmap_ip(map, sym->start);
+ u64 offset = arg->val - start;
+ size_t printed;
+
+ if (offset == 0)
+ printed = scnprintf(bf, size, "%s", sym->name);
+ else
+ printed = scnprintf(bf, size, "%s+0x%" PRIx64,
+ sym->name, offset);
+ map__put(map);
+ return printed;
+ }
+ map__put(map);
+ }
+
+ return syscall_arg__scnprintf_hex(bf, size, arg);
+}
+
size_t syscall_arg__scnprintf_int(char *bf, size_t size, struct syscall_arg *arg)
{
return scnprintf(bf, size, "%d", arg->val);
diff --git a/tools/perf/trace/beauty/beauty.h b/tools/perf/trace/beauty/beauty.h
index 58a3206481ae..0f4801c61a5b 100644
--- a/tools/perf/trace/beauty/beauty.h
+++ b/tools/perf/trace/beauty/beauty.h
@@ -160,6 +160,9 @@ size_t syscall_arg__scnprintf_hex(char *bf, size_t size, struct syscall_arg *arg
size_t syscall_arg__scnprintf_ptr(char *bf, size_t size, struct syscall_arg *arg);
#define SCA_PTR syscall_arg__scnprintf_ptr
+size_t syscall_arg__scnprintf_ksym(char *bf, size_t size, struct syscall_arg *arg);
+#define SCA_KSYM syscall_arg__scnprintf_ksym
+
size_t syscall_arg__scnprintf_int(char *bf, size_t size, struct syscall_arg *arg);
#define SCA_INT syscall_arg__scnprintf_int
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH perf-tools-next v6 3/5] perf trace: Auto-assign kernel symbol beautifier to function pointer fields
2026-08-24 13:31 [PATCH perf-tools-next v6 0/5] perf trace: Symbolise kernel virtual addresses and function pointers Aaron Tomlin
2026-08-24 13:31 ` [PATCH perf-tools-next v6 1/5] perf trace: Fix error checking in btf_struct_scnprintf() Aaron Tomlin
2026-08-24 13:31 ` [PATCH perf-tools-next v6 2/5] perf trace: Introduce kernel symbol beautifier for virtual addresses Aaron Tomlin
@ 2026-08-24 13:31 ` Aaron Tomlin
2026-08-24 13:31 ` [PATCH perf-tools-next v6 4/5] perf trace: Enhance BTF type formatting to symbolise kernel function pointers Aaron Tomlin
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Aaron Tomlin @ 2026-08-24 13:31 UTC (permalink / raw)
To: peterz, mingo, acme, namhyung
Cc: mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter,
james.clark, howardchu95, atomlin, neelx, chjohnst, sean, steve,
rishil1999, linux-perf-users, linux-kernel
Tracepoint fields that convey kernel function pointers, callbacks, and
call sites, such as "function", "func", "fn", "callback", "action",
"handler", "caller", "caller_ip", "location", "callsite", and
"call_site" are currently formatted as generic hexadecimal pointers by
default.
Enhance syscall_arg_fmt__init_array() to automatically detect non-array
function pointer fields by type signature (e.g., typedefs ending with
"_func_t" or "_fn", or C function pointer types containing "(*)") and
assign SCA_KSYM as their default beautifier.
Additionally, register common function pointer and callback field names
within the sorted syscall_arg_fmts__by_name lookup table. To prevent
misclassifying dynamic string arrays, enums, or standard non-pointer
integers sharing generic names, guard SCA_KSYM assignment to true pointer
fields and pointer-sized non-array scalars (via field_is_ptr_sized(),
field_is_enum(), and field_is_plain_int()). This ensures enum fields
continue falling through to BTF pretty-printing. To support
cross-architecture analysis (e.g., analyzing 32-bit trace data on a
64-bit host) and 64-bit instruction pointers, determine target pointer
size via tep_get_long_size() while also supporting 64-bit scalars.
This ensures tracepoint arguments such as
workqueue:workqueue_execute_start.function, csd:csd_function.func,
and xfs:xfs_bunmapi.caller_ip are symbolised automatically without
requiring explicit per-event configuration. For example:
❯ sudo tools/perf/perf trace --event workqueue:workqueue_execute_end --max-events 2 --show-cpu
0.000 [000] kworker/u32:15/236682 workqueue:workqueue_execute_end(work: 0xffffffffab2f1420, function: toggle_allocation_gate)
0.132 [000] kworker/u32:15/236682 workqueue:workqueue_execute_end(work: 0xffff8ac2c1adc010, function: flush_to_ldisc)
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
tools/perf/builtin-trace.c | 113 ++++++++++++++++++++++++-------------
1 file changed, 75 insertions(+), 38 deletions(-)
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index effb00117bff..f6ab7dabfa4c 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -2098,6 +2098,18 @@ static int syscall__alloc_arg_fmts(struct syscall *sc, int nr_args)
}
static const struct syscall_arg_fmt syscall_arg_fmts__by_name[] = {
+ { .name = "action", .scnprintf = SCA_KSYM, },
+ { .name = "call_site", .scnprintf = SCA_KSYM, },
+ { .name = "callback", .scnprintf = SCA_KSYM, },
+ { .name = "caller", .scnprintf = SCA_KSYM, },
+ { .name = "caller_ip", .scnprintf = SCA_KSYM, },
+ { .name = "callsite", .scnprintf = SCA_KSYM, },
+ { .name = "cb", .scnprintf = SCA_KSYM, },
+ { .name = "fn", .scnprintf = SCA_KSYM, },
+ { .name = "func", .scnprintf = SCA_KSYM, },
+ { .name = "function", .scnprintf = SCA_KSYM, },
+ { .name = "handler", .scnprintf = SCA_KSYM, },
+ { .name = "location", .scnprintf = SCA_KSYM, },
{ .name = "msr", .scnprintf = SCA_X86_MSR, .strtoul = STUL_X86_MSR, },
{ .name = "vector", .scnprintf = SCA_X86_IRQ_VECTORS, .strtoul = STUL_X86_IRQ_VECTORS, },
};
@@ -2173,6 +2185,29 @@ static bool field_has_hex_fmt(struct tep_format_field *field, int len)
return false;
}
+static bool field_is_enum(const struct tep_format_field *field)
+{
+ return field->type && strstr(field->type, "enum") != NULL;
+}
+
+static bool field_is_plain_int(const struct tep_format_field *field)
+{
+ if (!field->type)
+ return false;
+
+ return !strcmp(field->type, "int") ||
+ !strcmp(field->type, "unsigned int") ||
+ !strcmp(field->type, "u32") ||
+ !strcmp(field->type, "s32");
+}
+
+static bool field_is_ptr_sized(const struct tep_format_field *field)
+{
+ int ptr_size = tep_get_long_size(field->event->tep);
+
+ return field->size == ptr_size || field->size == sizeof(u64);
+}
+
static struct tep_format_field *
syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field *field,
bool *use_btf)
@@ -2200,38 +2235,49 @@ syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field
((len >= 4 && strcmp(field->name + len - 4, "name") == 0) ||
strstr(field->name, "path") != NULL)) {
arg->scnprintf = SCA_FILENAME;
- } else if ((field->flags & TEP_FIELD_IS_POINTER) || strstr(field->name, "addr") ||
- field_has_hex_fmt(field, len))
- arg->scnprintf = SCA_PTR;
- else if (strcmp(field->type, "pid_t") == 0)
- arg->scnprintf = SCA_PID;
- else if (strcmp(field->type, "umode_t") == 0)
- arg->scnprintf = SCA_MODE_T;
- else if ((field->flags & TEP_FIELD_IS_ARRAY) && strstr(field->type, "char")) {
- arg->scnprintf = SCA_CHAR_ARRAY;
- arg->nr_entries = field->arraylen;
- } else if ((strcmp(field->type, "int") == 0 ||
- strcmp(field->type, "unsigned int") == 0 ||
- strcmp(field->type, "long") == 0) &&
- len >= 2 && strcmp(field->name + len - 2, "fd") == 0) {
- /*
- * /sys/kernel/tracing/events/syscalls/sys_enter*
- * grep -E 'field:.*fd;' .../format|sed -r 's/.*field:([a-z ]+) [a-z_]*fd.+/\1/g'|sort|uniq -c
- * 65 int
- * 23 unsigned int
- * 7 unsigned long
- */
- arg->scnprintf = SCA_FD;
- } else if (strstr(field->type, "enum") && use_btf != NULL) {
- *use_btf = true;
- arg->strtoul = STUL_BTF_TYPE;
+ } else if (field->type && !(field->flags & TEP_FIELD_IS_ARRAY) &&
+ (strstr(field->type, "(*)") != NULL ||
+ strstr(field->type, "_func_t") != NULL ||
+ strstr(field->type, "_fn") != NULL)) {
+ arg->scnprintf = SCA_KSYM;
} else {
const struct syscall_arg_fmt *fmt =
syscall_arg_fmt__find_by_name(field->name);
if (fmt) {
- arg->scnprintf = fmt->scnprintf;
- arg->strtoul = fmt->strtoul;
+ if (fmt->scnprintf == SCA_KSYM) {
+ if ((field->flags & TEP_FIELD_IS_POINTER) ||
+ (!field_is_enum(field) && !field_is_plain_int(field) &&
+ field_is_ptr_sized(field) && !(field->flags & TEP_FIELD_IS_ARRAY))) {
+ arg->scnprintf = fmt->scnprintf;
+ arg->strtoul = fmt->strtoul;
+ }
+ } else {
+ arg->scnprintf = fmt->scnprintf;
+ arg->strtoul = fmt->strtoul;
+ }
+ }
+
+ if (arg->scnprintf == NULL) {
+ if ((field->flags & TEP_FIELD_IS_POINTER) || strstr(field->name, "addr") ||
+ field_has_hex_fmt(field, len)) {
+ arg->scnprintf = SCA_PTR;
+ } else if (strcmp(field->type, "pid_t") == 0) {
+ arg->scnprintf = SCA_PID;
+ } else if (strcmp(field->type, "umode_t") == 0) {
+ arg->scnprintf = SCA_MODE_T;
+ } else if ((field->flags & TEP_FIELD_IS_ARRAY) && strstr(field->type, "char")) {
+ arg->scnprintf = SCA_CHAR_ARRAY;
+ arg->nr_entries = field->arraylen;
+ } else if ((strcmp(field->type, "int") == 0 ||
+ strcmp(field->type, "unsigned int") == 0 ||
+ strcmp(field->type, "long") == 0) &&
+ len >= 2 && strcmp(field->name + len - 2, "fd") == 0) {
+ arg->scnprintf = SCA_FD;
+ } else if (field_is_enum(field) && use_btf != NULL) {
+ *use_btf = true;
+ arg->strtoul = STUL_BTF_TYPE;
+ }
}
}
}
@@ -3299,12 +3345,6 @@ static unsigned char bitmap_byte(const unsigned long *mask, int byte_idx)
return b_val;
}
-static bool trace__field_is_ip(const char *name)
-{
- return !strcmp(name, "__probe_ip") ||
- !strcmp(name, "caller_ip") ||
- !strcmp(name, "call_site");
-}
static size_t trace__fprintf_tp_fields(struct trace *trace, struct perf_sample *sample,
struct thread *thread, void *augmented_args, int augmented_args_size)
@@ -3406,14 +3446,11 @@ static size_t trace__fprintf_tp_fields(struct trace *trace, struct perf_sample *
* Suppress it by default to avoid cluttering the output.
* If verbose mode is enabled, ensure it is formatted as a
* hexadecimal memory address rather than a signed integer.
- *
- * caller_ip and call_site are also expected to be instruction
- * pointers and should always be represented in hexadecimal.
*/
is_probe_ip = evsel__is_probe(evsel) && !strcmp(field->name, "__probe_ip");
- if (is_probe_ip || trace__field_is_ip(field->name)) {
- if (is_probe_ip && !verbose)
+ if (is_probe_ip) {
+ if (!verbose)
continue;
printed += scnprintf(bf + printed, size - printed,
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH perf-tools-next v6 4/5] perf trace: Enhance BTF type formatting to symbolise kernel function pointers
2026-08-24 13:31 [PATCH perf-tools-next v6 0/5] perf trace: Symbolise kernel virtual addresses and function pointers Aaron Tomlin
` (2 preceding siblings ...)
2026-08-24 13:31 ` [PATCH perf-tools-next v6 3/5] perf trace: Auto-assign kernel symbol beautifier to function pointer fields Aaron Tomlin
@ 2026-08-24 13:31 ` Aaron Tomlin
2026-08-24 13:31 ` [PATCH perf-tools-next v6 5/5] perf tests: Add shell test for kernel symbol beautifier Aaron Tomlin
2026-08-24 16:30 ` [PATCH perf-tools-next v6 0/5] perf trace: Symbolise kernel virtual addresses and function pointers Ian Rogers
5 siblings, 0 replies; 7+ messages in thread
From: Aaron Tomlin @ 2026-08-24 13:31 UTC (permalink / raw)
To: peterz, mingo, acme, namhyung
Cc: mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter,
james.clark, howardchu95, atomlin, neelx, chjohnst, sean, steve,
rishil1999, linux-perf-users, linux-kernel
When BTF (BPF Type Format) metadata is loaded from vmlinux, 'perf trace'
can inspect the precise C types of tracepoint and system call parameters.
However, function pointer arguments are currently not recognised during
BTF pretty-printing and default to hexadecimal output.
Introduce btf_is_func_ptr() to inspect BTF type hierarchies
(i.e., traversing pointers, typedefs, and type modifiers) to determine
whether a parameter resolves to a function prototype
(BTF_KIND_FUNC_PROTO).
Generalise BTF type caching via syscall_arg_fmt__cache_btf_type() to
handle structs, unions, enums, and function pointers alike. When a field
is identified as a kernel function pointer, trace__btf_scnprintf()
routes its value to syscall_arg__scnprintf_ksym(), enabling automatic
zero-config symbolisation of kernel function pointers whenever BTF is
available.
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
tools/perf/builtin-trace.c | 106 +++++++++++++++++++++++++++++--------
1 file changed, 85 insertions(+), 21 deletions(-)
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index f6ab7dabfa4c..d0089230dc5d 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -122,6 +122,7 @@ struct syscall_arg_fmt {
#ifdef HAVE_LIBBPF_SUPPORT
const struct btf_type *type;
int type_id; /* used in btf_dump */
+ bool btf_type_cached;
#endif
};
@@ -979,21 +980,68 @@ static size_t syscall_arg__scnprintf_getrandom_flags(char *bf, size_t size,
#define SCA_GETRANDOM_FLAGS syscall_arg__scnprintf_getrandom_flags
#ifdef HAVE_LIBBPF_SUPPORT
-static void syscall_arg_fmt__cache_btf_enum(struct syscall_arg_fmt *arg_fmt, struct btf *btf, char *type)
+static bool btf_is_func_ptr(const struct btf *btf, const struct btf_type *type)
{
+ int nr_ptrs = 0;
+
+ while (type) {
+ if (btf_is_ptr(type)) {
+ if (++nr_ptrs > 1)
+ return false;
+ type = btf__type_by_id(btf, type->type);
+ } else if (btf_is_typedef(type) || btf_is_mod(type)) {
+ type = btf__type_by_id(btf, type->type);
+ } else {
+ break;
+ }
+ }
+ return nr_ptrs == 1 && type && btf_is_func_proto(type);
+}
+
+static void syscall_arg_fmt__cache_btf_type(struct syscall_arg_fmt *arg_fmt,
+ struct btf *btf, const char *type)
+{
+ char name[128];
+ const char *pos;
+ size_t len = 0;
int id;
- type = strstr(type, "enum ");
+ arg_fmt->btf_type_cached = true;
+
if (type == NULL)
return;
- type += 5; // skip "enum " to get the enumeration name
+ /* Pointers to enums are memory addresses, not scalar enums */
+ if (strstr(type, "enum ") && strchr(type, '*'))
+ return;
+
+ if ((pos = strstr(type, "enum ")) != NULL)
+ pos += 5;
+ else if ((pos = strstr(type, "struct ")) != NULL)
+ pos += 7;
+ else if ((pos = strstr(type, "union ")) != NULL)
+ pos += 6;
+ else
+ pos = type;
+
+ while (isspace(*pos))
+ pos++;
+
+ while ((isalnum(pos[len]) || pos[len] == '_') && len < sizeof(name) - 1) {
+ name[len] = pos[len];
+ len++;
+ }
+ name[len] = '\0';
+
+ if (len == 0)
+ return;
- id = btf__find_by_name(btf, type);
+ id = btf__find_by_name(btf, name);
if (id < 0)
return;
arg_fmt->type = btf__type_by_id(btf, id);
+ arg_fmt->type_id = id;
}
static bool syscall_arg__strtoul_btf_enum(char *bf, size_t size, struct syscall_arg *arg, u64 *val)
@@ -1027,10 +1075,8 @@ static bool syscall_arg__strtoul_btf_type(char *bf, size_t size, struct syscall_
if (btf == NULL)
return false;
- if (arg->fmt->type == NULL) {
- // See if this is an enum
- syscall_arg_fmt__cache_btf_enum(arg->fmt, btf, type);
- }
+ if (!arg->fmt->btf_type_cached)
+ syscall_arg_fmt__cache_btf_type(arg->fmt, btf, type);
// Now let's see if we have a BTF type resolved
bt = arg->fmt->type;
@@ -1038,19 +1084,22 @@ static bool syscall_arg__strtoul_btf_type(char *bf, size_t size, struct syscall_
return false;
// If it is an enum:
- if (btf_is_enum(arg->fmt->type))
+ if (btf_is_enum(arg->fmt->type)) {
+ if (type && strchr(type, '*'))
+ return false;
return syscall_arg__strtoul_btf_enum(bf, size, arg, val);
+ }
return false;
}
-static size_t btf_enum_scnprintf(const struct btf_type *type, struct btf *btf, char *bf, size_t size, int val)
+static size_t btf_enum_scnprintf(const struct btf_type *type, struct btf *btf, char *bf, size_t size, unsigned long val)
{
struct btf_enum *be = btf_enum(type);
const unsigned int nr_entries = btf_vlen(type);
for (unsigned int i = 0; i < nr_entries; ++i, ++be) {
- if (be->val == val) {
+ if ((unsigned long)(__u32)be->val == val || (unsigned long)be->val == val) {
return scnprintf(bf, size, "%s",
btf__name_by_offset(btf, be->name_off));
}
@@ -1077,14 +1126,19 @@ static size_t btf_struct_scnprintf(const struct btf_type *type, struct btf *btf,
.bf = bf,
.size = size,
};
- struct augmented_arg *augmented_arg = arg->augmented.args;
+ struct augmented_arg *augmented_arg;
int type_id = arg->fmt->type_id, consumed;
struct btf_dump *btf_dump;
LIBBPF_OPTS(btf_dump_opts, dump_opts);
LIBBPF_OPTS(btf_dump_type_data_opts, dump_data_opts);
- if (arg == NULL || arg->augmented.args == NULL)
+ if (arg == NULL || arg->augmented.args == NULL || arg->augmented.size <= 0 ||
+ arg->fmt == NULL || !arg->fmt->from_user)
+ return 0;
+
+ augmented_arg = arg->augmented.args;
+ if (augmented_arg->size <= 0)
return 0;
dump_data_opts.compact = true;
@@ -1097,8 +1151,10 @@ static size_t btf_struct_scnprintf(const struct btf_type *type, struct btf *btf,
/* pretty print the struct data here */
if (btf_dump__dump_type_data(btf_dump, type_id,
arg->augmented.args->value,
- type->size, &dump_data_opts) <= 0)
+ type->size, &dump_data_opts) <= 0) {
+ btf_dump__free(btf_dump);
return 0;
+ }
consumed = sizeof(*augmented_arg) + augmented_arg->size;
arg->augmented.args = ((void *)arg->augmented.args) + consumed;
@@ -1110,33 +1166,40 @@ static size_t btf_struct_scnprintf(const struct btf_type *type, struct btf *btf,
}
static size_t trace__btf_scnprintf(struct trace *trace, struct syscall_arg *arg, char *bf,
- size_t size, int val, char *type)
+ size_t size, unsigned long val, char *type)
{
struct syscall_arg_fmt *arg_fmt = arg->fmt;
if (trace->btf == NULL)
return 0;
- if (arg_fmt->type == NULL) {
- // Check if this is an enum and if we have the BTF type for it.
- syscall_arg_fmt__cache_btf_enum(arg_fmt, trace->btf, type);
- }
+ if (!arg_fmt->btf_type_cached)
+ syscall_arg_fmt__cache_btf_type(arg_fmt, trace->btf, type);
// Did we manage to find a BTF type for the syscall/tracepoint argument?
if (arg_fmt->type == NULL)
return 0;
+ if (type && strchr(type, '*')) {
+ if (btf_is_enum(arg_fmt->type) || btf_is_func_ptr(trace->btf, arg_fmt->type))
+ return 0;
+ }
+
if (btf_is_enum(arg_fmt->type))
return btf_enum_scnprintf(arg_fmt->type, trace->btf, bf, size, val);
else if (btf_is_struct(arg_fmt->type) || btf_is_union(arg_fmt->type))
return btf_struct_scnprintf(arg_fmt->type, trace->btf, bf, size, arg);
+ else if (btf_is_func_ptr(trace->btf, arg_fmt->type)) {
+ arg->val = val;
+ return syscall_arg__scnprintf_ksym(bf, size, arg);
+ }
return 0;
}
#else // HAVE_LIBBPF_SUPPORT
static size_t trace__btf_scnprintf(struct trace *trace __maybe_unused, struct syscall_arg *arg __maybe_unused,
- char *bf __maybe_unused, size_t size __maybe_unused, int val __maybe_unused,
+ char *bf __maybe_unused, size_t size __maybe_unused, unsigned long val __maybe_unused,
char *type __maybe_unused)
{
return 0;
@@ -2606,7 +2669,8 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size,
default_scnprintf = sc->arg_fmt[arg.idx].scnprintf;
- if (trace->force_btf || default_scnprintf == NULL || default_scnprintf == SCA_PTR) {
+ if (trace->force_btf || default_scnprintf == NULL ||
+ default_scnprintf == SCA_PTR || default_scnprintf == SCA_KSYM) {
btf_printed = trace__btf_scnprintf(trace, &arg, bf + printed,
size - printed, val, field->type);
if (btf_printed) {
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH perf-tools-next v6 5/5] perf tests: Add shell test for kernel symbol beautifier
2026-08-24 13:31 [PATCH perf-tools-next v6 0/5] perf trace: Symbolise kernel virtual addresses and function pointers Aaron Tomlin
` (3 preceding siblings ...)
2026-08-24 13:31 ` [PATCH perf-tools-next v6 4/5] perf trace: Enhance BTF type formatting to symbolise kernel function pointers Aaron Tomlin
@ 2026-08-24 13:31 ` Aaron Tomlin
2026-08-24 16:30 ` [PATCH perf-tools-next v6 0/5] perf trace: Symbolise kernel virtual addresses and function pointers Ian Rogers
5 siblings, 0 replies; 7+ messages in thread
From: Aaron Tomlin @ 2026-08-24 13:31 UTC (permalink / raw)
To: peterz, mingo, acme, namhyung
Cc: mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter,
james.clark, howardchu95, atomlin, neelx, chjohnst, sean, steve,
rishil1999, linux-perf-users, linux-kernel
Add a dedicated shell test script, trace_ksym_beautifier.sh, to verify
that 'perf trace' properly symbolises kernel virtual addresses (e.g.
call_site in kmem:kmalloc) and function pointer fields (e.g. function in
timer:hrtimer_start) using the kernel symbol beautifier (SCA_KSYM).
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
.../perf/tests/shell/trace_ksym_beautifier.sh | 38 +++++++++++++++++++
1 file changed, 38 insertions(+)
create mode 100755 tools/perf/tests/shell/trace_ksym_beautifier.sh
diff --git a/tools/perf/tests/shell/trace_ksym_beautifier.sh b/tools/perf/tests/shell/trace_ksym_beautifier.sh
new file mode 100755
index 000000000000..7c441f8551a2
--- /dev/null
+++ b/tools/perf/tests/shell/trace_ksym_beautifier.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# perf trace kernel symbol beautifier tests
+# SPDX-License-Identifier: GPL-2.0
+
+err=0
+
+# shellcheck source=lib/probe.sh
+. "$(dirname "$0")"/lib/probe.sh
+skip_if_no_perf_trace || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+test_ksym_call_site() {
+ echo "Testing perf trace kernel symbol beautifier (call_site)"
+ output="$(perf trace -e kmem:kmalloc --max-events=1 -- true 2>&1)"
+ if ! echo "$output" | grep -q -E "call_site: [a-zA-Z_][a-zA-Z0-9_]*" || echo "$output" | grep -q -E "call_site: 0x[0-9a-fA-F]+"
+ then
+ printf "Call site kernel symbol beautification failed, output:\n%s\n" "$output"
+ err=1
+ fi
+}
+
+test_ksym_function_ptr() {
+ echo "Testing perf trace kernel symbol beautifier (function pointer)"
+ output="$(perf trace -e timer:hrtimer_start --max-events=1 -- sleep 0.01 2>&1)"
+ if ! echo "$output" | grep -q -E "function: [a-zA-Z_][a-zA-Z0-9_]*" || echo "$output" | grep -q -E "function: 0x[0-9a-fA-F]+"
+ then
+ printf "Function pointer kernel symbol beautification failed, output:\n%s\n" "$output"
+ err=1
+ fi
+}
+
+test_ksym_call_site
+
+if [ $err = 0 ]; then
+ test_ksym_function_ptr
+fi
+
+exit $err
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH perf-tools-next v6 0/5] perf trace: Symbolise kernel virtual addresses and function pointers
2026-08-24 13:31 [PATCH perf-tools-next v6 0/5] perf trace: Symbolise kernel virtual addresses and function pointers Aaron Tomlin
` (4 preceding siblings ...)
2026-08-24 13:31 ` [PATCH perf-tools-next v6 5/5] perf tests: Add shell test for kernel symbol beautifier Aaron Tomlin
@ 2026-08-24 16:30 ` Ian Rogers
5 siblings, 0 replies; 7+ messages in thread
From: Ian Rogers @ 2026-08-24 16:30 UTC (permalink / raw)
To: Aaron Tomlin
Cc: peterz, mingo, acme, namhyung, mark.rutland, alexander.shishkin,
jolsa, adrian.hunter, james.clark, howardchu95, neelx, chjohnst,
sean, steve, rishil1999, linux-perf-users, linux-kernel
On Mon, Aug 24, 2026 at 6:31 AM Aaron Tomlin <atomlin@atomlin.com> wrote:
>
> When inspecting kernel execution flows using perf trace (e.g., when
> monitoring workqueues, delayed work items, timer callbacks, etc.),
> tracepoint payload arguments containing raw kernel virtual addresses are
> currently rendered as hexadecimal values (e.g., 0xffffffff81234567).
>
> This requires manual symbol lookups against /proc/kallsyms or vmlinux to
> identify the underlying kernel function being executed.
>
> This patch series enhances perf trace by introducing kernel virtual address
> and function pointer symbolisation using perf's native symbol engine
> (i.e., machine__find_kernel_symbol()).
>
> Before:
> workqueue:workqueue_execute_end(work: 0xffffffffab2f1420, function: 0xffffffffa8046b50)
>
> After:
> workqueue:workqueue_execute_end(work: 0xffff8ac2c420f270, function: wb_update_bandwidth_workfn)
>
> Patch 1 fixes a pre-existing error handling issue in btf_struct_scnprintf()
> where negative error return codes from btf_dump__dump_type_data() bypassed
> the error check and erroneously advanced the augmented arguments buffer.
>
> Patch 2 introduces the syscall_arg__scnprintf_ksym() (SCA_KSYM) beautifier,
> which resolves virtual addresses via machine__find_kernel_symbol(),
> formatting them as symbol_name+offset (or "NULL", with a graceful
> hexadecimal fallback upon lookup failure).
>
> Patch 3 updates event format initialisation in
> syscall_arg_fmt__init_array() to automatically assign SCA_KSYM to non-array
> tracepoint fields typed as function pointers (such as typedefs ending in
> "_func_t" or "_fn", or function prototypes matching "(*)"). It also
> registers common function pointer, callback, and callsite field names
> (e.g., "action", "call_site", "callback", "caller", "caller_ip",
> "callsite", "cb", "fn", "func", "function", "handler", "location") in
> syscall_arg_fmts__by_name[]. To prevent misclassifying enums or 32-bit
> integers on 32-bit architectures, helper guards (field_is_ptr_sized(),
> field_is_enum(), and field_is_plain_int()) ensure SCA_KSYM is strictly
> assigned to pointer-sized fields and 64-bit scalars while allowing enums to
> fall through to BTF pretty-printing. It also removes the legacy
> trace__field_is_ip() hex override in trace__fprintf_tp_fields().
>
> Patch 4 extends BTF pretty-printing in trace__btf_scnprintf() with
> btf_is_func_ptr() to automatically traverse BTF type hierarchies (including
> nested typedefs and qualifiers) and route kernel function pointer arguments
> to SCA_KSYM when BTF metadata is available. It strictly enforces a single
> pointer indirection limit and validates type signatures against extra
> pointer asterisks to prevent multi-level pointers from being
> mis-symbolised.
>
> Patch 5 adds an automated regression test script, trace_ksym_beautifier.sh,
> to verify kernel symbol beautification across both virtual address fields
> and function pointer fields.
>
> Changes since v5:
>
> - Introduced patch to correct error-checking in btf_struct_scnprintf() to
> prevent erroneously advancing arg->augmented.args on negative error
> returns
>
> - Added helper functions field_is_enum(), field_is_plain_int(), and
> field_is_ptr_sized() in Patch 3 to guard SCA_KSYM assignment from
> colliding with 4-byte enum or integer fields on 32-bit architectures
> while supporting 64-bit scalar addresses (e.g., u64 caller_ip)
>
> - Updated btf_is_func_ptr() in Patch 4 to enforce a strict single-pointer
> limit (nr_ptrs == 1) with early termination on multi-level pointers,
> and added a type-string pointer check in trace__btf_scnprintf() to reject
> function pointer typedefs with extra pointer indirections
>
> - Removed test_ksym_btf() from trace_ksym_beautifier.sh in Patch 5,
> refocusing the test on verifying SCA_KSYM symbolisation across both
> virtual address and function pointer fields
>
> - Link to v5: https://lore.kernel.org/lkml/20260822213558.704018-1-atomlin@atomlin.com/
>
> Changes since v4:
>
> - Added a non-array guard (!(field->flags & TEP_FIELD_IS_ARRAY)) to the
> type-signature heuristic in syscall_arg_fmt__init_array(), preventing
> array fields matching "(*)", "_func_t", or "_fn" from erroneously
> receiving SCA_KSYM
>
> - Replaced host sizeof(void *) check with tep_get_long_size() to support
> cross-architecture analysis of 32-bit trace data on 64-bit hosts
>
> - Added deterministic workloads to trace_ksym_beautifier.sh to prevent
> indefinite hangs on quiescent systems
>
> - Replaced csd:csd_function_entry in test_ksym_btf() with
> timer:hrtimer_start to ensure test reliability on both SMP and
> uniprocessor (CONFIG_SMP=n) systems
>
> - Link to v4: https://lore.kernel.org/lkml/20260821204930.679027-1-atomlin@atomlin.com/
>
> Changes since v3:
>
> - Added "caller_ip" to syscall_arg_fmts__by_name[] in alphabetical order
> to symbolise instruction pointer fields
>
> - Guarded SCA_KSYM auto-assignment in syscall_arg_fmt__init_array() to
> only match actual pointer fields or pointer-sized non-array scalars,
> allowing unmatched fields to fall through to subsequent type checks
>
> - Fixed btf_struct_scnprintf() to only dump struct data for augmented
> input parameters, preventing un-augmented output pointers from dumping
> empty "{}" instead of falling back to hexadecimal pointer addresses.
> Also ensured btf_dump is properly freed on error exits
>
> - Updated test_ksym_btf() to trace csd:csd_function_entry with --force-btf
> instead of call_site, directly verifying function pointer symbolisation
>
> - Link to v3: https://lore.kernel.org/lkml/20260820211100.649142-1-atomlin@atomlin.com/
>
> Changes since v2:
>
> - Populated syscall_arg_fmts__by_name[] with common function pointer,
> callback, handler, and callsite field names ("action", "call_site",
> "callback", "caller", "callsite", "cb", "fn", "func", "function",
> "handler", "location")
>
> - Checked syscall_arg_fmt__find_by_name() prior to generic pointer
> fallbacks in syscall_arg_fmt__init_array(), and dropped the inline
> 64-bit size check to ensure 32-bit and cross-platform compatibility
>
> - Removed legacy trace__field_is_ip() in trace__fprintf_tp_fields() to
> allow "call_site" and "caller_ip" to be beautified with SCA_KSYM rather
> than being forced to raw hexadecimal
>
> - Simplified btf_is_func_ptr() to remove the internal pointer requirement,
> correctly identifying bare prototype typedefs as function pointers
>
> - Prevented pointer enums (e.g., "enum foo *") from being misclassified
> and formatted as scalar enum values in
> syscall_arg_fmt__cache_btf_type(), trace__btf_scnprintf(), and
> syscall_arg__strtoul_btf_type()
>
> - Added negative caching (btf_type_cached) to struct syscall_arg_fmt to
> avoid repeated BTF searches on every event for unresolvable or primitive
> types
>
> - Widened btf_enum_scnprintf() to accept unsigned long val, eliminating
> narrowing truncation of 64-bit values to 32-bit signed integers
>
> - Fixed trace_ksym_beautifier.sh:
> - Quoted "$0" to support directory paths containing spaces
> - Captured output in memory to eliminate temporary file leaks on early
> skip
> - Hardened regex validation to require valid C symbol identifiers and
> reject raw hexadecimal addresses
> - Used '%s' format specifiers in printf to prevent format string
> injection
>
> - Link to v2: https://lore.kernel.org/lkml/20260816205921.576365-1-atomlin@atomlin.com/
>
> Changes since v1:
>
> - Fixed reference leak of struct map in syscall_arg__scnprintf_ksym() by
> calling map__put() prior to returning Removed unreachable and erroneous
> entries ("action", "callsite", "call_site", "fn", "function", "work")
> from syscall_arg_fmts__by_name[]
>
> - Restricted name-based SCA_KSYM auto-assignment in
> syscall_arg_fmt__init_array() to pointer or 64-bit address fields,
> preventing misclassification of non-pointer integer fields
>
> - Updated btf_is_func_ptr() to fully unwrap typedefs and type modifiers
> below pointer targets
>
> - Fixed BTF type name matching in syscall_arg_fmt__cache_btf_type() to
> handle leading modifiers and strip trailing pointer asterisks before
> lookup
>
> - Synchronised arg->val with val in trace__btf_scnprintf() and widened val
> to unsigned long, fixing erroneous "NULL" output
>
> - Added shell test script,
> tools/perf/tests/shell/trace_ksym_beautifier.sh, to verify kernel symbol
> beautification for both default kallsyms and BTF routing
>
> - Link to v1: https://lore.kernel.org/lkml/20260815233651.527936-1-atomlin@atomlin.com/
>
> Aaron Tomlin (5):
> perf trace: Fix error checking in btf_struct_scnprintf()
> perf trace: Introduce kernel symbol beautifier for virtual addresses
> perf trace: Auto-assign kernel symbol beautifier to function pointer
> fields
> perf trace: Enhance BTF type formatting to symbolise kernel function
> pointers
> perf tests: Add shell test for kernel symbol beautifier
For the series:
Reviewed-by: Ian Rogers <irogers@google.com>
Tested-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> tools/perf/builtin-trace.c | 250 +++++++++++++-----
> .../perf/tests/shell/trace_ksym_beautifier.sh | 38 +++
> tools/perf/trace/beauty/beauty.h | 3 +
> 3 files changed, 232 insertions(+), 59 deletions(-)
> create mode 100755 tools/perf/tests/shell/trace_ksym_beautifier.sh
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 7+ messages in thread