tracing/probes: Add a new testcase for BTF typecasts

With the introduction of container_of-style BTF typecasting and
per-CPU variable access support in trace probes, we need a way to
verify their functionality and prevent regressions.

Add a new ftrace kselftest and update the trace event sample module
to test and validate these features.

Specifically, update the trace-events-sample module to set up a
periodic timer whose callback accesses a per-CPU counter. Introduce
a new sample trace event, foo_timer_fn, to trace this callback
and log the current counter value.

Then, add a new test case, btf_probe_event.tc, which defines a
dynamic probe on the timer callback. The probe uses BTF typecasting
to recover the parent structure from the timer argument and
this_cpu_read() to fetch the per-CPU counter. The test verifies
the integrity of the implementation by ensuring the values
recorded by the dynamic probe match those from the static tracepoint.

Link: https://lore.kernel.org/all/178271368625.1176915.13502654445483530822.stgit@devnote2/

Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
This commit is contained in:
Masami Hiramatsu (Google)
2026-07-14 22:43:16 +09:00
parent 49b0a3f605
commit 7d8d6ad659
8 changed files with 261 additions and 5 deletions
+38 -2
View File
@@ -94,6 +94,20 @@ static int simple_thread_fn(void *arg)
static DEFINE_MUTEX(thread_mutex);
static int simple_thread_cnt;
static struct foo_timer_data *foo_timer_data;
static void sample_timer_cb(struct timer_list *t)
{
struct foo_timer_data *data = container_of(t, struct foo_timer_data, timer);
get_cpu();
trace_foo_timer_fn(data);
(*this_cpu_ptr(data->counter))++;
put_cpu();
mod_timer(t, jiffies + HZ);
}
int foo_bar_reg(void)
{
mutex_lock(&thread_mutex);
@@ -132,9 +146,27 @@ void foo_bar_unreg(void)
static int __init trace_event_init(void)
{
foo_timer_data = kzalloc_obj(*foo_timer_data, GFP_KERNEL);
if (!foo_timer_data)
return -ENOMEM;
foo_timer_data->name = "sample_timer_counter";
foo_timer_data->counter = alloc_percpu(int);
if (!foo_timer_data->counter) {
kfree(foo_timer_data);
return -ENOMEM;
}
timer_setup(&foo_timer_data->timer, sample_timer_cb, 0);
mod_timer(&foo_timer_data->timer, jiffies + HZ);
simple_tsk = kthread_run(simple_thread, NULL, "event-sample");
if (IS_ERR(simple_tsk))
return -1;
if (IS_ERR(simple_tsk)) {
timer_shutdown_sync(&foo_timer_data->timer);
free_percpu(foo_timer_data->counter);
kfree(foo_timer_data);
return PTR_ERR(simple_tsk);
}
return 0;
}
@@ -147,6 +179,10 @@ static void __exit trace_event_exit(void)
kthread_stop(simple_tsk_fn);
simple_tsk_fn = NULL;
mutex_unlock(&thread_mutex);
timer_shutdown_sync(&foo_timer_data->timer);
free_percpu(foo_timer_data->counter);
kfree(foo_timer_data);
}
module_init(trace_event_init);
+31 -3
View File
@@ -247,12 +247,14 @@
*/
/*
* It is OK to have helper functions in the file, but they need to be protected
* from being defined more than once. Remember, this file gets included more
* than once.
* It is OK to have helper functions and data structures in the file, but they
* need to be protected from being defined more than once. Remember, this file
* gets included more than once.
*/
#ifndef __TRACE_EVENT_SAMPLE_HELPER_FUNCTIONS
#define __TRACE_EVENT_SAMPLE_HELPER_FUNCTIONS
#include <linux/timer.h>
static inline int __length_of(const int *list)
{
int i;
@@ -270,6 +272,13 @@ enum {
TRACE_SAMPLE_BAR = 4,
TRACE_SAMPLE_ZOO = 8,
};
struct foo_timer_data {
const char *name;
struct timer_list timer;
int __percpu *counter;
};
#endif
/*
@@ -595,6 +604,25 @@ TRACE_EVENT(foo_rel_loc,
__get_rel_bitmask(bitmask),
__get_rel_cpumask(cpumask))
);
TRACE_EVENT(foo_timer_fn,
TP_PROTO(struct foo_timer_data *data),
TP_ARGS(data),
TP_STRUCT__entry(
__string( name, data->name )
__field( int, count )
),
TP_fast_assign(
__assign_str(name);
__entry->count = *this_cpu_ptr(data->counter);
),
TP_printk("name=%s count=%d", __get_str(name), __entry->count)
);
#endif
/***** NOTICE! The #if protection ends here. *****/
@@ -0,0 +1,51 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# description: BTF event with typecast and percpu access
# requires: dynamic_events "this_cpu_read(<fetcharg>)":README "[(structname[,field])]<argname>[->field[->field|.field...]]":README
# Check if the sample module is loaded
if ! lsmod | grep -q trace_events_sample; then
modprobe trace-events-sample || exit_unresolved
fi
echo 0 > events/enable
echo > dynamic_events
# The sample_timer_cb(struct timer_list *t) is called.
# We want to check (STRUCT,FIELD)VAR typecast and this_cpu_read() access.
# (foo_timer_data,timer)t converts t to struct foo_timer_data * using container_of.
# data->counter is a per-cpu pointer to int.
# this_cpu_read(data->counter) should give the value of the counter.
echo 'f:mysample/myevent sample_timer_cb name=(foo_timer_data,timer)t->name:string count=this_cpu_read((foo_timer_data,timer)t->counter)' >> dynamic_events
echo 1 > events/mysample/myevent/enable
echo 1 > events/sample-trace/foo_timer_fn/enable
sleep 2
echo 0 > events/mysample/myevent/enable
echo 0 > events/sample-trace/foo_timer_fn/enable
# Compare the values.
MATCH=0
while read line; do
if echo $line | grep -q "foo_timer_fn:"; then
NAME=`echo $line | sed 's/.*name=\([^ ]*\) .*/\1/'`
COUNT=`echo $line | sed 's/.*count=\([^ ]*\).*/\1/'`
if grep -q "myevent:.*name=\"${NAME}\" count=$COUNT" trace; then
MATCH=$((MATCH+1))
fi
fi
done < trace
if [ $MATCH -eq 0 ]; then
echo "No matching events found"
exit_fail
fi
# Clean up
echo 0 > events/mysample/myevent/enable
echo 0 > events/sample-trace/foo_timer_fn/enable
echo > dynamic_events
clear_trace
@@ -0,0 +1,103 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# description: BTF typecast and percpu access syntax validation
# requires: dynamic_events "this_cpu_read(<fetcharg>)":README "[(structname[,field])]<argname>[->field[->field|.field...]]":README
KPROBES=
FPROBES=
if grep -qF "p[:[<group>/][<event>]] <place> [<args>]" README ; then
KPROBES=yes
fi
if grep -qF "f[:[<group>/][<event>]] <func-name>[%return] [<args>]" README ; then
FPROBES=yes
fi
if [ -z "$KPROBES" -a -z "$FPROBES" ] ; then
exit_unsupported
fi
echo 0 > events/enable
echo > dynamic_events
# Load trace-events-sample module if available to have per-CPU counter structure defined
if ! lsmod | grep -q trace_events_sample; then
modprobe trace-events-sample || exit_unresolved
fi
if [ "$FPROBES" ] ; then
# 1. Test basic typecast on fprobe
echo 'f:fpevent1 vfs_read name=(file)file->f_path.dentry->d_name.name:string' >> dynamic_events
# 2. Test parenthesized typecast target on fprobe
echo 'f:fpevent2 vfs_read name=(file)(file)->f_path.dentry->d_name.name:string' >> dynamic_events
# 3. Test nested typecasts on fprobe
echo 'f:fpevent3 vfs_read name=(dentry)((file)file->f_path.dentry)->d_name.name:string' >> dynamic_events
# 4. Test container_of-style typecast with field option on fprobe
echo 'f:fpevent4 vfs_read name=(file,f_path)file->f_mode' >> dynamic_events
# 5. Test typecast on return value on fprobe
echo 'f:fpevent5 vfs_read%return name=(file)$retval->f_path.dentry->d_name.name:string' >> dynamic_events
# 6. Test $current variable support on fprobe
echo 'f:fpevent6 vfs_read pid=$current->pid' >> dynamic_events
echo 'f:fpevent7 vfs_read pid=(task_struct)$current->pid' >> dynamic_events
echo 'f:fpevent8 vfs_read pid=(task_struct,group_leader)$current->pid' >> dynamic_events
# Test this_cpu_read and this_cpu_ptr on fprobe
echo 'f:fpevent9 sample_timer_cb name=(foo_timer_data,timer)t->name:string count=this_cpu_read((foo_timer_data,timer)t->counter)' >> dynamic_events
echo 'f:fpevent10 sample_timer_cb ptr=this_cpu_ptr((foo_timer_data,timer)t->counter)' >> dynamic_events
fi
if [ "$KPROBES" ] ; then
# 7. Test basic typecast on kprobe
echo 'p:kpevent1 vfs_read name=(file)file->f_path.dentry->d_name.name:string' >> dynamic_events
# 8. Test parenthesized typecast target on kprobe
echo 'p:kpevent2 vfs_read name=(file)(file)->f_path.dentry->d_name.name:string' >> dynamic_events
# 9. Test nested typecasts on kprobe
echo 'p:kpevent3 vfs_read name=(dentry)((file)file->f_path.dentry)->d_name.name:string' >> dynamic_events
# 10. Test container_of-style typecast with field option on kprobe
echo 'p:kpevent4 vfs_read name=(file,f_path)file->f_mode' >> dynamic_events
# 11. Test typecast on return value on kretprobe
echo 'r:kpevent5 vfs_read name=(file)$retval->f_path.dentry->d_name.name:string' >> dynamic_events
# 12. Test $current variable support on kprobe
echo 'p:kpevent6 vfs_read pid=$current->pid' >> dynamic_events
echo 'p:kpevent7 vfs_read pid=(task_struct)$current->pid' >> dynamic_events
echo 'p:kpevent8 vfs_read pid=(task_struct,group_leader)$current->pid' >> dynamic_events
# Test this_cpu_read and this_cpu_ptr on kprobe
echo 'p:kpevent9 sample_timer_cb name=(foo_timer_data,timer)t->name:string count=this_cpu_read((foo_timer_data,timer)t->counter)' >> dynamic_events
echo 'p:kpevent10 sample_timer_cb ptr=this_cpu_ptr((foo_timer_data,timer)t->counter)' >> dynamic_events
fi
# Verify the events exist in dynamic_events
if [ "$FPROBES" ] ; then
grep -q "fpevent1 " dynamic_events
grep -q "fpevent2 " dynamic_events
grep -q "fpevent3 " dynamic_events
grep -q "fpevent4 " dynamic_events
grep -q "fpevent5 " dynamic_events
grep -q "fpevent6 " dynamic_events
grep -q "fpevent7 " dynamic_events
grep -q "fpevent8 " dynamic_events
if lsmod | grep -q trace_events_sample; then
grep -q "fpevent9 " dynamic_events
grep -q "fpevent10 " dynamic_events
fi
fi
if [ "$KPROBES" ] ; then
grep -q "kpevent1 " dynamic_events
grep -q "kpevent2 " dynamic_events
grep -q "kpevent3 " dynamic_events
grep -q "kpevent4 " dynamic_events
grep -q "kpevent5 " dynamic_events
grep -q "kpevent6 " dynamic_events
grep -q "kpevent7 " dynamic_events
grep -q "kpevent8 " dynamic_events
if lsmod | grep -q trace_events_sample; then
grep -q "kpevent9 " dynamic_events
grep -q "kpevent10 " dynamic_events
fi
fi
# Clean up
echo > dynamic_events
clear_trace
@@ -21,8 +21,17 @@ check_error 'e:foo/^bar.1 syscalls/sys_enter_openat' # BAD_EVENT_NAME
check_error 'e:foo/bar syscalls/sys_enter_openat arg=^$foo' # BAD_ATTACH_ARG
check_error 'e:foo/bar syscalls/sys_enter_openat arg=^COMM' # NO_EVENT_FIELD
if grep -q "\$current.*" README; then
check_error 'e:foo/bar syscalls/sys_enter_openat arg=^current' # NO_EVENT_FIELD
fi
if grep -q '<attached-group>\.<attached-event>.*\[if <filter>\]' README; then
check_error 'e:foo/bar syscalls/sys_enter_openat if ^' # NO_EP_FILTER
fi
if grep -q 'this_cpu_read(<fetcharg>)' README; then
check_error 'e:foo/bar syscalls/sys_enter_openat arg=^this_cpu_read(file)' # NOSUP_PERCPU
fi
exit 0
@@ -112,6 +112,18 @@ check_error 'f vfs_read%return $retval->^foo' # NO_PTR_STRCT
check_error 'f vfs_read file->^foo' # NO_BTF_FIELD
check_error 'f vfs_read file^-.foo' # BAD_HYPHEN
check_error 'f vfs_read ^file:string' # BAD_TYPE4STR
if grep -qF "[(structname" README ; then
check_error 'f vfs_read arg1=(task_struct)file^' # TYPECAST_REQ_FIELD
check_error 'f vfs_read arg1=(a)((b)((c)(^(d)file->d)->c)->b)->a' # TOO_MANY_NESTED
check_error 'f vfs_read arg1=(task_struct,^in_execve)file->comm' # TYPECAST_NOT_ALIGNED
check_error 'f vfs_read arg1=(task_struct,^foo_bar)file->pid' # NO_BTF_FIELD
check_error 'f vfs_read arg1=(^task_struct1234)file->pid' # NO_PTR_STRCT
check_error 'f vfs_read arg1=(task_struct,se^->group_node)file->comm' # TYPECAST_BAD_ARROW
check_error 'f vfs_read arg1=(task_struct,^->pid)file->comm' # NO_BTF_FIELD
check_error 'f vfs_read arg1=(task_struct,^.pid)file->comm' # NO_BTF_FIELD
check_error 'f vfs_read arg1=(task_struct,^.)file->comm' # NO_BTF_FIELD
check_error 'f vfs_read arg1=(task_struct)^@symbol+10->comm' # TYPECAST_SYM_OFFSET
fi
fi
else
@@ -115,6 +115,18 @@ check_error 'p vfs_read+20 ^$arg*' # NOFENTRY_ARGS
check_error 'p vfs_read ^hoge' # NO_BTFARG
check_error 'p kfree ^$arg10' # NO_BTFARG (exceed the number of parameters)
check_error 'r kfree ^$retval' # NO_RETVAL
if grep -qF "[(structname" README ; then
check_error 'p vfs_read arg1=(task_struct)file^' # TYPECAST_REQ_FIELD
check_error 'p vfs_read arg1=(a)((b)((c)(^(d)file->d)->c)->b)->a' # TOO_MANY_NESTED
check_error 'p vfs_read arg1=(task_struct,^in_execve)file->comm' # TYPECAST_NOT_ALIGNED
check_error 'p vfs_read arg1=(task_struct,^foo_bar)file->pid' # NO_BTF_FIELD
check_error 'p vfs_read arg1=(^task_struct1234)file->pid' # NO_PTR_STRCT
check_error 'p vfs_read arg1=(task_struct,se^->group_node)file->comm' # TYPECAST_BAD_ARROW
check_error 'p vfs_read arg1=(task_struct,^->pid)file->comm' # NO_BTF_FIELD
check_error 'p vfs_read arg1=(task_struct,^.pid)file->comm' # NO_BTF_FIELD
check_error 'p vfs_read arg1=(task_struct,^.)file->comm' # NO_BTF_FIELD
check_error 'p vfs_read arg1=(task_struct)^@symbol+10->comm' # TYPECAST_SYM_OFFSET
fi
else
check_error 'p vfs_read ^$arg*' # NOSUP_BTFARG
fi
@@ -28,4 +28,9 @@ if grep -q ".*symstr.*" README; then
check_error 'p /bin/sh:10 $stack0:^symstr' # BAD_TYPE
fi
# $current is not supported by uprobe
if grep -q "\$current.*" README; then
check_error 'p /bin/sh:10 ^$current:u8' # BAD_VAR
fi
exit 0