* [PATCH perf-tools-next v5 1/4] perf trace: Introduce kernel symbol beautifier for virtual addresses
2026-08-22 21:35 [PATCH perf-tools-next v5 0/4] perf trace: Symbolise kernel virtual addresses and function pointers Aaron Tomlin
@ 2026-08-22 21:35 ` Aaron Tomlin
2026-08-22 21:45 ` sashiko-bot
2026-08-22 21:35 ` [PATCH perf-tools-next v5 2/4] perf trace: Auto-assign kernel symbol beautifier to function pointer fields Aaron Tomlin
` (2 subsequent siblings)
3 siblings, 1 reply; 12+ messages in thread
From: Aaron Tomlin @ 2026-08-22 21:35 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 c3c7f1f85c53..003048946503 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 related [flat|nested] 12+ messages in thread* Re: [PATCH perf-tools-next v5 1/4] perf trace: Introduce kernel symbol beautifier for virtual addresses
2026-08-22 21:35 ` [PATCH perf-tools-next v5 1/4] perf trace: Introduce kernel symbol beautifier for virtual addresses Aaron Tomlin
@ 2026-08-22 21:45 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-08-22 21:45 UTC (permalink / raw)
To: Aaron Tomlin; +Cc: linux-perf-users
> 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>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822213558.704018-1-atomlin@atomlin.com?part=1
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH perf-tools-next v5 2/4] perf trace: Auto-assign kernel symbol beautifier to function pointer fields
2026-08-22 21:35 [PATCH perf-tools-next v5 0/4] perf trace: Symbolise kernel virtual addresses and function pointers Aaron Tomlin
2026-08-22 21:35 ` [PATCH perf-tools-next v5 1/4] perf trace: Introduce kernel symbol beautifier for virtual addresses Aaron Tomlin
@ 2026-08-22 21:35 ` Aaron Tomlin
2026-08-22 21:48 ` sashiko-bot
2026-08-22 21:35 ` [PATCH perf-tools-next v5 3/4] perf trace: Enhance BTF type formatting to symbolise kernel function pointers Aaron Tomlin
2026-08-22 21:35 ` [PATCH perf-tools-next v5 4/4] perf tests: Add shell test for kernel symbol beautifier Aaron Tomlin
3 siblings, 1 reply; 12+ messages in thread
From: Aaron Tomlin @ 2026-08-22 21:35 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 or non-pointer integers sharing
generic names, guard SCA_KSYM assignment to pointer fields and
pointer-sized non-array scalars. To support cross-architecture analysis
(e.g., analyzing 32-bit trace data on a 64-bit host), determine the
target pointer size via tep_get_long_size().
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 | 93 ++++++++++++++++++++++----------------
1 file changed, 55 insertions(+), 38 deletions(-)
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index 003048946503..10fbea7ed4db 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -2096,6 +2096,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, },
};
@@ -2198,38 +2210,52 @@ 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) {
+ int ptr_size = (field->event && field->event->tep) ?
+ tep_get_long_size(field->event->tep) :
+ (int)sizeof(void *);
+
+ if ((field->flags & TEP_FIELD_IS_POINTER) ||
+ (field->size == ptr_size && !(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 (strstr(field->type, "enum") && use_btf != NULL) {
+ *use_btf = true;
+ arg->strtoul = STUL_BTF_TYPE;
+ }
}
}
}
@@ -3297,12 +3323,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)
@@ -3404,14 +3424,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 related [flat|nested] 12+ messages in thread* Re: [PATCH perf-tools-next v5 2/4] perf trace: Auto-assign kernel symbol beautifier to function pointer fields
2026-08-22 21:35 ` [PATCH perf-tools-next v5 2/4] perf trace: Auto-assign kernel symbol beautifier to function pointer fields Aaron Tomlin
@ 2026-08-22 21:48 ` sashiko-bot
2026-08-23 22:28 ` Aaron Tomlin
0 siblings, 1 reply; 12+ messages in thread
From: sashiko-bot @ 2026-08-22 21:48 UTC (permalink / raw)
To: Aaron Tomlin; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Name-based lookup precedence inversion and flawed pointer size heuristic incorrectly classify non-pointer integers and enums as kernel symbols.
- [Medium] Removal of `trace__field_is_ip()` causes oversized IP fields to be formatted as signed base-10 integers.
--
--- Patch [2]: [PATCH perf-tools-next v5 2/4] perf trace: Auto-assign kernel symbol beautifier to function pointer fields ---
commit 7d955fc9994a613ba6129e3ae693c4d493643489
Author: Aaron Tomlin <atomlin@atomlin.com>
perf trace: Auto-assign kernel symbol beautifier to function pointer fields
This commit enhances perf trace to automatically assign the SCA_KSYM
beautifier to tracepoint fields conveying function pointers and call sites.
It identifies these fields by their type signature and via a lookup table of
common generic field names.
> diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
> --- a/tools/perf/builtin-trace.c
> +++ b/tools/perf/builtin-trace.c
[ ... ]
> @@ -2198,38 +2210,52 @@ syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field
[ ... ]
> } 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) {
> + int ptr_size = (field->event && field->event->tep) ?
> + tep_get_long_size(field->event->tep) :
> + (int)sizeof(void *);
> +
> + if ((field->flags & TEP_FIELD_IS_POINTER) ||
> + (field->size == ptr_size && !(field->flags & TEP_FIELD_IS_ARRAY))) {
[Severity: Medium]
Will this size heuristic mistakenly match non-pointer integer and enum fields?
If a tracepoint has a generic-named field like action, location, or handler
that is actually an integer or enum (e.g. 4 bytes), and the architecture's
pointer size is also 4 bytes, this check passes and assigns SCA_KSYM.
If SCA_KSYM is assigned here, the enum BTF resolution logic located in the
subsequent fallback block is entirely skipped:
if (arg->scnprintf == NULL) {
...
} else if (strstr(field->type, "enum") && use_btf != NULL) {
*use_btf = true;
arg->strtoul = STUL_BTF_TYPE;
}
Could this cause small integer or enum values to be printed as meaningless
hex kernel addresses or NULL instead of their proper symbolic enum names?
> + arg->scnprintf = fmt->scnprintf;
> + arg->strtoul = fmt->strtoul;
> + }
[ ... ]
> @@ -3404,14 +3424,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;
[Severity: Medium]
Does removing trace__field_is_ip() cause oversized IP fields to be formatted as
signed base-10 integers?
By relying entirely on the SCA_KSYM assignment in syscall_arg_fmt__init_array(),
we depend on the strict size check:
if ((field->flags & TEP_FIELD_IS_POINTER) ||
(field->size == ptr_size && !(field->flags & TEP_FIELD_IS_ARRAY))) {
If a tracepoint defines an IP field like caller_ip or call_site as a fixed
64-bit integer, but the trace data is processed as 32-bit (where ptr_size
is 4), the size check fails. The field receives no beautifier since
arg->scnprintf will be NULL.
Without the trace__field_is_ip() fallback, won't the default formatter in
syscall_arg_fmt__scnprintf_val() print the raw kernel address as a signed
decimal integer instead of a hex pointer?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822213558.704018-1-atomlin@atomlin.com?part=2
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH perf-tools-next v5 2/4] perf trace: Auto-assign kernel symbol beautifier to function pointer fields
2026-08-22 21:48 ` sashiko-bot
@ 2026-08-23 22:28 ` Aaron Tomlin
0 siblings, 0 replies; 12+ messages in thread
From: Aaron Tomlin @ 2026-08-23 22:28 UTC (permalink / raw)
To: sashiko-reviews, namhyung, irogers, acme; +Cc: linux-perf-users
On Sat, Aug 22, 2026 at 09:48:48PM +0000, sashiko-bot@kernel.org wrote:
> --- Patch [2]: [PATCH perf-tools-next v5 2/4] perf trace: Auto-assign kernel symbol beautifier to function pointer fields ---
> commit 7d955fc9994a613ba6129e3ae693c4d493643489
> Author: Aaron Tomlin <atomlin@atomlin.com>
>
> perf trace: Auto-assign kernel symbol beautifier to function pointer fields
>
> This commit enhances perf trace to automatically assign the SCA_KSYM
> beautifier to tracepoint fields conveying function pointers and call sites.
> It identifies these fields by their type signature and via a lookup table of
> common generic field names.
>
> > diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
> > --- a/tools/perf/builtin-trace.c
> > +++ b/tools/perf/builtin-trace.c
> [ ... ]
> > @@ -2198,38 +2210,52 @@ syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field
> [ ... ]
> > } 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) {
> > + int ptr_size = (field->event && field->event->tep) ?
> > + tep_get_long_size(field->event->tep) :
> > + (int)sizeof(void *);
> > +
> > + if ((field->flags & TEP_FIELD_IS_POINTER) ||
> > + (field->size == ptr_size && !(field->flags & TEP_FIELD_IS_ARRAY))) {
>
> [Severity: Medium]
> Will this size heuristic mistakenly match non-pointer integer and enum fields?
>
> If a tracepoint has a generic-named field like action, location, or handler
> that is actually an integer or enum (e.g. 4 bytes), and the architecture's
> pointer size is also 4 bytes, this check passes and assigns SCA_KSYM.
>
> If SCA_KSYM is assigned here, the enum BTF resolution logic located in the
> subsequent fallback block is entirely skipped:
>
> if (arg->scnprintf == NULL) {
> ...
> } else if (strstr(field->type, "enum") && use_btf != NULL) {
> *use_btf = true;
> arg->strtoul = STUL_BTF_TYPE;
> }
>
> Could this cause small integer or enum values to be printed as meaningless
> hex kernel addresses or NULL instead of their proper symbolic enum names?
So, some kernel subsystems (e.g., XFS), tracepoints define instruction
pointers as 'unsigned long caller_ip'; instead of 'void *caller_ip'. For
instance, xfs_freeblocks_resv_class:
- File: fs/xfs/xfs_trace.h
#define DEFINE_FREEBLOCKS_RESV_EVENT(name) \
DEFINE_EVENT(xfs_freeblocks_resv_class, name, \
TP_PROTO(struct xfs_mount *mp, enum xfs_free_counter ctr, \
uint64_t delta, unsigned long caller_ip), \
TP_ARGS(mp, ctr, delta, caller_ip))
Consequently, since there is no '*', libtraceevent does not set
TEP_FIELD_IS_POINTER. Earlier, I proposed the use of tep_get_long_size() to
support cross-platform analysis (i.e., 32-bit traces work on 64-bit hosts).
Unfortunately, a known entry in syscall_arg_fmts__by_name[], (e.g.,
"action") now falsely matches SCA_KSYM and skips BTF enum resolution.
Perhaps a combined solution:
} else {
const struct syscall_arg_fmt *fmt =
syscall_arg_fmt__find_by_name(field->name);
if (fmt) {
if (fmt->scnprintf == SCA_KSYM) {
int ptr_size = tep_get_long_size(field->event->tep);
if ((field->flags & TEP_FIELD_IS_POINTER) ||
(!field_is_enum(field) && !field_is_plain_int(field) &&
field->size == ptr_size && !(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;
}
}
}
>
> > + arg->scnprintf = fmt->scnprintf;
> > + arg->strtoul = fmt->strtoul;
> > + }
> [ ... ]
> > @@ -3404,14 +3424,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;
>
> [Severity: Medium]
> Does removing trace__field_is_ip() cause oversized IP fields to be formatted as
> signed base-10 integers?
>
> By relying entirely on the SCA_KSYM assignment in syscall_arg_fmt__init_array(),
> we depend on the strict size check:
>
> if ((field->flags & TEP_FIELD_IS_POINTER) ||
> (field->size == ptr_size && !(field->flags & TEP_FIELD_IS_ARRAY))) {
>
> If a tracepoint defines an IP field like caller_ip or call_site as a fixed
> 64-bit integer, but the trace data is processed as 32-bit (where ptr_size
> is 4), the size check fails. The field receives no beautifier since
> arg->scnprintf will be NULL.
>
> Without the trace__field_is_ip() fallback, won't the default formatter in
> syscall_arg_fmt__scnprintf_val() print the raw kernel address as a signed
> decimal integer instead of a hex pointer?
Wait, currently there are zero tracepoints defining caller_ip as u64 or __u64.
Anyhow, perhaps the following?
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);
}
And guarded SCA_KSYM assignment in syscall_arg_fmt__init_array():
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;
}
--
Aaron Tomlin
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH perf-tools-next v5 3/4] perf trace: Enhance BTF type formatting to symbolise kernel function pointers
2026-08-22 21:35 [PATCH perf-tools-next v5 0/4] perf trace: Symbolise kernel virtual addresses and function pointers Aaron Tomlin
2026-08-22 21:35 ` [PATCH perf-tools-next v5 1/4] perf trace: Introduce kernel symbol beautifier for virtual addresses Aaron Tomlin
2026-08-22 21:35 ` [PATCH perf-tools-next v5 2/4] perf trace: Auto-assign kernel symbol beautifier to function pointer fields Aaron Tomlin
@ 2026-08-22 21:35 ` Aaron Tomlin
2026-08-22 21:51 ` sashiko-bot
2026-08-22 21:35 ` [PATCH perf-tools-next v5 4/4] perf tests: Add shell test for kernel symbol beautifier Aaron Tomlin
3 siblings, 1 reply; 12+ messages in thread
From: Aaron Tomlin @ 2026-08-22 21:35 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 | 100 ++++++++++++++++++++++++++++---------
1 file changed, 77 insertions(+), 23 deletions(-)
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index 10fbea7ed4db..4f8b4f2cf677 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,61 @@ 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)
{
+ while (type) {
+ if (btf_is_ptr(type) || btf_is_typedef(type) || btf_is_mod(type))
+ type = btf__type_by_id(btf, type->type);
+ else
+ break;
+ }
+ return 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';
- id = btf__find_by_name(btf, type);
+ if (len == 0)
+ return;
+
+ 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 +1068,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 +1077,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 +1119,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;
@@ -1095,8 +1142,10 @@ 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, augmented_arg->value, 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;
@@ -1108,33 +1157,37 @@ 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 (btf_is_enum(arg_fmt->type))
+ if (btf_is_enum(arg_fmt->type)) {
+ if (type && strchr(type, '*'))
+ return 0;
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))
+ } 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;
@@ -2584,7 +2637,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 related [flat|nested] 12+ messages in thread* Re: [PATCH perf-tools-next v5 3/4] perf trace: Enhance BTF type formatting to symbolise kernel function pointers
2026-08-22 21:35 ` [PATCH perf-tools-next v5 3/4] perf trace: Enhance BTF type formatting to symbolise kernel function pointers Aaron Tomlin
@ 2026-08-22 21:51 ` sashiko-bot
2026-08-24 10:02 ` Aaron Tomlin
0 siblings, 1 reply; 12+ messages in thread
From: sashiko-bot @ 2026-08-22 21:51 UTC (permalink / raw)
To: Aaron Tomlin; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] Pointers to function pointers (and deeper pointer indirections) will be incorrectly symbolized as kernel functions due to greedy unwrapping in `btf_is_func_ptr()` and missing pointer checks in `trace__btf_scnprintf()`.
Pre-existing issues:
- [Medium] Incorrect error handling in `btf_struct_scnprintf` causes `arg->augmented.args` to be inappropriately advanced on failure, leading to an out-of-bounds read in fallback formatters.
--
--- Patch [3]: [PATCH perf-tools-next v5 3/4] perf trace: Enhance BTF type formatting to symbolise kernel function pointers ---
commit 544922c709767641e98cb8ac1eabf4bff305afea
Author: Aaron Tomlin <atomlin@atomlin.com>
perf trace: Enhance BTF type formatting to symbolise kernel function pointers
When BTF (BPF Type Format) metadata is loaded from vmlinux, 'perf trace'
can inspect the precise C types of tracepoint and system call parameters.
This patch introduces btf_is_func_ptr() to inspect BTF type hierarchies
to determine whether a parameter resolves to a function prototype.
> diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
> index 10fbea7ed4dbf..4f8b4f2cf677e 100644
> --- a/tools/perf/builtin-trace.c
> +++ b/tools/perf/builtin-trace.c
[ ... ]
> @@ -979,21 +980,61 @@ 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)
> {
> + while (type) {
> + if (btf_is_ptr(type) || btf_is_typedef(type) || btf_is_mod(type))
> + type = btf__type_by_id(btf, type->type);
> + else
> + break;
> + }
> + return type && btf_is_func_proto(type);
> +}
[Severity: Medium]
Will this loop eagerly unwrap all pointer modifiers, failing to enforce a
strict single-pointer limit for function pointers?
If perf trace encounters a tracepoint or syscall argument whose type
string contains an asterisk but resolves to a function pointer typedef
(e.g. bpf_callback_t *), wouldn't this greedily unwrap all pointer
indirections, causing pointers to function pointers to be incorrectly
symbolized as kernel functions?
[ ... ]
> @@ -1095,8 +1142,10 @@ 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, augmented_arg->value, 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;
[Severity: Medium]
This is a pre-existing issue, but does this error handling correctly check
for negative error codes from btf_dump__dump_type_data()?
If btf_dump__dump_type_data() returns a negative error code (e.g., -EINVAL),
the equality check against 0 evaluates to false. The function incorrectly
proceeds to advance the arg->augmented.args pointer by the consumed struct
size and returns 0.
Then, in syscall__scnprintf_args():
tools/perf/builtin-trace.c:syscall__scnprintf_args() {
...
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) {
printed += btf_printed;
continue;
}
}
printed += syscall_arg_fmt__scnprintf_val(&sc->arg_fmt[arg.idx],
bf + printed, size - printed, &arg, val);
...
}
Because btf_printed is 0, it falls back to the default formatter
(syscall_arg_fmt__scnprintf_val). Does this fallback formatter then read from
the erroneously advanced arg->augmented.args pointer (now pointing to the
next argument or past the buffer end), leading to an out-of-bounds memory
access?
[ ... ]
> @@ -1108,33 +1157,37 @@ 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 (btf_is_enum(arg_fmt->type))
> + if (btf_is_enum(arg_fmt->type)) {
> + if (type && strchr(type, '*'))
> + return 0;
> 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))
> + } 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;
> }
[Severity: Medium]
Does this branch lack a safeguard for function pointers similar to the enum
check above?
Unlike the strchr(type, '*') check for enums, there is no verification that
the original C type string lacks an extra pointer asterisk. Since
btf_is_func_ptr() greedily unwraps pointer indirections, could a pointer to a
function pointer be mistakenly routed to syscall_arg__scnprintf_ksym(),
resulting in mis-symbolization of the memory address instead of the kernel
symbol?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822213558.704018-1-atomlin@atomlin.com?part=3
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH perf-tools-next v5 3/4] perf trace: Enhance BTF type formatting to symbolise kernel function pointers
2026-08-22 21:51 ` sashiko-bot
@ 2026-08-24 10:02 ` Aaron Tomlin
0 siblings, 0 replies; 12+ messages in thread
From: Aaron Tomlin @ 2026-08-24 10:02 UTC (permalink / raw)
To: sashiko-reviews, namhyung, irogers, acme; +Cc: linux-perf-users
On Sat, Aug 22, 2026 at 09:51:23PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
>
> New issues:
> - [Medium] Pointers to function pointers (and deeper pointer indirections) will be incorrectly symbolized as kernel functions due to greedy unwrapping in `btf_is_func_ptr()` and missing pointer checks in `trace__btf_scnprintf()`.
>
> Pre-existing issues:
> - [Medium] Incorrect error handling in `btf_struct_scnprintf` causes `arg->augmented.args` to be inappropriately advanced on failure, leading to an out-of-bounds read in fallback formatters.
> --
>
> --- Patch [3]: [PATCH perf-tools-next v5 3/4] perf trace: Enhance BTF type formatting to symbolise kernel function pointers ---
> commit 544922c709767641e98cb8ac1eabf4bff305afea
> Author: Aaron Tomlin <atomlin@atomlin.com>
>
> perf trace: Enhance BTF type formatting to symbolise kernel function pointers
>
> When BTF (BPF Type Format) metadata is loaded from vmlinux, 'perf trace'
> can inspect the precise C types of tracepoint and system call parameters.
> This patch introduces btf_is_func_ptr() to inspect BTF type hierarchies
> to determine whether a parameter resolves to a function prototype.
>
> > diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
> > index 10fbea7ed4dbf..4f8b4f2cf677e 100644
> > --- a/tools/perf/builtin-trace.c
> > +++ b/tools/perf/builtin-trace.c
>
> [ ... ]
>
> > @@ -979,21 +980,61 @@ 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)
> > {
> > + while (type) {
> > + if (btf_is_ptr(type) || btf_is_typedef(type) || btf_is_mod(type))
> > + type = btf__type_by_id(btf, type->type);
> > + else
> > + break;
> > + }
> > + return type && btf_is_func_proto(type);
> > +}
>
> [Severity: Medium]
> Will this loop eagerly unwrap all pointer modifiers, failing to enforce a
> strict single-pointer limit for function pointers?
>
> If perf trace encounters a tracepoint or syscall argument whose type
> string contains an asterisk but resolves to a function pointer typedef
> (e.g. bpf_callback_t *), wouldn't this greedily unwrap all pointer
> indirections, causing pointers to function pointers to be incorrectly
> symbolized as kernel functions?
At the time of writing, no tracepoint or syscall argument in the Linux
kernel source tree that uses bpf_callback_t * (or any other function
pointer typedef with an extra asterisk like work_func_t *).
However, in the next iteration, function btf_is_func_ptr(), will track the
pointer count and immediately exit if more than one pointer is encountered,
avoiding any further BTF type lookups.
>
> [ ... ]
>
> > @@ -1095,8 +1142,10 @@ 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, augmented_arg->value, 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;
>
> [Severity: Medium]
> This is a pre-existing issue, but does this error handling correctly check
> for negative error codes from btf_dump__dump_type_data()?
>
> If btf_dump__dump_type_data() returns a negative error code (e.g., -EINVAL),
> the equality check against 0 evaluates to false. The function incorrectly
> proceeds to advance the arg->augmented.args pointer by the consumed struct
> size and returns 0.
>
> Then, in syscall__scnprintf_args():
>
> tools/perf/builtin-trace.c:syscall__scnprintf_args() {
> ...
> 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) {
> printed += btf_printed;
> continue;
> }
> }
>
> printed += syscall_arg_fmt__scnprintf_val(&sc->arg_fmt[arg.idx],
> bf + printed, size - printed, &arg, val);
> ...
> }
>
> Because btf_printed is 0, it falls back to the default formatter
> (syscall_arg_fmt__scnprintf_val). Does this fallback formatter then read from
> the erroneously advanced arg->augmented.args pointer (now pointing to the
> next argument or past the buffer end), leading to an out-of-bounds memory
> access?
>
Okay, then:
if (btf_dump__dump_type_data(btf_dump, type_id, augmented_arg->value,
type->size, &dump_data_opts) <= 0) {
btf_dump__free(btf_dump);
return 0;
}
--
Aaron Tomlin
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH perf-tools-next v5 4/4] perf tests: Add shell test for kernel symbol beautifier
2026-08-22 21:35 [PATCH perf-tools-next v5 0/4] perf trace: Symbolise kernel virtual addresses and function pointers Aaron Tomlin
` (2 preceding siblings ...)
2026-08-22 21:35 ` [PATCH perf-tools-next v5 3/4] perf trace: Enhance BTF type formatting to symbolise kernel function pointers Aaron Tomlin
@ 2026-08-22 21:35 ` Aaron Tomlin
2026-08-22 21:48 ` sashiko-bot
3 siblings, 1 reply; 12+ messages in thread
From: Aaron Tomlin @ 2026-08-22 21:35 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 and
function pointers using both the default kallsyms beautifier (SCA_KSYM)
and BTF type routing i.e., --force-btf.
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
.../perf/tests/shell/trace_ksym_beautifier.sh | 43 +++++++++++++++++++
1 file changed, 43 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..45c803338d4c
--- /dev/null
+++ b/tools/perf/tests/shell/trace_ksym_beautifier.sh
@@ -0,0 +1,43 @@
+#!/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_kallsyms() {
+ echo "Testing perf trace kernel symbol beautifier (default kallsyms)"
+ 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 "Default kallsyms function symbolization failed, output:\n%s\n" "$output"
+ err=1
+ fi
+}
+
+test_ksym_btf() {
+ echo "Testing perf trace kernel symbol beautifier (BTF)"
+ if [ ! -f /sys/kernel/btf/vmlinux ]; then
+ echo "Skipping BTF test due to missing vmlinux BTF"
+ return
+ fi
+
+ output="$(perf trace -e timer:hrtimer_start --force-btf --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 "BTF function symbolization failed, output:\n%s\n" "$output"
+ err=1
+ fi
+}
+
+test_ksym_kallsyms
+
+if [ $err = 0 ]; then
+ test_ksym_btf
+fi
+
+exit $err
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH perf-tools-next v5 4/4] perf tests: Add shell test for kernel symbol beautifier
2026-08-22 21:35 ` [PATCH perf-tools-next v5 4/4] perf tests: Add shell test for kernel symbol beautifier Aaron Tomlin
@ 2026-08-22 21:48 ` sashiko-bot
2026-08-24 11:28 ` Aaron Tomlin
0 siblings, 1 reply; 12+ messages in thread
From: sashiko-bot @ 2026-08-22 21:48 UTC (permalink / raw)
To: Aaron Tomlin; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The test script `trace_ksym_beautifier.sh` intends to verify BTF type routing for function pointers using the `timer:hrtimer_start` tracepoint, but this tracepoint's `function` field is of type `void *`, which bypasses the BTF function pointer routing logic. The test only passes due to a hardcoded fallback for arguments named 'function'.
--
--- Patch [4]: [PATCH perf-tools-next v5 4/4] perf tests: Add shell test for kernel symbol beautifier ---
commit e969790d116081081634b0e3ece5c71834b3fe8f
Author: Aaron Tomlin <atomlin@atomlin.com>
perf tests: Add shell test for kernel symbol beautifier
This commit introduces a shell test script to verify that perf trace properly
symbolizes kernel virtual addresses and function pointers. It checks both
the default kallsyms beautifier and BTF type routing.
> 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 0000000000000..45c803338d4ca
> --- /dev/null
> +++ b/tools/perf/tests/shell/trace_ksym_beautifier.sh
[ ... ]
> +test_ksym_btf() {
> + echo "Testing perf trace kernel symbol beautifier (BTF)"
> + if [ ! -f /sys/kernel/btf/vmlinux ]; then
> + echo "Skipping BTF test due to missing vmlinux BTF"
> + return
> + fi
> +
> + output="$(perf trace -e timer:hrtimer_start --force-btf --max-events=1 -- sleep 0.01 2>&1)"
[Severity: Medium]
Does this test actually verify BTF type routing?
For the timer:hrtimer_start tracepoint, the function field is declared as
a void pointer in both BTF and tracefs. This means btf_is_func_ptr()
will evaluate to false, and trace__btf_scnprintf() will return 0.
The output is successfully symbolized here only because builtin-trace.c
contains a hardcoded fallback array, syscall_arg_fmts__by_name, that maps
any field named function to SCA_KSYM.
Could a different tracepoint be used here that has an actual function
pointer type in BTF, to ensure the BTF type routing logic is being properly
exercised instead of the hardcoded fallback?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822213558.704018-1-atomlin@atomlin.com?part=4
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH perf-tools-next v5 4/4] perf tests: Add shell test for kernel symbol beautifier
2026-08-22 21:48 ` sashiko-bot
@ 2026-08-24 11:28 ` Aaron Tomlin
0 siblings, 0 replies; 12+ messages in thread
From: Aaron Tomlin @ 2026-08-24 11:28 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-perf-users
On Sat, Aug 22, 2026 at 09:48:02PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The test script `trace_ksym_beautifier.sh` intends to verify BTF type routing for function pointers using the `timer:hrtimer_start` tracepoint, but this tracepoint's `function` field is of type `void *`, which bypasses the BTF function pointer routing logic. The test only passes due to a hardcoded fallback for arguments named 'function'.
> --
>
> --- Patch [4]: [PATCH perf-tools-next v5 4/4] perf tests: Add shell test for kernel symbol beautifier ---
> commit e969790d116081081634b0e3ece5c71834b3fe8f
> Author: Aaron Tomlin <atomlin@atomlin.com>
>
> perf tests: Add shell test for kernel symbol beautifier
>
> This commit introduces a shell test script to verify that perf trace properly
> symbolizes kernel virtual addresses and function pointers. It checks both
> the default kallsyms beautifier and BTF type routing.
>
> > 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 0000000000000..45c803338d4ca
> > --- /dev/null
> > +++ b/tools/perf/tests/shell/trace_ksym_beautifier.sh
>
> [ ... ]
>
> > +test_ksym_btf() {
> > + echo "Testing perf trace kernel symbol beautifier (BTF)"
> > + if [ ! -f /sys/kernel/btf/vmlinux ]; then
> > + echo "Skipping BTF test due to missing vmlinux BTF"
> > + return
> > + fi
> > +
> > + output="$(perf trace -e timer:hrtimer_start --force-btf --max-events=1 -- sleep 0.01 2>&1)"
>
> [Severity: Medium]
> Does this test actually verify BTF type routing?
>
> For the timer:hrtimer_start tracepoint, the function field is declared as
> a void pointer in both BTF and tracefs. This means btf_is_func_ptr()
> will evaluate to false, and trace__btf_scnprintf() will return 0.
>
> The output is successfully symbolized here only because builtin-trace.c
> contains a hardcoded fallback array, syscall_arg_fmts__by_name, that maps
> any field named function to SCA_KSYM.
>
> Could a different tracepoint be used here that has an actual function
> pointer type in BTF, to ensure the BTF type routing logic is being properly
> exercised instead of the hardcoded fallback?
Let's drop test_ksym_btf(). We already have trace_btf_general.sh as the
dedicated BTF test suite, and it tests btf_is_struct() via clock_nanosleep.
Kind regards,
--
Aaron Tomlin
^ permalink raw reply [flat|nested] 12+ messages in thread