[PATCH] libtracefs: Add API for processing fprobes
Steven Rostedt <[email protected]> Wed, 4 Feb 2026 12:05:43 -0500
| Newsgroups | org.kernel.vger.linux-trace-devel |
|---|---|
| Message-ID | <[email protected]> |
From: "Steven Rostedt (Google)" <[email protected]> Add the following API: tracefs_fprobe_alloc() tracefs_fprobe_raw() tracefs_fprobe_destroy() To create and destroy Linux kernel fprobes. Signed-off-by: Steven Rostedt (Google) <[email protected]> --- Documentation/libtracefs-fprobes.txt | 245 +++++++++++++++++++++++++++ Documentation/libtracefs.txt | 9 +- include/tracefs.h | 10 +- samples/Makefile | 1 + src/tracefs-dynevents.c | 1 + src/tracefs-kprobes.c | 92 +++++++++- utest/tracefs-utest.c | 90 ++++++++++ 7 files changed, 443 insertions(+), 5 deletions(-) create mode 100644 Documentation/libtracefs-fprobes.txt diff --git a/Documentation/libtracefs-fprobes.txt b/Documentation/libtracefs-fprobes.txt new file mode 100644 index 000000000000..dcf9f455ace1 --- /dev/null +++ b/Documentation/libtracefs-fprobes.txt @@ -0,0 +1,245 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_fprobe_alloc, tracefs_fprobe_raw, tracefs_fprobe_destroy - +Allocate, get, create, and remove fprobes + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +struct tracefs_dynevent pass:[*]*tracefs_fprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_func_, const char pass:[*]_format_); +int *tracefs_fprobe_raw*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_func_, const char pass:[*]_format_); +int *tracefs_fprobe_destroy*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_func_, const char pass:[*]_format_, bool _force_); +-- + +DESCRIPTION +----------- +*tracefs_fprobe_alloc*() allocates a new fprobe context. The fbrobe is not configured in the system. +The fprobe can be added to the system by passing in the returned descriptor into +*tracefs_dynevent_create(3)*. +The new fprobe will be in the _system_ group (or fprobes if _system_ is NULL) and have the name of +_event_ (or _func_ if _event_ is NULL). The fprobe will be inserted to _func_ (function name), +and the _format_ will define the format of the fprobe. + +See the Linux documentation file under: Documentation/trace/fprobetrace.rst + +*tracefs_fprobe_raw*() will create a fprobe event. If _system_ is NULL, then +the default "fprobes" is used for the group (event system). Otherwise if _system_ +is specified then the fprobe will be created under the group by that name. The +_event_ is the name of the fprobe event to create. The _func_ is a function name. +This is where the location of the fprobe will be inserted in the kernel. The +_format_ is the fprobe format as specified as FETCHARGS in the Linux kernel source +in the Documentation/trace/fprobetrace.rst document. + +*tracefs_fprobe_destroy*() will destroy a specific fprobe created by +*tracefs_fprobe_raw*() with the same parameters. + +RETURN VALUE +------------ + +*tracefs_fprobe_raw*() returns 0 on success, or -1 on error. +If a parsing error occurs on *tracefs_fprobe_raw*() then +*tracefs_error_last*(3) may be used to retrieve the error message explaining the parsing issue. + +The *tracefs_fprobe_alloc*() function returns a pointer to an allocated +tracefs_dynevent structure, describing the probe. This pointer must be freed by +*tracefs_dynevent_free*(3). Note, this only allocates a descriptor representing the fprobe. It does +not modify the running system. + +The *tracefs_fprobe_destroy*() returns 0 on success or -1 on error if it was not able to +successful destory (or find) the fprobe. + + +ERRORS +------ +The following errors are for all the above calls: + +*EPERM* Not run as root user + +*ENODEV* fprobe events are not configured for the running kernel. + +*ENOMEM* Memory allocation error. + +*tracefs_fprobe_raw*() and *tracefs_fprobe_alloc*() can fail with the following errors: + +*EBADMSG* if _func_ is NULL. + +*EINVAL* Most likely a parsing error occurred (use *tracefs_error_last*(3) to possibly + see what that error was). + +Other errors may also happen caused by internal system calls. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> + +#include <tracefs.h> + +static struct tep_event *open_event; +static struct tep_format_field *file_field; + +static struct tep_event *openret_event; +static struct tep_format_field *ret_field; + +static int callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct trace_seq seq; + + trace_seq_init(&seq); + tep_print_event(event->tep, &seq, record, "%d-%s: ", TEP_PRINT_PID, TEP_PRINT_COMM); + + if (event->id == open_event->id) { + trace_seq_puts(&seq, "open file='"); + tep_print_field(&seq, record->data, file_field); + trace_seq_puts(&seq, "'\n"); + } else if (event->id == openret_event->id) { + unsigned long long ret; + tep_read_number_field(ret_field, record->data, &ret); + trace_seq_printf(&seq, "open ret=%lld\n", ret); + } else { + goto out; + } + + trace_seq_terminate(&seq); + trace_seq_do_printf(&seq); +out: + trace_seq_destroy(&seq); + + return 0; +} + +static pid_t run_exec(char **argv, char **env) +{ + pid_t pid; + + pid = fork(); + if (pid) + return pid; + + execve(argv[0], argv, env); + perror("exec"); + exit(-1); +} + +const char *myfprobe = "my_fprobes"; + +static void fprobe_create(const char *event, const char *func, const char *fmt) +{ + char *err; + int r; + + r = tracefs_fprobe_raw(myfprobe, event, func, fmt); + if (r < 0) { + err = tracefs_error_last(NULL); + perror("Failed to create kprobe:"); + if (err && strlen(err)) + fprintf(stderr, "%s\n", err); + } +} + +int main (int argc, char **argv, char **env) +{ + struct tracefs_instance *instance; + struct tep_handle *tep; + const char *sysnames[] = { myfprobe, NULL }; + pid_t pid; + + if (argc < 2) { + printf("usage: %s command\n", argv[0]); + exit(-1); + } + + instance = tracefs_instance_create("exec_open"); + if (!instance) { + perror("creating instance"); + exit(-1); + } + + tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_FPROBE, true); + + fprobe_create("open", "do_sys_openat2", + "file=+0($arg2):ustring flags=+0($arg3):x64 mode=+8($arg3):x64\n"); + + fprobe_create("openret", "do_sys_openat2", "ret=$retval"); + + tep = tracefs_local_events_system(NULL, sysnames); + if (!tep) { + perror("reading events"); + exit(-1); + } + open_event = tep_find_event_by_name(tep, myfprobe, "open"); + file_field = tep_find_field(open_event, "file"); + + openret_event = tep_find_event_by_name(tep, myfprobe, "openret"); + ret_field = tep_find_field(openret_event, "ret"); + + tracefs_event_enable(instance, myfprobe, NULL); + pid = run_exec(&argv[1], env); + + /* Let the child start to run */ + sched_yield(); + + do { + tracefs_load_cmdlines(NULL, tep); + tracefs_iterate_raw_events(tep, instance, NULL, 0, callback, NULL); + } while (waitpid(pid, NULL, WNOHANG) != pid); + + /* Will disable the events */ + tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_FPROBE, true); + tracefs_instance_destroy(instance); + tep_free(tep); + + return 0; +} +-- + +FILES +----- +[verse] +-- +*tracefs.h* + Header file to include in order to have access to the library APIs. +*-ltracefs* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +*libtracefs*(3), +*libtraceevent*(3), +*trace-cmd*(1) + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <[email protected]> +-- +REPORTING BUGS +-------------- +Report bugs to <[email protected]> + +LICENSE +------- +libtracefs is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git/ + +COPYING +------- +Copyright \(C) 2026 Google LLC. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs.txt b/Documentation/libtracefs.txt index 860e2be7d96a..84c70d23b5fd 100644 --- a/Documentation/libtracefs.txt +++ b/Documentation/libtracefs.txt @@ -210,10 +210,10 @@ Dynamic event generic APIs: enum tracefs_dynevent_type *tracefs_dynevent_info*(struct tracefs_dynevent pass:[*]_dynevent_, char pass:[*]pass:[*]_system_, char pass:[*]pass:[*]_event_, char pass:[*]pass:[*]_prefix_, char pass:[*]pass:[*]_addr_, char pass:[*]pass:[*]_format_); struct tep_event pass:[*]*tracefs_dynevent_get_event*(struct tep_handle pass:[*]_tep_, struct tracefs_dynevent pass:[*]_dynevent_); -Even probes (eprobes): +Event probes (eprobes): struct tracefs_dynevent pass:[*] *tracefs_eprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_target_system_, const char pass:[*]_target_event_, const char pass:[*]_fetchargs_); -Uprobes, Kprobes and Kretprobes: +Uprobes, Fprobes, Kprobes and Kretprobes: struct tracefs_dynevent pass:[*] *tracefs_kprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_addr_, const char pass:[*]_format_); struct tracefs_dynevent pass:[*] *tracefs_kretprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_addr_, const char pass:[*]_format_, unsigned int _max_); int *tracefs_kprobe_raw*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_addr_, const char pass:[*]_format_); @@ -224,6 +224,11 @@ Uprobes, Kprobes and Kretprobes: const char pass:[*]_file_, unsigned long long _offset_, const char pass:[*]_fetchargs_); int *tracefs_kprobe_destroy*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_addr_, const char pass:[*]_format_, bool _force_); + struct tracefs_dynevent pass:[*]*tracefs_fprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_func_, const char pass:[*]_format_); + int *tracefs_fprobe_raw*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_func_, const char pass:[*]_format_); + int *tracefs_fprobe_destroy*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_func_, const char pass:[*]_format_, bool _force_); Synthetic events: struct tracefs_synth pass:[*]*tracefs_sql*(struct tep_handle pass:[*]_tep_, const char pass:[*]_name_, diff --git a/include/tracefs.h b/include/tracefs.h index b6e0f6b3c851..392259c25496 100644 --- a/include/tracefs.h +++ b/include/tracefs.h @@ -321,7 +321,8 @@ enum tracefs_dynevent_type { TRACEFS_DYNEVENT_URETPROBE = 1 << 3, TRACEFS_DYNEVENT_EPROBE = 1 << 4, TRACEFS_DYNEVENT_SYNTH = 1 << 5, - TRACEFS_DYNEVENT_MAX = 1 << 6, + TRACEFS_DYNEVENT_FPROBE = 1 << 6, + TRACEFS_DYNEVENT_MAX = 1 << 7, }; #define TRACEFS_DYNEVENT_ALL 0xFFFFFFFF @@ -363,6 +364,13 @@ int tracefs_kretprobe_raw(const char *system, const char *event, int tracefs_kprobe_destroy(const char *system, const char *event, const char *addr, const char *format, bool force); +struct tracefs_dynevent * +tracefs_fprobe_alloc(const char *system, const char *event, const char *func, const char *format); +int tracefs_fprobe_raw(const char *system, const char *event, + const char *func, const char *format); +int tracefs_fprobe_destroy(const char *system, const char *event, + const char *addr, const char *format, bool force); + enum tracefs_hist_key_type { TRACEFS_HIST_KEY_NORMAL = 0, TRACEFS_HIST_KEY_HEX, diff --git a/samples/Makefile b/samples/Makefile index 7b68ae7ad34c..7f662a8d33da 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -12,6 +12,7 @@ EXAMPLES += dynevents EXAMPLES += kprobes EXAMPLES += eprobes EXAMPLES += uprobes +EXAMPLES += fprobes EXAMPLES += synth EXAMPLES += error EXAMPLES += filter diff --git a/src/tracefs-dynevents.c b/src/tracefs-dynevents.c index 330ef24e586f..5fdb723b6825 100644 --- a/src/tracefs-dynevents.c +++ b/src/tracefs-dynevents.c @@ -47,6 +47,7 @@ struct dyn_events_desc { {TRACEFS_DYNEVENT_URETPROBE, UPROBE_EVENTS, "r", dyn_generic_del, dyn_generic_parse}, {TRACEFS_DYNEVENT_EPROBE, "", "e", dyn_generic_del, dyn_generic_parse}, {TRACEFS_DYNEVENT_SYNTH, SYNTH_EVENTS, "", dyn_synth_del, dyn_synth_parse}, + {TRACEFS_DYNEVENT_FPROBE, "", "f", dyn_generic_del, dyn_generic_parse}, }; static int dyn_generic_del(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn) diff --git a/src/tracefs-kprobes.c b/src/tracefs-kprobes.c index 09b09d7ace04..c85df9a06525 100644 --- a/src/tracefs-kprobes.c +++ b/src/tracefs-kprobes.c @@ -19,6 +19,7 @@ #define KPROBE_EVENTS "kprobe_events" #define KPROBE_DEFAULT_GROUP "kprobes" +#define FPROBE_DEFAULT_GROUP "fprobes" static struct tracefs_dynevent * kprobe_alloc(enum tracefs_dynevent_type type, const char *system, const char *event, @@ -33,8 +34,10 @@ kprobe_alloc(enum tracefs_dynevent_type type, const char *system, const char *ev errno = EBADMSG; return NULL; } - if (!sys) - sys = KPROBE_DEFAULT_GROUP; + if (!sys) { + sys = type == TRACEFS_DYNEVENT_FPROBE ? + FPROBE_DEFAULT_GROUP : KPROBE_DEFAULT_GROUP; + } if (!event) { ename = strdup(addr); @@ -226,3 +229,88 @@ int tracefs_kprobe_destroy(const char *system, const char *event, return ret; } + +/** + * tracefs_fprobe_alloc - Allocate new fprobe + * @system: The system name (NULL for the default fprobes) + * @event: The event to create (NULL to use @func for the event) + * @func: The function to insert the probe + * @format: The format string to define the probe. + * + * Allocate a fprobe context that will be in the @system group (or fprobes if + * @system is NULL). Have the name of @event (or @func if @event is NULL). Will + * be inserted to @func (function name). And the @format will define the format + * of the fprobe. + * + * See the Linux documentation file under: + * Documentation/trace/fprobetrace.rst + * + * The fprobe is not created in the system. + * + * Return a pointer to a fprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + * errno will be set to EBADMSG if addr is NULL. + */ +struct tracefs_dynevent * +tracefs_fprobe_alloc(const char *system, const char *event, const char *func, const char *format) + +{ + return kprobe_alloc(TRACEFS_DYNEVENT_FPROBE, system, event, func, format); +} + +/** + * tracefs_fprobe_raw - Create a fprobe using raw format + * @system: The system name (NULL for the default fprobes) + * @event: The event to create (NULL to use @func for the event) + * @func: The function to insert the probe + * @format: The raw format string to define the probe. + * + * Create a fprobe that will be in the @system group (or fprobes if + * @system is NULL). Have the name of @event (or @func if @event is + * NULL). Will be inserted to @func (function name). And the @format + * will define the raw format of the fprobe. + * + * See the Linux documentation file under: + * Documentation/trace/fprobetrace.rst + * + * Return 0 on success, or -1 on error. + * If the syntex of @format was incorrect, running + * tracefs_error_last(NULL) may show what went wrong. + * + * errno will be set to EBADMSG if addr or format is NULL. + */ +int tracefs_fprobe_raw(const char *system, const char *event, + const char *func, const char *format) +{ + return kprobe_raw(TRACEFS_DYNEVENT_FPROBE, system, event, func, format); +} + +/** + * tracefs_fprobe_destroy - Remove an individual fprobe + * @system: The system of the fprobe to remove (could be NULL) + * @event: The event of the fprobe to remove + * @func: The function used to create the fprobe + * @format: The format used to create the fprobe + * @force: If true, try to disable the fprobe first + * + * This removes the fprobe was created by tracefs_kprobe_raw(). + * + * Returns 0 on success and -1 otherwise. + */ +int tracefs_fprobe_destroy(const char *system, const char *event, + const char *func, const char *format, bool force) +{ + struct tracefs_dynevent *fp; + int ret; + + fp = tracefs_fprobe_alloc(system, event, func, format); + if (!fp) + return -1; + + ret = tracefs_dynevent_destroy(fp, force); + + tracefs_dynevent_free(fp); + + return ret; +} diff --git a/utest/tracefs-utest.c b/utest/tracefs-utest.c index 97a91dfcdb2c..353b3cbee199 100644 --- a/utest/tracefs-utest.c +++ b/utest/tracefs-utest.c @@ -2529,6 +2529,95 @@ static void test_eprobes(void) test_eprobes_instance(test_instance); } +static void test_fprobes_instance(struct tracefs_instance *instance) +{ + struct probe_test ftests[] = { + { TRACEFS_DYNEVENT_FPROBE, "f", NULL, "mkdir", "do_mkdirat", "path=+u0($arg2):ustring" }, + { TRACEFS_DYNEVENT_FPROBE, "f", NULL, "close", "close_fd", NULL }, + { TRACEFS_DYNEVENT_FPROBE, "f", "ftest", "open2", "do_sys_openat2", + "file=+u0($arg2):ustring flags=+0($arg3):x64" }, + }; + int fprobe_count = sizeof(ftests) / sizeof((ftests)[0]); + struct tracefs_dynevent **dfprobe; + struct tracefs_dynevent **devents; + struct tep_handle *tep; + int ret; + int i; + + tep = tep_alloc(); + CU_TEST(tep != NULL); + + dfprobe = calloc(fprobe_count + 1, sizeof(*dfprobe)); + + /* Invalid parameters */ + CU_TEST(tracefs_fprobe_alloc("test", NULL, NULL, "test") == NULL); + CU_TEST(tracefs_fprobe_raw("test", "test", NULL, "test") != 0); + + /* fprobes APIs */ + destroy_dynevents(TRACEFS_DYNEVENT_FPROBE); + + for (i = 0; i < fprobe_count; i++) { + dfprobe[i] = tracefs_fprobe_alloc(ftests[i].system, ftests[i].event, + ftests[i].address, ftests[i].format); + CU_TEST(dfprobe[i] != NULL); + } + dfprobe[i] = NULL; + get_dynevents_check(TRACEFS_DYNEVENT_FPROBE, 0); + CU_TEST(check_probes(ftests, fprobe_count, dfprobe, false, instance, tep)); + + for (i = 0; i < fprobe_count; i++) { + CU_TEST(tracefs_dynevent_create(dfprobe[i]) == 0); + } + devents = get_dynevents_check(TRACEFS_DYNEVENT_FPROBE, fprobe_count); + CU_TEST(check_probes(ftests, fprobe_count, devents, true, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + for (i = 0; i < fprobe_count; i++) { + CU_TEST(tracefs_dynevent_destroy(dfprobe[i], false) == 0); + } + get_dynevents_check(TRACEFS_DYNEVENT_FPROBE, 0); + CU_TEST(check_probes(ftests, fprobe_count, dfprobe, false, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + for (i = 0; i < fprobe_count; i++) + tracefs_dynevent_free(dfprobe[i]); + + /* kprobes raw APIs */ + destroy_dynevents(TRACEFS_DYNEVENT_FPROBE); + + for (i = 0; i < fprobe_count; i++) { + ret = tracefs_fprobe_raw(ftests[i].system, ftests[i].event, + ftests[i].address, ftests[i].format); + CU_TEST(ret == 0); + } + + devents = get_dynevents_check(TRACEFS_DYNEVENT_FPROBE, fprobe_count); + CU_TEST(check_probes(ftests, fprobe_count, devents, true, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + /* Try destroying all the events using tracefs_kprobe_destroy */ + for (i = 0; i < fprobe_count; i++) { + ret = tracefs_fprobe_destroy(ftests[i].system, ftests[i].event, + ftests[i].address, ftests[i].format, true); + CU_TEST(ret == 0); + devents = get_dynevents_check(TRACEFS_DYNEVENT_FPROBE, + fprobe_count - (i + 1)); + tracefs_dynevent_list_free(devents); + } + get_dynevents_check(TRACEFS_DYNEVENT_KPROBE, 0); + + free(dfprobe); + tep_free(tep); +} + +static void test_fprobes(void) +{ + test_fprobes_instance(test_instance); +} + #define FOFFSET 1000ll static void test_uprobes_instance(struct tracefs_instance *instance) { @@ -3947,5 +4036,6 @@ void test_tracefs_lib(void) CU_add_test(suite, "synthetic events", test_synthetic); CU_add_test(suite, "eprobes", test_eprobes); CU_add_test(suite, "uprobes", test_uprobes); + CU_add_test(suite, "fprobes", test_fprobes); CU_add_test(suite, "multi probe test", test_multi_probes); } -- 2.51.0