* [PATCH 0/2] libtraceevent: Add BTF parsing for function arguments
@ 2025-07-31 18:52 Steven Rostedt
2025-07-31 18:52 ` [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle Steven Rostedt
2025-07-31 18:52 ` [PATCH 2/2] libtraceevent: Add man page for the new BTF functions Steven Rostedt
0 siblings, 2 replies; 14+ messages in thread
From: Steven Rostedt @ 2025-07-31 18:52 UTC (permalink / raw)
To: linux-trace-devel
Cc: Douglas Raillard, Masami Hiramatsu, Namhyung Kim, Takaya Saeki,
Ian Rogers, aahringo, Steven Rostedt (Google)
From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
Now that functions can save the first 6 registers of their args during a
trace, and BTF can be used to decipher those arguments, have libtraceevent
handle this as well.
Add a tep_load_btf() function that loads the content of /sys/kernel/btf/vmlinux
and then uses that to parse it to show the arguments of functions in the trace.
When trace-cmd supports this, it would look like:
# trace-cmd record -p function -O func-args cat /etc/passwd
# trace-cmd report
cat-33501 [005] ..... 136155.767937: function: mutex_unlock(lock=0xffffffff831dbbe0)
cat-33501 [005] ..... 136155.767939: function: __mutex_unlock_slowpath(lock=0xffffffff831dbbe0, ip=0xffffffff814a7154)
cat-33501 [005] ..... 136155.767940: function: __f_unlock_pos(f=0xffff8881538de000)
cat-33501 [005] ..... 136155.767940: function: mutex_unlock(lock=0xffff8881538de090)
cat-33501 [005] ..... 136155.767940: function: __mutex_unlock_slowpath(lock=0xffff8881538de090, ip=0xffffffff816e8ed1)
cat-33501 [005] ..... 136155.767941: function: mem_cgroup_handle_over_high(gfp_mask=0xcc0)
cat-33501 [005] ..... 136155.767941: function: blkcg_maybe_throttle_current()
cat-33501 [005] ..... 136155.767942: function: __rseq_handle_notify_resume(ksig=0x0, regs=0xffffc9000e3eff58)
cat-33501 [005] d.... 136155.767943: function: fpregs_assert_state_consistent()
cat-33501 [005] d.... 136155.767943: function: switch_fpu_return()
cat-33501 [005] ..... 136155.767950: function: __x64_sys_execve(regs=0xffffc9000e3eff58)
cat-33501 [005] ..... 136155.767951: function: getname_flags(filename=0x7ffe7d33f3d0, flags=0)
cat-33501 [005] ..... 136155.767951: function: getname_flags.part.0(7ffe7d33f3d0, 0, 0, 0, 0, 0)
cat-33501 [005] ..... 136155.767951: function: kmem_cache_alloc_noprof(s=0xffff8881001d3800, gfpflags=0xcc0)
cat-33501 [005] ..... 136155.767951: function: fs_reclaim_acquire(gfp_mask=0xcc0)
cat-33501 [005] ..... 136155.767952: function: fs_reclaim_release(gfp_mask=0xcc0)
cat-33501 [005] ..... 136155.767953: function: kmemleak_alloc(ptr=0xffff8881114a0000, size=0x1000, min_count=1, gfp=0xcc0)
cat-33501 [005] ..... 136155.767953: function: __create_object(ptr=0xffff8881114a0000, size=0x1000, min_count=1, gfp=0xcc0, objflags=0x0)
cat-33501 [005] ..... 136155.767954: function: __alloc_object(gfp=0xcc0)
Steven Rostedt (Google) (2):
libtraceevent: Add loading of BTF to the tep handle
libtraceevent: Add man page for the new BTF functions
Documentation/libtraceevent-btf.txt | 158 ++++++++++
Documentation/libtraceevent.txt | 5 +
include/traceevent/event-parse.h | 5 +
plugins/plugin_function.c | 15 +-
src/Makefile | 1 +
src/event-parse-local.h | 6 +
src/event-parse.c | 2 +
src/trace-btf.c | 449 ++++++++++++++++++++++++++++
8 files changed, 631 insertions(+), 10 deletions(-)
create mode 100644 Documentation/libtraceevent-btf.txt
create mode 100644 src/trace-btf.c
--
2.47.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle
2025-07-31 18:52 [PATCH 0/2] libtraceevent: Add BTF parsing for function arguments Steven Rostedt
@ 2025-07-31 18:52 ` Steven Rostedt
2025-08-04 11:47 ` Douglas Raillard
2025-08-04 16:07 ` Douglas Raillard
2025-07-31 18:52 ` [PATCH 2/2] libtraceevent: Add man page for the new BTF functions Steven Rostedt
1 sibling, 2 replies; 14+ messages in thread
From: Steven Rostedt @ 2025-07-31 18:52 UTC (permalink / raw)
To: linux-trace-devel
Cc: Douglas Raillard, Masami Hiramatsu, Namhyung Kim, Takaya Saeki,
Ian Rogers, aahringo, Steven Rostedt (Google)
From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
Add loading of the BTF file /sys/kernel/btf/vmlinux into the tep_handler and
then use it to format the function tracer if it has arguments:
cat-33501 [005] ..... 136155.767950: function: __x64_sys_execve(regs=0xffffc9000e3eff58)
cat-33501 [005] ..... 136155.767951: function: getname_flags(filename=0x7ffe7d33f3d0, flags=0)
cat-33501 [005] ..... 136155.767951: function: getname_flags.part.0(7ffe7d33f3d0, 0, 0, 0, 0, 0)
cat-33501 [005] ..... 136155.767951: function: kmem_cache_alloc_noprof(s=0xffff8881001d3800, gfpflags=0xcc0)
cat-33501 [005] ..... 136155.767951: function: fs_reclaim_acquire(gfp_mask=0xcc0)
cat-33501 [005] ..... 136155.767952: function: fs_reclaim_release(gfp_mask=0xcc0)
cat-33501 [005] ..... 136155.767953: function: kmemleak_alloc(ptr=0xffff8881114a0000, size=0x1000, min_count=1, gfp=0xcc0)
cat-33501 [005] ..... 136155.767953: function: __create_object(ptr=0xffff8881114a0000, size=0x1000, min_count=1, gfp=0xcc0, objflags=0x0)
cat-33501 [005] ..... 136155.767954: function: __alloc_object(gfp=0xcc0)
cat-33501 [005] ..... 136155.767954: function: kmem_cache_alloc_noprof(s=0xffff888100045700, gfpflags=0x92cc0)
cat-33501 [005] ..... 136155.767954: function: fs_reclaim_acquire(gfp_mask=0x92cc0)
cat-33501 [005] ..... 136155.767955: function: fs_reclaim_release(gfp_mask=0x92cc0)
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
---
include/traceevent/event-parse.h | 5 +
plugins/plugin_function.c | 15 +-
src/Makefile | 1 +
src/event-parse-local.h | 6 +
src/event-parse.c | 2 +
src/trace-btf.c | 449 +++++++++++++++++++++++++++++++
6 files changed, 468 insertions(+), 10 deletions(-)
create mode 100644 src/trace-btf.c
diff --git a/include/traceevent/event-parse.h b/include/traceevent/event-parse.h
index d3ce0a43a8fc..ebfc2c7abac2 100644
--- a/include/traceevent/event-parse.h
+++ b/include/traceevent/event-parse.h
@@ -580,6 +580,11 @@ int tep_get_ref(struct tep_handle *tep);
struct kbuffer *tep_kbuffer(struct tep_handle *tep);
+/* BTF */
+int tep_load_btf(struct tep_handle *tep, void *raw_data, size_t data_size);
+int tep_btf_print_args(struct tep_handle *tep, struct trace_seq *s, void *args,
+ int nmem, int size, const char *func);
+
/* for debugging */
void tep_print_funcs(struct tep_handle *tep);
void tep_print_printk(struct tep_handle *tep);
diff --git a/plugins/plugin_function.c b/plugins/plugin_function.c
index a337446e3462..379fc3ad0ffd 100644
--- a/plugins/plugin_function.c
+++ b/plugins/plugin_function.c
@@ -135,10 +135,9 @@ static void show_function(struct trace_seq *s, struct tep_handle *tep,
/* Returns true if it printed args, otherwise it returns false */
static bool print_args(struct trace_seq *s, struct tep_event *event,
- struct tep_record *record)
+ struct tep_record *record, const char *func)
{
struct tep_format_field *field;
- unsigned long arg;
void *args;
int len;
@@ -156,12 +155,8 @@ static bool print_args(struct trace_seq *s, struct tep_event *event,
trace_seq_putc(s, '(');
- for (int i = 0; i < len; i += sizeof(long), args += sizeof(long)) {
- memcpy(&arg, args, sizeof(long));
- trace_seq_printf(s, "%lx", arg);
- if (i + sizeof(long) < len)
- trace_seq_puts(s, ", ");
- }
+ tep_btf_print_args(event->tep, s, args, len / sizeof(long), sizeof(long), func);
+
trace_seq_putc(s, ')');
return true;
}
@@ -196,7 +191,7 @@ static int function_handler(struct trace_seq *s, struct tep_record *record,
else
trace_seq_printf(s, "0x%llx", function);
- print_args(s, event, record);
+ print_args(s, event, record, func);
if (ftrace_parent->set) {
trace_seq_printf(s, " <-- ");
@@ -281,7 +276,7 @@ trace_stack_handler(struct trace_seq *s, struct tep_record *record,
static int
trace_raw_data_handler(struct trace_seq *s, struct tep_record *record,
- struct tep_event *event, void *context)
+ struct tep_event *event, void *context)
{
struct tep_format_field *field;
unsigned long long id;
diff --git a/src/Makefile b/src/Makefile
index 53bb570182d2..1ff669ce8deb 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -11,6 +11,7 @@ OBJS += parse-filter.o
OBJS += parse-utils.o
OBJS += tep_strerror.o
OBJS += trace-seq.o
+OBJS += trace-btf.o
OBJS := $(OBJS:%.o=$(bdir)/%.o)
DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d)
diff --git a/src/event-parse-local.h b/src/event-parse-local.h
index d9e9faf649d1..50bc4d87bbe6 100644
--- a/src/event-parse-local.h
+++ b/src/event-parse-local.h
@@ -14,6 +14,7 @@ struct func_list;
struct event_handler;
struct func_resolver;
struct tep_plugins_dir;
+struct tep_btf;
#define __hidden __attribute__((visibility ("hidden")))
@@ -89,6 +90,8 @@ struct tep_handle {
const char *input_buf;
unsigned long long input_buf_ptr;
unsigned long long input_buf_siz;
+
+ struct tep_btf *btf;
};
enum tep_print_parse_type {
@@ -124,4 +127,7 @@ const char *tep_get_input_buf(struct tep_handle *tep);
enum tep_event_type tep_read_token(struct tep_handle *tep, char **tok);
void tep_free_token(char *tok);
+/* BTF routines */
+void tep_btf_free(struct tep_btf *btf);
+
#endif /* _PARSE_EVENTS_INT_H */
diff --git a/src/event-parse.c b/src/event-parse.c
index aa16d83bb2ad..730f25c92110 100644
--- a/src/event-parse.c
+++ b/src/event-parse.c
@@ -8726,6 +8726,8 @@ void tep_free(struct tep_handle *tep)
free(tep->func_resolver);
tep_free_plugin_paths(tep);
+ tep_btf_free(tep->btf);
+
free(tep);
}
diff --git a/src/trace-btf.c b/src/trace-btf.c
new file mode 100644
index 000000000000..79c5d6c93327
--- /dev/null
+++ b/src/trace-btf.c
@@ -0,0 +1,449 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * Copyright (C) 2025 Google, Steven Rostedt <rostedt@goodmis.org>
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <linux/btf.h>
+
+#include "event-parse.h"
+#include "event-utils.h"
+#include "event-parse-local.h"
+
+struct btf_header;
+struct btf_type;
+
+struct tep_btf {
+ struct btf_header *hdr;
+ const char *strings;
+ struct btf_type **types;
+ unsigned long nr_types;
+ struct btf_type **funcs;
+ unsigned long nr_funcs;
+ void *data;
+ size_t raw_size;
+ void *raw_data;
+};
+
+#define REALLOC_SIZE (1 << 10)
+#define REALLOC_MASK (REALLOC_SIZE - 1)
+
+static const char *btf_name(struct tep_btf *btf, int off)
+{
+ if (off < btf->hdr->str_len)
+ return btf->strings + off;
+ return "";
+}
+
+/* List taken from the Linux kernel */
+static const char * const btf_kind_str[NR_BTF_KINDS] = {
+ [BTF_KIND_UNKN] = "UNKNOWN",
+ [BTF_KIND_INT] = "INT",
+ [BTF_KIND_PTR] = "PTR",
+ [BTF_KIND_ARRAY] = "ARRAY",
+ [BTF_KIND_STRUCT] = "STRUCT",
+ [BTF_KIND_UNION] = "UNION",
+ [BTF_KIND_ENUM] = "ENUM",
+ [BTF_KIND_FWD] = "FWD",
+ [BTF_KIND_TYPEDEF] = "TYPEDEF",
+ [BTF_KIND_VOLATILE] = "VOLATILE",
+ [BTF_KIND_CONST] = "CONST",
+ [BTF_KIND_RESTRICT] = "RESTRICT",
+ [BTF_KIND_FUNC] = "FUNC",
+ [BTF_KIND_FUNC_PROTO] = "FUNC_PROTO",
+ [BTF_KIND_VAR] = "VAR",
+ [BTF_KIND_DATASEC] = "DATASEC",
+ [BTF_KIND_FLOAT] = "FLOAT",
+ [BTF_KIND_DECL_TAG] = "DECL_TAG",
+ [BTF_KIND_TYPE_TAG] = "TYPE_TAG",
+ [BTF_KIND_ENUM64] = "ENUM64",
+};
+
+static const char *btf_type_str(const struct btf_type *t)
+{
+ return btf_kind_str[BTF_INFO_KIND(t->info)];
+}
+
+static int insert_type(struct btf_type ***types, unsigned long *cnt, struct btf_type *type)
+{
+ unsigned long nr_types = *cnt;
+ struct btf_type **array = *types;
+
+ if (!(nr_types & REALLOC_MASK)) {
+ int size = nr_types + REALLOC_SIZE;
+
+ array = realloc(array, sizeof(struct btf_type *) * size);
+ if (!array) {
+ tep_warning("Failed to alloct memory for new type");
+ return -1;
+ }
+ *types = array;
+ }
+
+ array[nr_types++] = type;
+ *cnt = nr_types;
+ return 0;
+}
+
+static int add_type(struct tep_btf *btf, struct btf_type *type)
+{
+ return insert_type(&btf->types, &btf->nr_types, type);
+}
+
+static int add_func(struct tep_btf *btf, struct btf_type *type)
+{
+ return insert_type(&btf->funcs, &btf->nr_funcs, type);
+}
+
+static int btf_type_size(struct btf_type *type)
+{
+ int kind = BTF_INFO_KIND(type->info);
+ int size = sizeof(*type);
+
+ switch (kind) {
+ case BTF_KIND_INT:
+ return size + sizeof(int);
+ case BTF_KIND_VAR:
+ return size + sizeof(struct btf_var);
+ case BTF_KIND_ARRAY:
+ return size + sizeof(struct btf_array);
+ case BTF_KIND_DECL_TAG:
+ return size + sizeof(struct btf_decl_tag);
+ case BTF_KIND_ENUM:
+ return size + sizeof(struct btf_enum) * BTF_INFO_VLEN(type->info);
+ case BTF_KIND_ENUM64:
+ return size + sizeof(struct btf_enum64) * BTF_INFO_VLEN(type->info);
+ case BTF_KIND_STRUCT:
+ case BTF_KIND_UNION:
+ return size + sizeof(struct btf_member) * BTF_INFO_VLEN(type->info);
+ case BTF_KIND_FUNC_PROTO:
+ return size + sizeof(struct btf_param) * BTF_INFO_VLEN(type->info);
+ case BTF_KIND_DATASEC:
+ return size + sizeof(struct btf_var_secinfo) * BTF_INFO_VLEN(type->info);
+ case BTF_KIND_PTR:
+ case BTF_KIND_FWD:
+ case BTF_KIND_TYPEDEF:
+ case BTF_KIND_VOLATILE:
+ case BTF_KIND_CONST:
+ case BTF_KIND_RESTRICT:
+ case BTF_KIND_TYPE_TAG:
+ case BTF_KIND_FUNC:
+ case BTF_KIND_FLOAT:
+ return size;
+ }
+ return -1;
+}
+
+static int cmp_funcs(const void *A, const void *B, void *data)
+{
+ struct tep_btf *btf = data;
+ const struct btf_type *a = *(const struct btf_type **)A;
+ const struct btf_type *b = *(const struct btf_type **)B;
+ const char *name_a = btf_name(btf, a->name_off);
+ const char *name_b = btf_name(btf, b->name_off);
+
+ return strcmp(name_a, name_b);
+}
+
+struct tcmd_search {
+ struct tep_btf *btf;
+ const char *name;
+};
+
+static int cmp_key_func(const void *A, const void *B)
+{
+ const struct tcmd_search *key = A;
+ const struct btf_type *b = *(const struct btf_type **)B;
+ const char *name_b = btf_name(key->btf, b->name_off);
+
+ return strcmp(key->name, name_b);
+}
+
+static struct btf_type *tep_btf_find_func(struct tep_btf *btf, const char *name)
+{
+ struct tcmd_search tsearch;
+ struct btf_type **t;
+
+ if (!btf || !name)
+ return NULL;
+
+ tsearch.btf = btf;
+ tsearch.name = name;
+
+ t = bsearch(&tsearch, btf->funcs, btf->nr_funcs, sizeof(btf->funcs[0]),
+ cmp_key_func);
+
+ return t ? *t : NULL;
+}
+
+static int load_types(struct tep_btf *btf)
+{
+ struct btf_type *type;
+ void *start, *end;
+ int size;
+
+ start = btf->data + btf->hdr->type_off;
+ end = start + btf->hdr->type_len;
+
+ if (end > btf->raw_data + btf->raw_size)
+ return -1;
+
+ for (type = start; (void *)type < end;) {
+ if (add_type(btf, type))
+ return -1;
+ if (BTF_INFO_KIND(type->info) == BTF_KIND_FUNC) {
+ if (add_func(btf, type))
+ return -1;
+ }
+ size = btf_type_size(type);
+ if (size < 0) {
+ tep_warning("Invalid type %d\n", BTF_INFO_KIND(type->info));
+ return -1;
+ }
+ type = (void *)type + size;
+ }
+
+ qsort_r(btf->funcs, btf->nr_funcs, sizeof(btf->funcs[0]), cmp_funcs, btf);
+ return 0;
+}
+
+__hidden void tep_btf_free(struct tep_btf *btf)
+{
+ if (!btf)
+ return;
+
+ free((void *)btf->strings);
+ free(btf->types);
+ free(btf->funcs);
+ free(btf->raw_data);
+}
+
+/** tep_btf_init - Initialize btf with raw data
+ * @raw_data: Allocated data to use for BTF (/sys/kernel/btf/vmlinux)
+ * @data_size: The amount of data in @raw_data
+ *
+ * The @raw_data ownership belongs to this function when called.
+ * It must be allocated by the glibc allocator as it will be freed
+ * when the BTF descriptor is freed. It will not be freed if this
+ * function returns failure (NULL).
+ *
+ * Returns: On success it returns the BTF descriptor, and he @raw_data
+ * will now be apart of it (it will be freed by tep_btf_free().
+ * On failure it returns NULL, and the @raw_data is NOT freed.
+ */
+static struct tep_btf *tep_btf_init(void *raw_data, size_t data_size)
+{
+ struct tep_btf *btf;
+
+ btf = calloc(1, sizeof(*btf));
+ if (!btf)
+ return NULL;
+
+ btf->raw_data = raw_data;
+ btf->raw_size = data_size;
+ btf->hdr = raw_data;
+ if (btf->hdr->hdr_len < sizeof(*btf->hdr)) {
+ tep_warning("Header (%d) smaller than expected header %zd",
+ btf->hdr->hdr_len, sizeof(*btf->hdr));
+ goto fail;
+ }
+
+ btf->data = btf->raw_data + btf->hdr->hdr_len;
+
+ btf->strings = btf->data + btf->hdr->str_off;
+ printf("str len = %d\n", btf->hdr->str_len);
+
+ if (load_types(btf) < 0)
+ goto fail;
+
+ return btf;
+ fail:
+ btf->raw_data = NULL;
+ tep_btf_free(btf);
+ return NULL;
+}
+
+/**
+ * tep_load_btf - Load BTF information into a tep handle
+ * @tep: The tep handle to load the BTF info into
+ * @raw_data: The raw data containing the BTF file
+ * @data_size: The amount of data in @raw_data
+ *
+ * Initializes BTF into the @tep handle. If it had already had BTF
+ * loaded, it will free the previous BTF and recreate it from @raw_data.
+ *
+ * Returns: 0 on success and -1 on failure.
+ */
+int tep_load_btf(struct tep_handle *tep, void *raw_data, size_t data_size)
+{
+ /* If btf was already loaded, free it */
+ tep_btf_free(tep->btf);
+
+ tep->btf = tep_btf_init(raw_data, data_size);
+ if (!tep->btf)
+ return -1;
+ return 0;
+}
+
+static struct btf_type *btf_get_type(struct tep_btf *btf, int id)
+{
+ if (!id || id > btf->nr_types)
+ return NULL;
+
+ return btf->types[id - 1];
+}
+
+static struct btf_type *btf_skip_modifiers(struct tep_btf *btf, int id)
+{
+ struct btf_type *t = btf_get_type(btf, id);
+
+ for (;;) {
+ switch (BTF_INFO_KIND(t->info)) {
+ case BTF_KIND_TYPEDEF:
+ case BTF_KIND_VOLATILE:
+ case BTF_KIND_CONST:
+ case BTF_KIND_RESTRICT:
+ case BTF_KIND_TYPE_TAG:
+ id = t->type;
+ t = btf_get_type(btf, t->type);
+ continue;
+ }
+ break;
+ }
+
+ return t;
+}
+
+static void assign_arg(unsigned long long *arg, void *args, int size, int a)
+{
+ *arg = size == 4 ?
+ *(unsigned int *)(args + a * sizeof(int)) :
+ *(unsigned long long *)(args + a * sizeof(long long));
+}
+
+/**
+ * tep_btf_print_args - Print function arguments from BTF info
+ * @tep: The tep descriptor to use
+ * @s: The trace_seq to write the arguments into
+ * @args: The array that holds the arguments
+ * @nmem: The number of arguments
+ * @size: The size of each item in args (4 or 8).
+ * @func: The name of the function.
+ *
+ * Loads up the @s with a list of arguments for the function based on
+ * the @args.
+ *
+ * If there's no BTF loaded or @func is not found, then it just writes
+ * the @args as raw numbers. Otherwise it will pretty print the
+ * arguments based on the function info in BTF.
+ *
+ * Returns: 0 on success and -1 on failure.
+ */
+int tep_btf_print_args(struct tep_handle *tep, struct trace_seq *s, void *args,
+ int nmem, int size, const char *func)
+{
+ struct tep_btf *btf = tep->btf;
+ struct btf_type *type = tep_btf_find_func(btf, func);
+ struct btf_param *param;
+ unsigned long long arg;
+ unsigned int encode;
+ const char *param_name;
+ int a, p, x, nr;
+
+ if (size != 4 && size != 8)
+ return -1;
+
+ if (!type) {
+ for (int i = 0; i < nmem; i++) {
+ assign_arg(&arg, args, size, i);
+ trace_seq_printf(s, "%llx", arg);
+ if (i + 1 < nmem)
+ trace_seq_puts(s, ", ");
+ }
+ return 0;
+ }
+
+ if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC) {
+ printf("Invalid func type %d %s\n", BTF_INFO_KIND(type->info),
+ btf_type_str(type));
+ return -1;
+ }
+
+ /* Get the function proto */
+ type = btf_get_type(btf, type->type);
+
+ /* No proto means "()" ? */
+ if (!type)
+ return 0;
+
+ if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC_PROTO) {
+ printf("Invalid func proto type %d %s\n", BTF_INFO_KIND(type->info),
+ btf_type_str(type));
+ return -1;
+ }
+
+ /* Get the number of parameters */
+ nr = BTF_INFO_VLEN(type->info);
+
+ /* The parameters are right after the FUNC_PROTO type */
+ param = ((void *)type) + sizeof(*type);
+
+ for (a = 0, p = 0; p < nr; a++, p++) {
+ struct btf_type *t;
+
+ if (p)
+ trace_seq_puts(s, ", ");
+
+ if (a == nmem) {
+ trace_seq_puts(s, "...");
+ break;
+ }
+
+ assign_arg(&arg, args, size, a);
+
+ param_name = btf_name(btf, param[p].name_off);
+ if (param_name)
+ trace_seq_printf(s, "%s=", param_name);
+
+ t = btf_skip_modifiers(btf, param[p].type);
+
+ switch (t ? BTF_INFO_KIND(t->info) : BTF_KIND_UNKN) {
+ case BTF_KIND_UNKN:
+ trace_seq_putc(s, '?');
+ /* Still print unknown type values */
+ /* fallthough */
+ case BTF_KIND_PTR:
+ trace_seq_printf(s, "0x%llx", arg);
+ break;
+ case BTF_KIND_INT:
+ encode = *(int *)((void *)t + sizeof(*t));
+ /* Print unsigned ints as hex */
+ if (BTF_INT_ENCODING(encode) & BTF_INT_SIGNED)
+ trace_seq_printf(s, "%lld", arg);
+ else
+ trace_seq_printf(s, "0x%llx", arg);
+ break;
+ case BTF_KIND_ENUM:
+ trace_seq_printf(s, "%lld", arg);
+ break;
+ default:
+ /* This does not handle complex arguments */
+ trace_seq_printf(s, "(%s)[0x%llx", btf_type_str(t), arg);
+ for (x = sizeof(long); x < t->size; x += sizeof(long)) {
+ trace_seq_putc(s, ':');
+ if (++a == nmem) {
+ trace_seq_puts(s, "...]");
+ return 0;
+ }
+ assign_arg(&arg, args, size, a);
+ trace_seq_printf(s, "0x%llx", arg);
+ }
+ trace_seq_putc(s, ']');
+ break;
+ }
+ }
+ return 0;
+}
--
2.47.2
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 2/2] libtraceevent: Add man page for the new BTF functions
2025-07-31 18:52 [PATCH 0/2] libtraceevent: Add BTF parsing for function arguments Steven Rostedt
2025-07-31 18:52 ` [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle Steven Rostedt
@ 2025-07-31 18:52 ` Steven Rostedt
2025-08-04 11:09 ` Douglas Raillard
1 sibling, 1 reply; 14+ messages in thread
From: Steven Rostedt @ 2025-07-31 18:52 UTC (permalink / raw)
To: linux-trace-devel
Cc: Douglas Raillard, Masami Hiramatsu, Namhyung Kim, Takaya Saeki,
Ian Rogers, aahringo, Steven Rostedt (Google)
From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
Add a man page for tep_load_btf() and tep_btf_print_args()
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
---
Documentation/libtraceevent-btf.txt | 158 ++++++++++++++++++++++++++++
Documentation/libtraceevent.txt | 5 +
2 files changed, 163 insertions(+)
create mode 100644 Documentation/libtraceevent-btf.txt
diff --git a/Documentation/libtraceevent-btf.txt b/Documentation/libtraceevent-btf.txt
new file mode 100644
index 000000000000..802d953a6ef1
--- /dev/null
+++ b/Documentation/libtraceevent-btf.txt
@@ -0,0 +1,158 @@
+libtraceevent(3)
+================
+
+NAME
+----
+tep_load_btf, tep_btf_print_args -
+Load BTF file and use it to pretty print function arguments.
+
+SYNOPSIS
+--------
+[verse]
+--
+*#include <event-parse.h>*
+
+int *tep_load_btf*(struct tep_handle pass:[*]_tep_, void pass:[*]_raw_data_, size_t _data_size_);
+int *tep_btf_print_args*(struct tep_handle pass:[*]_tep_, struct trace_seq pass:[*]_s_, void pass:[*]_args_,
+ int nmem, int size, const char pass:[*]_func_);
+--
+
+DESCRIPTION
+-----------
+If the Linux kernel has BTF configured, then a binary file will exist
+in the path of */sys/kernel/btf/vmlinux*. If this file is read into memory
+and passed to *tep_load_btf()* function, then it will be used to parse
+the arguments of a given function, if that function data is found within
+the BTF file.
+
+The *tep_load_btf()* takes the _tep_ handle and will load the allocated _raw_data_
+into it. NOTE, the _raw_data_ must be allocated via glibc() allocations as on
+success, it will save the _raw_data_ internally, and will be freed when the
+_tep_ handler is destroyed. If it is called twice, it will free the old _raw_data_
+and reinitialize itself with the new _raw_data_. The _data_size_ should be set
+to the size of the content in _raw_data_.
+
+The *tep_btf_print_args()* takes a _tep_ handle, a trace_seq _s_ pointer
+(that was initialized by *trace_seq_init(3)*), an _args_ array that holds either
+4 byte or 8 byte values, the _nmem_ that is the number of values in the _args_
+parameter, a _size_ that is either 4 or 8 to denote the size of each value in _args_,
+and a _func_ string that is the name of the function to find the BTF information
+to use for parsing. If BTF is not loaded or the _func_ name is not found it
+will just print a hex value of all the _args_ into the _s_ descriptor.
+
+RETURN VALUE
+------------
+*tep_load_btf()* function returns 0 on success and the _raw_data_ is now
+owned by the _tep_ descriptor and will be freed when it is destroyed.
+It returns -1 on failure and the _raw_data_ will need to be freed by the caller.
+
+*tep_btf_print_args()* returns 0 on success and -1 on failure, which happens
+if the _size_ is not valid or the BTF file that was loaded is corrupted.
+
+EXAMPLE
+-------
+[source,c]
+--
+#include <event-parse.h>
+...
+int print_args(const char *func, unsigned long *args, int nr)
+{
+ struct tep_handle *tep;
+ struct trace_seq s;
+ struct stat st;
+ ssize_t r;
+ size_t tot = 0;
+ void *buf;
+ int fd;
+
+ if (!tep)
+ return -1;
+
+ fd = open("/sys/kernel/btf/vmlinux", O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ if (fstat(fd, &st) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ buf = malloc(st.st_size);
+ if (!buf) {
+ close(fd);
+ return -1;
+ }
+
+ while (tot < st.size) {
+ r = read(fd, buf + tot, st.st_size - tot);
+ if (r <= 0)
+ break;
+ tot += r;
+ }
+ close(fd);
+
+ tep = tep_alloc();
+ if (!tep) {
+ free(buf);
+ return -1;
+ }
+
+ if (tep_load_btf(tep, buf, tot) < 0) {
+ free(buf);
+ return -1;
+ }
+
+ /* Now buf belongs to tep and does not need to be freed */
+
+ trace_seq_init(&s);
+
+ printf("%s(", func);
+ tep_btf_print_args(tep, &s, args, nr, sizeof(long), func);
+
+ /* This will also free buf */
+ tep_free(tep);
+}
+...
+The above may output:
+
+ getname_flags(filename=0x7ffe7d33f3d0, flags=0)
+
+If BTF is loaded and the function was found, or it may show:
+
+ getname_flags(7ffe7d33f3d0, 0, 0, 0, 0, 0)
+
+If it was not.
+...
+--
+FILES
+-----
+[verse]
+--
+*event-parse.h*
+ Header file to include in order to have access to the library APIs.
+*-ltraceevent*
+ Linker switch to add when building a program that uses the library.
+--
+
+SEE ALSO
+--------
+*libtraceevent*(3), *trace-cmd*(1)
+
+AUTHOR
+------
+[verse]
+--
+*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*.
+*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page.
+--
+REPORTING BUGS
+--------------
+Report bugs to <linux-trace-devel@vger.kernel.org>
+
+LICENSE
+-------
+libtraceevent is Free Software licensed under the GNU LGPL 2.1
+
+RESOURCES
+---------
+https://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git/
diff --git a/Documentation/libtraceevent.txt b/Documentation/libtraceevent.txt
index 9e7777283c52..5fb5fc19ffc1 100644
--- a/Documentation/libtraceevent.txt
+++ b/Documentation/libtraceevent.txt
@@ -166,6 +166,11 @@ KVM plugin calllbacks: (Defined by the application and complied with -rdynamic)
unsigned long long pass:[*]paddr);
void *tep_plugin_kvm_put_func*(const char pass:[*]func);
+BTF parsing:
+ int *tep_load_btf*(struct tep_handle pass:[*]_tep_, void pass:[*]_raw_data_, size_t _data_size_);
+ int *tep_btf_print_args*(struct tep_handle pass:[*]_tep_, struct trace_seq pass:[*]_s_, void pass:[*]_args_,
+ int nmem, int size, const char pass:[*]_func_);
+
Trace sequences:
*#include <trace-seq.h>*
void *trace_seq_init*(struct trace_seq pass:[*]_s_);
--
2.47.2
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH 2/2] libtraceevent: Add man page for the new BTF functions
2025-07-31 18:52 ` [PATCH 2/2] libtraceevent: Add man page for the new BTF functions Steven Rostedt
@ 2025-08-04 11:09 ` Douglas Raillard
2025-08-04 12:32 ` Steven Rostedt
0 siblings, 1 reply; 14+ messages in thread
From: Douglas Raillard @ 2025-08-04 11:09 UTC (permalink / raw)
To: Steven Rostedt, linux-trace-devel
Cc: Masami Hiramatsu, Namhyung Kim, Takaya Saeki, Ian Rogers,
aahringo
On 31-07-2025 19:52, Steven Rostedt wrote:
> From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
>
> Add a man page for tep_load_btf() and tep_btf_print_args()
>
> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
> ---
> Documentation/libtraceevent-btf.txt | 158 ++++++++++++++++++++++++++++
> Documentation/libtraceevent.txt | 5 +
> 2 files changed, 163 insertions(+)
> create mode 100644 Documentation/libtraceevent-btf.txt
>
> diff --git a/Documentation/libtraceevent-btf.txt b/Documentation/libtraceevent-btf.txt
> new file mode 100644
> index 000000000000..802d953a6ef1
> --- /dev/null
> +++ b/Documentation/libtraceevent-btf.txt
> @@ -0,0 +1,158 @@
> +libtraceevent(3)
> +================
> +
> +NAME
> +----
> +tep_load_btf, tep_btf_print_args -
> +Load BTF file and use it to pretty print function arguments.
> +
> +SYNOPSIS
> +--------
> +[verse]
> +--
> +*#include <event-parse.h>*
> +
> +int *tep_load_btf*(struct tep_handle pass:[*]_tep_, void pass:[*]_raw_data_, size_t _data_size_);
> +int *tep_btf_print_args*(struct tep_handle pass:[*]_tep_, struct trace_seq pass:[*]_s_, void pass:[*]_args_,
> + int nmem, int size, const char pass:[*]_func_);
> +--
> +
> +DESCRIPTION
> +-----------
> +If the Linux kernel has BTF configured, then a binary file will exist
> +in the path of */sys/kernel/btf/vmlinux*. If this file is read into memory
> +and passed to *tep_load_btf()* function, then it will be used to parse
> +the arguments of a given function, if that function data is found within
> +the BTF file.
> +
> +The *tep_load_btf()* takes the _tep_ handle and will load the allocated _raw_data_
> +into it. NOTE, the _raw_data_ must be allocated via glibc() allocations as on
> +success, it will save the _raw_data_ internally, and will be freed when the
> +_tep_ handler is destroyed. If it is called twice, it will free the old _raw_data_
> +and reinitialize itself with the new _raw_data_. The _data_size_ should be set
> +to the size of the content in _raw_data_.
> +
> +The *tep_btf_print_args()* takes a _tep_ handle, a trace_seq _s_ pointer
> +(that was initialized by *trace_seq_init(3)*), an _args_ array that holds either
> +4 byte or 8 byte values, the _nmem_ that is the number of values in the _args_
> +parameter, a _size_ that is either 4 or 8 to denote the size of each value in _args_,
> +and a _func_ string that is the name of the function to find the BTF information
> +to use for parsing. If BTF is not loaded or the _func_ name is not found it
> +will just print a hex value of all the _args_ into the _s_ descriptor.
For clarity, it may be best to not call those things "args", as they may well not be args.
Before BTF is used to decode, this is merely the values of a bunch of registers assumed to
hold arguments value, with an unknown mapping of register value to actual args. For example,
both square() and square_struct() map the exact same assembly on arm64, clang 20 (tested with godbolt):
struct nums {
unsigned long num1;
unsigned long num2;
};
unsigned long square(unsigned long num1, unsigned long num2) {
return num1 * num2;
}
unsigned long square_struct(struct nums nums) {
return nums.num2 * nums.num1;
}
square:
mul x0, x1, x0
ret
square_struct:
mul x0, x1, x0
ret
If "int" is used instead of "unsigned long", you get an even more interesting non-1-1 mapping
where a single register is used to hold both 32 bit values, but only when the struct parameter
is used:
square_struct:
lsr x8, x0, #32
mul w0, w8, w0
ret
So there can be more arguments than register used, and also less arguments than register used.
On a somewhat related note, I realized while experimenting that the equivalent function in Rust
does not necessarily comply with the same ABI:
#[unsafe(no_mangle)]
pub fn square(num1: u32, num2: u32) -> u32 {
num1 * num2
}
struct Nums {
num1: u32,
num2: u32,
}
#[unsafe(no_mangle)]
pub fn square_struct(nums: Nums) -> u32 {
nums.num1 * nums.num2
}
square:
mul w0, w1, w0
ret
square_struct:
mul w0, w1, w0
ret
So both those functions have the same ABI, unlike the C version where the struct version
packs both nums in a single 64 bits register. While I haven't checked the Rust/kernel BTF
support, BTF itself currently only encodes AST-level information (with a handful of exceptions)
and the Rust struct version would likely be encoded the same way as the C variant. And yet
they both have different ABIs ...
Since Rust ABI is not stable, you can't assume assume anything in general, but even pretty basic
things like that can still differ with the C version. Unfortunately, BTF does not encode the language
used for each function, so libtraceevent will have to be blind.
> +RETURN VALUE
> +------------
> +*tep_load_btf()* function returns 0 on success and the _raw_data_ is now
> +owned by the _tep_ descriptor and will be freed when it is destroyed.
> +It returns -1 on failure and the _raw_data_ will need to be freed by the caller.
> +
> +*tep_btf_print_args()* returns 0 on success and -1 on failure, which happens
> +if the _size_ is not valid or the BTF file that was loaded is corrupted.
> +
> +EXAMPLE
> +-------
> +[source,c]
> +--
> +#include <event-parse.h>
> +...
> +int print_args(const char *func, unsigned long *args, int nr)
> +{
> + struct tep_handle *tep;
> + struct trace_seq s;
> + struct stat st;
> + ssize_t r;
> + size_t tot = 0;
> + void *buf;
> + int fd;
> +
> + if (!tep)
> + return -1;
> +
> + fd = open("/sys/kernel/btf/vmlinux", O_RDONLY);
> + if (fd < 0)
> + return -1;
> +
> + if (fstat(fd, &st) < 0) {
> + close(fd);
> + return -1;
> + }
> +
> + buf = malloc(st.st_size);
> + if (!buf) {
> + close(fd);
> + return -1;
> + }
> +
> + while (tot < st.size) {
> + r = read(fd, buf + tot, st.st_size - tot);
> + if (r <= 0)
> + break;
> + tot += r;
> + }
> + close(fd);
> +
> + tep = tep_alloc();
> + if (!tep) {
> + free(buf);
> + return -1;
> + }
> +
> + if (tep_load_btf(tep, buf, tot) < 0) {
> + free(buf);
> + return -1;
> + }
> +
> + /* Now buf belongs to tep and does not need to be freed */
> +
> + trace_seq_init(&s);
> +
> + printf("%s(", func);
> + tep_btf_print_args(tep, &s, args, nr, sizeof(long), func);
> +
> + /* This will also free buf */
> + tep_free(tep);
> +}
> +...
> +The above may output:
> +
> + getname_flags(filename=0x7ffe7d33f3d0, flags=0)
> +
> +If BTF is loaded and the function was found, or it may show:
> +
> + getname_flags(7ffe7d33f3d0, 0, 0, 0, 0, 0)
> +
> +If it was not.
> +...
> +--
> +FILES
> +-----
> +[verse]
> +--
> +*event-parse.h*
> + Header file to include in order to have access to the library APIs.
> +*-ltraceevent*
> + Linker switch to add when building a program that uses the library.
> +--
> +
> +SEE ALSO
> +--------
> +*libtraceevent*(3), *trace-cmd*(1)
> +
> +AUTHOR
> +------
> +[verse]
> +--
> +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*.
> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page.
> +--
> +REPORTING BUGS
> +--------------
> +Report bugs to <linux-trace-devel@vger.kernel.org>
> +
> +LICENSE
> +-------
> +libtraceevent is Free Software licensed under the GNU LGPL 2.1
> +
> +RESOURCES
> +---------
> +https://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git/
> diff --git a/Documentation/libtraceevent.txt b/Documentation/libtraceevent.txt
> index 9e7777283c52..5fb5fc19ffc1 100644
> --- a/Documentation/libtraceevent.txt
> +++ b/Documentation/libtraceevent.txt
> @@ -166,6 +166,11 @@ KVM plugin calllbacks: (Defined by the application and complied with -rdynamic)
> unsigned long long pass:[*]paddr);
> void *tep_plugin_kvm_put_func*(const char pass:[*]func);
>
> +BTF parsing:
> + int *tep_load_btf*(struct tep_handle pass:[*]_tep_, void pass:[*]_raw_data_, size_t _data_size_);
> + int *tep_btf_print_args*(struct tep_handle pass:[*]_tep_, struct trace_seq pass:[*]_s_, void pass:[*]_args_,
> + int nmem, int size, const char pass:[*]_func_);
> +
> Trace sequences:
> *#include <trace-seq.h>*
> void *trace_seq_init*(struct trace_seq pass:[*]_s_);
--
Douglas
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle
2025-07-31 18:52 ` [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle Steven Rostedt
@ 2025-08-04 11:47 ` Douglas Raillard
2025-08-04 12:43 ` Steven Rostedt
2025-08-04 16:07 ` Douglas Raillard
1 sibling, 1 reply; 14+ messages in thread
From: Douglas Raillard @ 2025-08-04 11:47 UTC (permalink / raw)
To: Steven Rostedt, linux-trace-devel
Cc: Masami Hiramatsu, Namhyung Kim, Takaya Saeki, Ian Rogers,
aahringo
On 31-07-2025 19:52, Steven Rostedt wrote:
> From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
>
> Add loading of the BTF file /sys/kernel/btf/vmlinux into the tep_handler and
> then use it to format the function tracer if it has arguments:
>
> cat-33501 [005] ..... 136155.767950: function: __x64_sys_execve(regs=0xffffc9000e3eff58)
> cat-33501 [005] ..... 136155.767951: function: getname_flags(filename=0x7ffe7d33f3d0, flags=0)
> cat-33501 [005] ..... 136155.767951: function: getname_flags.part.0(7ffe7d33f3d0, 0, 0, 0, 0, 0)
> cat-33501 [005] ..... 136155.767951: function: kmem_cache_alloc_noprof(s=0xffff8881001d3800, gfpflags=0xcc0)
> cat-33501 [005] ..... 136155.767951: function: fs_reclaim_acquire(gfp_mask=0xcc0)
> cat-33501 [005] ..... 136155.767952: function: fs_reclaim_release(gfp_mask=0xcc0)
> cat-33501 [005] ..... 136155.767953: function: kmemleak_alloc(ptr=0xffff8881114a0000, size=0x1000, min_count=1, gfp=0xcc0)
> cat-33501 [005] ..... 136155.767953: function: __create_object(ptr=0xffff8881114a0000, size=0x1000, min_count=1, gfp=0xcc0, objflags=0x0)
> cat-33501 [005] ..... 136155.767954: function: __alloc_object(gfp=0xcc0)
> cat-33501 [005] ..... 136155.767954: function: kmem_cache_alloc_noprof(s=0xffff888100045700, gfpflags=0x92cc0)
> cat-33501 [005] ..... 136155.767954: function: fs_reclaim_acquire(gfp_mask=0x92cc0)
> cat-33501 [005] ..... 136155.767955: function: fs_reclaim_release(gfp_mask=0x92cc0)
>
> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
> ---
> include/traceevent/event-parse.h | 5 +
> plugins/plugin_function.c | 15 +-
> src/Makefile | 1 +
> src/event-parse-local.h | 6 +
> src/event-parse.c | 2 +
> src/trace-btf.c | 449 +++++++++++++++++++++++++++++++
> 6 files changed, 468 insertions(+), 10 deletions(-)
> create mode 100644 src/trace-btf.c
>
> diff --git a/include/traceevent/event-parse.h b/include/traceevent/event-parse.h
> index d3ce0a43a8fc..ebfc2c7abac2 100644
> --- a/include/traceevent/event-parse.h
> +++ b/include/traceevent/event-parse.h
> @@ -580,6 +580,11 @@ int tep_get_ref(struct tep_handle *tep);
>
> struct kbuffer *tep_kbuffer(struct tep_handle *tep);
>
> +/* BTF */
> +int tep_load_btf(struct tep_handle *tep, void *raw_data, size_t data_size);
> +int tep_btf_print_args(struct tep_handle *tep, struct trace_seq *s, void *args,
> + int nmem, int size, const char *func);
> +
> /* for debugging */
> void tep_print_funcs(struct tep_handle *tep);
> void tep_print_printk(struct tep_handle *tep);
> diff --git a/plugins/plugin_function.c b/plugins/plugin_function.c
> index a337446e3462..379fc3ad0ffd 100644
> --- a/plugins/plugin_function.c
> +++ b/plugins/plugin_function.c
> @@ -135,10 +135,9 @@ static void show_function(struct trace_seq *s, struct tep_handle *tep,
>
> /* Returns true if it printed args, otherwise it returns false */
> static bool print_args(struct trace_seq *s, struct tep_event *event,
> - struct tep_record *record)
> + struct tep_record *record, const char *func)
> {
> struct tep_format_field *field;
> - unsigned long arg;
> void *args;
> int len;
>
> @@ -156,12 +155,8 @@ static bool print_args(struct trace_seq *s, struct tep_event *event,
>
> trace_seq_putc(s, '(');
>
> - for (int i = 0; i < len; i += sizeof(long), args += sizeof(long)) {
> - memcpy(&arg, args, sizeof(long));
> - trace_seq_printf(s, "%lx", arg);
> - if (i + sizeof(long) < len)
> - trace_seq_puts(s, ", ");
> - }
> + tep_btf_print_args(event->tep, s, args, len / sizeof(long), sizeof(long), func);
> +
> trace_seq_putc(s, ')');
> return true;
> }
> @@ -196,7 +191,7 @@ static int function_handler(struct trace_seq *s, struct tep_record *record,
> else
> trace_seq_printf(s, "0x%llx", function);
>
> - print_args(s, event, record);
> + print_args(s, event, record, func);
>
> if (ftrace_parent->set) {
> trace_seq_printf(s, " <-- ");
> @@ -281,7 +276,7 @@ trace_stack_handler(struct trace_seq *s, struct tep_record *record,
>
> static int
> trace_raw_data_handler(struct trace_seq *s, struct tep_record *record,
> - struct tep_event *event, void *context)
> + struct tep_event *event, void *context)
> {
> struct tep_format_field *field;
> unsigned long long id;
> diff --git a/src/Makefile b/src/Makefile
> index 53bb570182d2..1ff669ce8deb 100644
> --- a/src/Makefile
> +++ b/src/Makefile
> @@ -11,6 +11,7 @@ OBJS += parse-filter.o
> OBJS += parse-utils.o
> OBJS += tep_strerror.o
> OBJS += trace-seq.o
> +OBJS += trace-btf.o
>
> OBJS := $(OBJS:%.o=$(bdir)/%.o)
> DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d)
> diff --git a/src/event-parse-local.h b/src/event-parse-local.h
> index d9e9faf649d1..50bc4d87bbe6 100644
> --- a/src/event-parse-local.h
> +++ b/src/event-parse-local.h
> @@ -14,6 +14,7 @@ struct func_list;
> struct event_handler;
> struct func_resolver;
> struct tep_plugins_dir;
> +struct tep_btf;
>
> #define __hidden __attribute__((visibility ("hidden")))
>
> @@ -89,6 +90,8 @@ struct tep_handle {
> const char *input_buf;
> unsigned long long input_buf_ptr;
> unsigned long long input_buf_siz;
> +
> + struct tep_btf *btf;
> };
>
> enum tep_print_parse_type {
> @@ -124,4 +127,7 @@ const char *tep_get_input_buf(struct tep_handle *tep);
> enum tep_event_type tep_read_token(struct tep_handle *tep, char **tok);
> void tep_free_token(char *tok);
>
> +/* BTF routines */
> +void tep_btf_free(struct tep_btf *btf);
> +
> #endif /* _PARSE_EVENTS_INT_H */
> diff --git a/src/event-parse.c b/src/event-parse.c
> index aa16d83bb2ad..730f25c92110 100644
> --- a/src/event-parse.c
> +++ b/src/event-parse.c
> @@ -8726,6 +8726,8 @@ void tep_free(struct tep_handle *tep)
> free(tep->func_resolver);
> tep_free_plugin_paths(tep);
>
> + tep_btf_free(tep->btf);
> +
> free(tep);
> }
>
> diff --git a/src/trace-btf.c b/src/trace-btf.c
> new file mode 100644
> index 000000000000..79c5d6c93327
> --- /dev/null
> +++ b/src/trace-btf.c
> @@ -0,0 +1,449 @@
> +// SPDX-License-Identifier: LGPL-2.1
> +/*
> + * Copyright (C) 2025 Google, Steven Rostedt <rostedt@goodmis.org>
> + *
> + */
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <stdbool.h>
> +
> +#include <linux/btf.h>
> +
> +#include "event-parse.h"
> +#include "event-utils.h"
> +#include "event-parse-local.h"
> +
> +struct btf_header;
> +struct btf_type;
> +
> +struct tep_btf {
> + struct btf_header *hdr;
> + const char *strings;
> + struct btf_type **types;
> + unsigned long nr_types;
> + struct btf_type **funcs;
> + unsigned long nr_funcs;
> + void *data;
> + size_t raw_size;
> + void *raw_data;
> +};
> +
> +#define REALLOC_SIZE (1 << 10)
> +#define REALLOC_MASK (REALLOC_SIZE - 1)
> +
> +static const char *btf_name(struct tep_btf *btf, int off)
> +{
> + if (off < btf->hdr->str_len)
> + return btf->strings + off;
> + return "";
> +}
> +
> +/* List taken from the Linux kernel */
> +static const char * const btf_kind_str[NR_BTF_KINDS] = {
> + [BTF_KIND_UNKN] = "UNKNOWN",
> + [BTF_KIND_INT] = "INT",
> + [BTF_KIND_PTR] = "PTR",
> + [BTF_KIND_ARRAY] = "ARRAY",
> + [BTF_KIND_STRUCT] = "STRUCT",
> + [BTF_KIND_UNION] = "UNION",
> + [BTF_KIND_ENUM] = "ENUM",
> + [BTF_KIND_FWD] = "FWD",
> + [BTF_KIND_TYPEDEF] = "TYPEDEF",
> + [BTF_KIND_VOLATILE] = "VOLATILE",
> + [BTF_KIND_CONST] = "CONST",
> + [BTF_KIND_RESTRICT] = "RESTRICT",
> + [BTF_KIND_FUNC] = "FUNC",
> + [BTF_KIND_FUNC_PROTO] = "FUNC_PROTO",
> + [BTF_KIND_VAR] = "VAR",
> + [BTF_KIND_DATASEC] = "DATASEC",
> + [BTF_KIND_FLOAT] = "FLOAT",
> + [BTF_KIND_DECL_TAG] = "DECL_TAG",
> + [BTF_KIND_TYPE_TAG] = "TYPE_TAG",
> + [BTF_KIND_ENUM64] = "ENUM64",
> +};
> +
> +static const char *btf_type_str(const struct btf_type *t)
> +{
> + return btf_kind_str[BTF_INFO_KIND(t->info)];
> +}
> +
> +static int insert_type(struct btf_type ***types, unsigned long *cnt, struct btf_type *type)
> +{
> + unsigned long nr_types = *cnt;
> + struct btf_type **array = *types;
> +
> + if (!(nr_types & REALLOC_MASK)) {
> + int size = nr_types + REALLOC_SIZE;
> +
> + array = realloc(array, sizeof(struct btf_type *) * size);
> + if (!array) {
> + tep_warning("Failed to alloct memory for new type");
> + return -1;
> + }
> + *types = array;
> + }
> +
> + array[nr_types++] = type;
> + *cnt = nr_types;
> + return 0;
> +}
> +
> +static int add_type(struct tep_btf *btf, struct btf_type *type)
> +{
> + return insert_type(&btf->types, &btf->nr_types, type);
> +}
> +
> +static int add_func(struct tep_btf *btf, struct btf_type *type)
> +{
> + return insert_type(&btf->funcs, &btf->nr_funcs, type);
> +}
> +
> +static int btf_type_size(struct btf_type *type)
> +{
> + int kind = BTF_INFO_KIND(type->info);
> + int size = sizeof(*type);
> +
> + switch (kind) {
> + case BTF_KIND_INT:
> + return size + sizeof(int);
> + case BTF_KIND_VAR:
> + return size + sizeof(struct btf_var);
> + case BTF_KIND_ARRAY:
> + return size + sizeof(struct btf_array);
> + case BTF_KIND_DECL_TAG:
> + return size + sizeof(struct btf_decl_tag);
> + case BTF_KIND_ENUM:
> + return size + sizeof(struct btf_enum) * BTF_INFO_VLEN(type->info);
> + case BTF_KIND_ENUM64:
> + return size + sizeof(struct btf_enum64) * BTF_INFO_VLEN(type->info);
> + case BTF_KIND_STRUCT:
> + case BTF_KIND_UNION:
> + return size + sizeof(struct btf_member) * BTF_INFO_VLEN(type->info);
> + case BTF_KIND_FUNC_PROTO:
> + return size + sizeof(struct btf_param) * BTF_INFO_VLEN(type->info);
> + case BTF_KIND_DATASEC:
> + return size + sizeof(struct btf_var_secinfo) * BTF_INFO_VLEN(type->info);
> + case BTF_KIND_PTR:
> + case BTF_KIND_FWD:
> + case BTF_KIND_TYPEDEF:
> + case BTF_KIND_VOLATILE:
> + case BTF_KIND_CONST:
> + case BTF_KIND_RESTRICT:
> + case BTF_KIND_TYPE_TAG:
> + case BTF_KIND_FUNC:
> + case BTF_KIND_FLOAT:
> + return size;
> + }
> + return -1;
> +}
> +
> +static int cmp_funcs(const void *A, const void *B, void *data)
> +{
> + struct tep_btf *btf = data;
> + const struct btf_type *a = *(const struct btf_type **)A;
> + const struct btf_type *b = *(const struct btf_type **)B;
> + const char *name_a = btf_name(btf, a->name_off);
> + const char *name_b = btf_name(btf, b->name_off);
> +
> + return strcmp(name_a, name_b);
> +}
> +
> +struct tcmd_search {
> + struct tep_btf *btf;
> + const char *name;
> +};
> +
> +static int cmp_key_func(const void *A, const void *B)
> +{
> + const struct tcmd_search *key = A;
> + const struct btf_type *b = *(const struct btf_type **)B;
> + const char *name_b = btf_name(key->btf, b->name_off);
> +
> + return strcmp(key->name, name_b);
> +}
> +
> +static struct btf_type *tep_btf_find_func(struct tep_btf *btf, const char *name)
> +{
> + struct tcmd_search tsearch;
> + struct btf_type **t;
> +
> + if (!btf || !name)
> + return NULL;
> +
> + tsearch.btf = btf;
> + tsearch.name = name;
> +
> + t = bsearch(&tsearch, btf->funcs, btf->nr_funcs, sizeof(btf->funcs[0]),
> + cmp_key_func);
> +
> + return t ? *t : NULL;
> +}
> +
> +static int load_types(struct tep_btf *btf)
> +{
> + struct btf_type *type;
> + void *start, *end;
> + int size;
> +
> + start = btf->data + btf->hdr->type_off;
> + end = start + btf->hdr->type_len;
> +
> + if (end > btf->raw_data + btf->raw_size)
> + return -1;
> +
> + for (type = start; (void *)type < end;) {
> + if (add_type(btf, type))
> + return -1;
> + if (BTF_INFO_KIND(type->info) == BTF_KIND_FUNC) {
> + if (add_func(btf, type))
> + return -1;
> + }
> + size = btf_type_size(type);
> + if (size < 0) {
> + tep_warning("Invalid type %d\n", BTF_INFO_KIND(type->info));
> + return -1;
> + }
> + type = (void *)type + size;
> + }
> +
> + qsort_r(btf->funcs, btf->nr_funcs, sizeof(btf->funcs[0]), cmp_funcs, btf);
> + return 0;
> +}
> +
> +__hidden void tep_btf_free(struct tep_btf *btf)
> +{
> + if (!btf)
> + return;
> +
> + free((void *)btf->strings);
> + free(btf->types);
> + free(btf->funcs);
> + free(btf->raw_data);
> +}
> +
> +/** tep_btf_init - Initialize btf with raw data
> + * @raw_data: Allocated data to use for BTF (/sys/kernel/btf/vmlinux)
> + * @data_size: The amount of data in @raw_data
> + *
> + * The @raw_data ownership belongs to this function when called.
> + * It must be allocated by the glibc allocator as it will be freed
> + * when the BTF descriptor is freed. It will not be freed if this
> + * function returns failure (NULL).
> + *
> + * Returns: On success it returns the BTF descriptor, and he @raw_data
> + * will now be apart of it (it will be freed by tep_btf_free().
> + * On failure it returns NULL, and the @raw_data is NOT freed.
> + */
> +static struct tep_btf *tep_btf_init(void *raw_data, size_t data_size)
> +{
> + struct tep_btf *btf;
> +
> + btf = calloc(1, sizeof(*btf));
> + if (!btf)
> + return NULL;
> +
> + btf->raw_data = raw_data;
> + btf->raw_size = data_size;
> + btf->hdr = raw_data;
> + if (btf->hdr->hdr_len < sizeof(*btf->hdr)) {
> + tep_warning("Header (%d) smaller than expected header %zd",
> + btf->hdr->hdr_len, sizeof(*btf->hdr));
> + goto fail;
> + }
> +
> + btf->data = btf->raw_data + btf->hdr->hdr_len;
> +
> + btf->strings = btf->data + btf->hdr->str_off;
> + printf("str len = %d\n", btf->hdr->str_len);
> +
> + if (load_types(btf) < 0)
> + goto fail;
> +
> + return btf;
> + fail:
> + btf->raw_data = NULL;
> + tep_btf_free(btf);
> + return NULL;
> +}
> +
> +/**
> + * tep_load_btf - Load BTF information into a tep handle
> + * @tep: The tep handle to load the BTF info into
> + * @raw_data: The raw data containing the BTF file
> + * @data_size: The amount of data in @raw_data
> + *
> + * Initializes BTF into the @tep handle. If it had already had BTF
> + * loaded, it will free the previous BTF and recreate it from @raw_data.
> + *
> + * Returns: 0 on success and -1 on failure.
> + */
> +int tep_load_btf(struct tep_handle *tep, void *raw_data, size_t data_size)
> +{
> + /* If btf was already loaded, free it */
> + tep_btf_free(tep->btf);
> +
> + tep->btf = tep_btf_init(raw_data, data_size);
> + if (!tep->btf)
> + return -1;
> + return 0;
> +}
> +
> +static struct btf_type *btf_get_type(struct tep_btf *btf, int id)
> +{
> + if (!id || id > btf->nr_types)
> + return NULL;
> +
> + return btf->types[id - 1];
> +}
> +
> +static struct btf_type *btf_skip_modifiers(struct tep_btf *btf, int id)
> +{
> + struct btf_type *t = btf_get_type(btf, id);
> +
> + for (;;) {
> + switch (BTF_INFO_KIND(t->info)) {
> + case BTF_KIND_TYPEDEF:
> + case BTF_KIND_VOLATILE:
> + case BTF_KIND_CONST:
> + case BTF_KIND_RESTRICT:
> + case BTF_KIND_TYPE_TAG:
> + id = t->type;
> + t = btf_get_type(btf, t->type);
> + continue;
> + }
> + break;
> + }
> +
> + return t;
> +}
> +
> +static void assign_arg(unsigned long long *arg, void *args, int size, int a)
> +{
> + *arg = size == 4 ?
> + *(unsigned int *)(args + a * sizeof(int)) :
> + *(unsigned long long *)(args + a * sizeof(long long));
> +}
> +
> +/**
> + * tep_btf_print_args - Print function arguments from BTF info
> + * @tep: The tep descriptor to use
> + * @s: The trace_seq to write the arguments into
> + * @args: The array that holds the arguments
> + * @nmem: The number of arguments
> + * @size: The size of each item in args (4 or 8).
> + * @func: The name of the function.
> + *
> + * Loads up the @s with a list of arguments for the function based on
> + * the @args.
> + *
> + * If there's no BTF loaded or @func is not found, then it just writes
> + * the @args as raw numbers. Otherwise it will pretty print the
> + * arguments based on the function info in BTF.
> + *
> + * Returns: 0 on success and -1 on failure.
> + */
> +int tep_btf_print_args(struct tep_handle *tep, struct trace_seq *s, void *args,
> + int nmem, int size, const char *func)
> +{
> + struct tep_btf *btf = tep->btf;
> + struct btf_type *type = tep_btf_find_func(btf, func);
> + struct btf_param *param;
> + unsigned long long arg;
> + unsigned int encode;
> + const char *param_name;
> + int a, p, x, nr;
> +
> + if (size != 4 && size != 8)
> + return -1;
> +
> + if (!type) {
> + for (int i = 0; i < nmem; i++) {
> + assign_arg(&arg, args, size, i);
> + trace_seq_printf(s, "%llx", arg);
> + if (i + 1 < nmem)
> + trace_seq_puts(s, ", ");
> + }
> + return 0;
> + }
> +
> + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC) {
> + printf("Invalid func type %d %s\n", BTF_INFO_KIND(type->info),
> + btf_type_str(type));
> + return -1;
> + }
> +
> + /* Get the function proto */
> + type = btf_get_type(btf, type->type);
> +
> + /* No proto means "()" ? */
> + if (!type)
> + return 0;
> +
> + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC_PROTO) {
> + printf("Invalid func proto type %d %s\n", BTF_INFO_KIND(type->info),
> + btf_type_str(type));
> + return -1;
> + }
> +
> + /* Get the number of parameters */
> + nr = BTF_INFO_VLEN(type->info);
> +
> + /* The parameters are right after the FUNC_PROTO type */
> + param = ((void *)type) + sizeof(*type);
> +
> + for (a = 0, p = 0; p < nr; a++, p++) {
> + struct btf_type *t;
> +
> + if (p)
> + trace_seq_puts(s, ", ");
> +
> + if (a == nmem) {
> + trace_seq_puts(s, "...");
> + break;
> + }
> +
> + assign_arg(&arg, args, size, a);
> +
> + param_name = btf_name(btf, param[p].name_off);
> + if (param_name)
> + trace_seq_printf(s, "%s=", param_name);
> +
> + t = btf_skip_modifiers(btf, param[p].type);
> +
> + switch (t ? BTF_INFO_KIND(t->info) : BTF_KIND_UNKN) {
> + case BTF_KIND_UNKN:
> + trace_seq_putc(s, '?');
> + /* Still print unknown type values */
> + /* fallthough */
> + case BTF_KIND_PTR:
> + trace_seq_printf(s, "0x%llx", arg);
> + break;
> + case BTF_KIND_INT:
> + encode = *(int *)((void *)t + sizeof(*t));
> + /* Print unsigned ints as hex */
> + if (BTF_INT_ENCODING(encode) & BTF_INT_SIGNED)
BTF_INT_OFFSET() and BTF_INT_VAL() values should also be used to shift and mask
appropriately.
I assume that regardless of BTF_INT_CHAR and BTF_INT_BOOL value, BTF_INT_SIGNED is
set appropriately.
> + trace_seq_printf(s, "%lld", arg);
> + else
> + trace_seq_printf(s, "0x%llx", arg);
> + break;
> + case BTF_KIND_ENUM:
Could add as well: case BTF_KIND_ENUM64:
> + trace_seq_printf(s, "%lld", arg);
This could probably be improved to display the enum variant name, with the caveats that:
1. C allows values that are not that of any enumerator AFAIR, so you'd need the
raw int display fallback.
2. GNU C allows enum forward declaration. This is not ISO C and BTF has no specific
representation for that. As a result, last time I checked you end up with
BTF_KIND_ENUM and info.vlen == 0. Another entry with info.vlen != 0 should
also exist and give the actual enumerators list. Those forward decl provide an
incomplete type though, so you should not encounter it directly in function
parameters.
> + break;
> + default:
> + /* This does not handle complex arguments */
> + trace_seq_printf(s, "(%s)[0x%llx", btf_type_str(t), arg);
> + for (x = sizeof(long); x < t->size; x += sizeof(long)) {
> + trace_seq_putc(s, ':');
> + if (++a == nmem) {
> + trace_seq_puts(s, "...]");
> + return 0;
> + }
> + assign_arg(&arg, args, size, a);
> + trace_seq_printf(s, "0x%llx", arg);
> + }
> + trace_seq_putc(s, ']');
> + break;
> + }
> + }
> + return 0;
> +}
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 2/2] libtraceevent: Add man page for the new BTF functions
2025-08-04 11:09 ` Douglas Raillard
@ 2025-08-04 12:32 ` Steven Rostedt
2025-08-04 13:19 ` Douglas Raillard
0 siblings, 1 reply; 14+ messages in thread
From: Steven Rostedt @ 2025-08-04 12:32 UTC (permalink / raw)
To: Douglas Raillard
Cc: linux-trace-devel, Masami Hiramatsu, Namhyung Kim, Takaya Saeki,
Ian Rogers, aahringo
On Mon, 4 Aug 2025 12:09:09 +0100
Douglas Raillard <douglas.raillard@arm.com> wrote:
> > +The *tep_btf_print_args()* takes a _tep_ handle, a trace_seq _s_ pointer
> > +(that was initialized by *trace_seq_init(3)*), an _args_ array that holds either
> > +4 byte or 8 byte values, the _nmem_ that is the number of values in the _args_
> > +parameter, a _size_ that is either 4 or 8 to denote the size of each value in _args_,
> > +and a _func_ string that is the name of the function to find the BTF information
> > +to use for parsing. If BTF is not loaded or the _func_ name is not found it
> > +will just print a hex value of all the _args_ into the _s_ descriptor.
>
> For clarity, it may be best to not call those things "args", as they may well not be args.
> Before BTF is used to decode, this is merely the values of a bunch of registers assumed to
> hold arguments value, with an unknown mapping of register value to actual args. For example,
> both square() and square_struct() map the exact same assembly on arm64, clang 20 (tested with godbolt):
>
I can update the description but I still want to keep "args" as they are
the arg elements that were recorded from the function tracer.
They may not be registers, even though they are now. For instance, on i386,
only the first three word size arguments are in registers. The rest comes
from the stack.
But I agree that the description should point out that the _args_ are the
function argument elements that were passed to the function call and are
used to build the function parameters that the program may see.
>
> If "int" is used instead of "unsigned long", you get an even more interesting non-1-1 mapping
> where a single register is used to hold both 32 bit values, but only when the struct parameter
> is used:
>
> square_struct:
> lsr x8, x0, #32
> mul w0, w8, w0
> ret
>
> So there can be more arguments than register used, and also less arguments than register used.
Correct. But still, I would not call them registers as they may be from the
stack. I'll stick to calling them _args_ but refer to them in the
documentation as "argument elements" as they are the building blocks of the
parameters that are used.
Thanks for the review.
-- Steve
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle
2025-08-04 11:47 ` Douglas Raillard
@ 2025-08-04 12:43 ` Steven Rostedt
2025-08-04 14:18 ` Douglas Raillard
0 siblings, 1 reply; 14+ messages in thread
From: Steven Rostedt @ 2025-08-04 12:43 UTC (permalink / raw)
To: Douglas Raillard
Cc: linux-trace-devel, Masami Hiramatsu, Namhyung Kim, Takaya Saeki,
Ian Rogers, aahringo
On Mon, 4 Aug 2025 12:47:31 +0100
Douglas Raillard <douglas.raillard@arm.com> wrote:
> > +int tep_btf_print_args(struct tep_handle *tep, struct trace_seq *s, void *args,
> > + int nmem, int size, const char *func)
> > +{
> > + struct tep_btf *btf = tep->btf;
> > + struct btf_type *type = tep_btf_find_func(btf, func);
> > + struct btf_param *param;
> > + unsigned long long arg;
> > + unsigned int encode;
> > + const char *param_name;
> > + int a, p, x, nr;
> > +
> > + if (size != 4 && size != 8)
> > + return -1;
> > +
> > + if (!type) {
> > + for (int i = 0; i < nmem; i++) {
> > + assign_arg(&arg, args, size, i);
> > + trace_seq_printf(s, "%llx", arg);
> > + if (i + 1 < nmem)
> > + trace_seq_puts(s, ", ");
> > + }
> > + return 0;
> > + }
> > +
> > + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC) {
> > + printf("Invalid func type %d %s\n", BTF_INFO_KIND(type->info),
> > + btf_type_str(type));
> > + return -1;
> > + }
> > +
> > + /* Get the function proto */
> > + type = btf_get_type(btf, type->type);
> > +
> > + /* No proto means "()" ? */
> > + if (!type)
> > + return 0;
> > +
> > + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC_PROTO) {
> > + printf("Invalid func proto type %d %s\n", BTF_INFO_KIND(type->info),
> > + btf_type_str(type));
> > + return -1;
> > + }
> > +
> > + /* Get the number of parameters */
> > + nr = BTF_INFO_VLEN(type->info);
> > +
> > + /* The parameters are right after the FUNC_PROTO type */
> > + param = ((void *)type) + sizeof(*type);
> > +
> > + for (a = 0, p = 0; p < nr; a++, p++) {
> > + struct btf_type *t;
> > +
> > + if (p)
> > + trace_seq_puts(s, ", ");
> > +
> > + if (a == nmem) {
> > + trace_seq_puts(s, "...");
> > + break;
> > + }
> > +
> > + assign_arg(&arg, args, size, a);
> > +
> > + param_name = btf_name(btf, param[p].name_off);
> > + if (param_name)
> > + trace_seq_printf(s, "%s=", param_name);
> > +
> > + t = btf_skip_modifiers(btf, param[p].type);
> > +
> > + switch (t ? BTF_INFO_KIND(t->info) : BTF_KIND_UNKN) {
> > + case BTF_KIND_UNKN:
> > + trace_seq_putc(s, '?');
> > + /* Still print unknown type values */
> > + /* fallthough */
> > + case BTF_KIND_PTR:
> > + trace_seq_printf(s, "0x%llx", arg);
> > + break;
> > + case BTF_KIND_INT:
> > + encode = *(int *)((void *)t + sizeof(*t));
> > + /* Print unsigned ints as hex */
> > + if (BTF_INT_ENCODING(encode) & BTF_INT_SIGNED)
>
> BTF_INT_OFFSET() and BTF_INT_VAL() values should also be used to shift and mask
> appropriately.
You meant to the "arg" passed in?
>
> I assume that regardless of BTF_INT_CHAR and BTF_INT_BOOL value, BTF_INT_SIGNED is
> set appropriately.
>
> > + trace_seq_printf(s, "%lld", arg);
> > + else
> > + trace_seq_printf(s, "0x%llx", arg);
> > + break;
> > + case BTF_KIND_ENUM:
>
> Could add as well: case BTF_KIND_ENUM64:
Of course then we would need to check if this is a 32 bit infrastructure.
I'm assuming it would be treated differently. More like a struct?
>
> > + trace_seq_printf(s, "%lld", arg);
>
> This could probably be improved to display the enum variant name, with the caveats that:
If btf has it, sure! But that can come later.
Thanks,
-- Steve
>
> 1. C allows values that are not that of any enumerator AFAIR, so you'd need the
> raw int display fallback.
> 2. GNU C allows enum forward declaration. This is not ISO C and BTF has no specific
> representation for that. As a result, last time I checked you end up with
> BTF_KIND_ENUM and info.vlen == 0. Another entry with info.vlen != 0 should
> also exist and give the actual enumerators list. Those forward decl provide an
> incomplete type though, so you should not encounter it directly in function
> parameters.
>
> > + break;
>
> > + default:
> > + /* This does not handle complex arguments */
> > + trace_seq_printf(s, "(%s)[0x%llx", btf_type_str(t), arg);
> > + for (x = sizeof(long); x < t->size; x += sizeof(long)) {
> > + trace_seq_putc(s, ':');
> > + if (++a == nmem) {
> > + trace_seq_puts(s, "...]");
> > + return 0;
> > + }
> > + assign_arg(&arg, args, size, a);
> > + trace_seq_printf(s, "0x%llx", arg);
> > + }
> > + trace_seq_putc(s, ']');
> > + break;
> > + }
> > + }
> > + return 0;
> > +}
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 2/2] libtraceevent: Add man page for the new BTF functions
2025-08-04 12:32 ` Steven Rostedt
@ 2025-08-04 13:19 ` Douglas Raillard
0 siblings, 0 replies; 14+ messages in thread
From: Douglas Raillard @ 2025-08-04 13:19 UTC (permalink / raw)
To: Steven Rostedt
Cc: linux-trace-devel, Masami Hiramatsu, Namhyung Kim, Takaya Saeki,
Ian Rogers, aahringo
On 04-08-2025 13:32, Steven Rostedt wrote:
> On Mon, 4 Aug 2025 12:09:09 +0100
> Douglas Raillard <douglas.raillard@arm.com> wrote:
>
>>> +The *tep_btf_print_args()* takes a _tep_ handle, a trace_seq _s_ pointer
>>> +(that was initialized by *trace_seq_init(3)*), an _args_ array that holds either
>>> +4 byte or 8 byte values, the _nmem_ that is the number of values in the _args_
>>> +parameter, a _size_ that is either 4 or 8 to denote the size of each value in _args_,
>>> +and a _func_ string that is the name of the function to find the BTF information
>>> +to use for parsing. If BTF is not loaded or the _func_ name is not found it
>>> +will just print a hex value of all the _args_ into the _s_ descriptor.
>>
>> For clarity, it may be best to not call those things "args", as they may well not be args.
>> Before BTF is used to decode, this is merely the values of a bunch of registers assumed to
>> hold arguments value, with an unknown mapping of register value to actual args. For example,
>> both square() and square_struct() map the exact same assembly on arm64, clang 20 (tested with godbolt):
>>
>
> I can update the description but I still want to keep "args" as they are
> the arg elements that were recorded from the function tracer.
>
> They may not be registers, even though they are now. For instance, on i386,
> only the first three word size arguments are in registers. The rest comes
> from the stack.
>
> But I agree that the description should point out that the _args_ are the
> function argument elements that were passed to the function call and are
> used to build the function parameters that the program may see.
>
>>
>> If "int" is used instead of "unsigned long", you get an even more interesting non-1-1 mapping
>> where a single register is used to hold both 32 bit values, but only when the struct parameter
>> is used:
>>
>> square_struct:
>> lsr x8, x0, #32
>> mul w0, w8, w0
>> ret
>>
>> So there can be more arguments than register used, and also less arguments than register used.
>
> Correct. But still, I would not call them registers as they may be from the
> stack. I'll stick to calling them _args_ but refer to them in the
> documentation as "argument elements" as they are the building blocks of the
> parameters that are used.
That makes sense. Encoding and decoding of that data will then evolve in fairly arch-specific
and lang-specific ways. Maybe this format should be versioned somewhere to allow future expansion
without breaking change ?
>
> Thanks for the review.
>
> -- Steve
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle
2025-08-04 12:43 ` Steven Rostedt
@ 2025-08-04 14:18 ` Douglas Raillard
2025-08-04 14:41 ` Steven Rostedt
0 siblings, 1 reply; 14+ messages in thread
From: Douglas Raillard @ 2025-08-04 14:18 UTC (permalink / raw)
To: Steven Rostedt
Cc: linux-trace-devel, Masami Hiramatsu, Namhyung Kim, Takaya Saeki,
Ian Rogers, aahringo
On 04-08-2025 13:43, Steven Rostedt wrote:
> On Mon, 4 Aug 2025 12:47:31 +0100
> Douglas Raillard <douglas.raillard@arm.com> wrote:
>
>>> +int tep_btf_print_args(struct tep_handle *tep, struct trace_seq *s, void *args,
>>> + int nmem, int size, const char *func)
>>> +{
>>> + struct tep_btf *btf = tep->btf;
>>> + struct btf_type *type = tep_btf_find_func(btf, func);
>>> + struct btf_param *param;
>>> + unsigned long long arg;
>>> + unsigned int encode;
>>> + const char *param_name;
>>> + int a, p, x, nr;
>>> +
>>> + if (size != 4 && size != 8)
>>> + return -1;
>>> +
>>> + if (!type) {
>>> + for (int i = 0; i < nmem; i++) {
>>> + assign_arg(&arg, args, size, i);
>>> + trace_seq_printf(s, "%llx", arg);
>>> + if (i + 1 < nmem)
>>> + trace_seq_puts(s, ", ");
>>> + }
>>> + return 0;
>>> + }
>>> +
>>> + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC) {
>>> + printf("Invalid func type %d %s\n", BTF_INFO_KIND(type->info),
>>> + btf_type_str(type));
>>> + return -1;
>>> + }
>>> +
>>> + /* Get the function proto */
>>> + type = btf_get_type(btf, type->type);
>>> +
>>> + /* No proto means "()" ? */
>>> + if (!type)
>>> + return 0;
>>> +
>>> + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC_PROTO) {
>>> + printf("Invalid func proto type %d %s\n", BTF_INFO_KIND(type->info),
>>> + btf_type_str(type));
>>> + return -1;
>>> + }
>>> +
>>> + /* Get the number of parameters */
>>> + nr = BTF_INFO_VLEN(type->info);
>>> +
>>> + /* The parameters are right after the FUNC_PROTO type */
>>> + param = ((void *)type) + sizeof(*type);
>>> +
>>> + for (a = 0, p = 0; p < nr; a++, p++) {
>>> + struct btf_type *t;
>>> +
>>> + if (p)
>>> + trace_seq_puts(s, ", ");
>>> +
>>> + if (a == nmem) {
>>> + trace_seq_puts(s, "...");
>>> + break;
>>> + }
>>> +
>>> + assign_arg(&arg, args, size, a);
>>> +
>>> + param_name = btf_name(btf, param[p].name_off);
>>> + if (param_name)
>>> + trace_seq_printf(s, "%s=", param_name);
>>> +
>>> + t = btf_skip_modifiers(btf, param[p].type);
>>> +
>>> + switch (t ? BTF_INFO_KIND(t->info) : BTF_KIND_UNKN) {
>>> + case BTF_KIND_UNKN:
>>> + trace_seq_putc(s, '?');
>>> + /* Still print unknown type values */
>>> + /* fallthough */
>>> + case BTF_KIND_PTR:
>>> + trace_seq_printf(s, "0x%llx", arg);
>>> + break;
>>> + case BTF_KIND_INT:
>>> + encode = *(int *)((void *)t + sizeof(*t));
>>> + /* Print unsigned ints as hex */
>>> + if (BTF_INT_ENCODING(encode) & BTF_INT_SIGNED)
>>
>> BTF_INT_OFFSET() and BTF_INT_VAL() values should also be used to shift and mask
>> appropriately.
>
> You meant to the "arg" passed in?
Yes, something like that:
unsigned long long mask = (1 << BTF_INT_BITS(encode)) - 1;
unsigned long long shift = BTF_INT_OFFSET(encode);
arg = (arg >> shift) & mask;
I suppose that how BTF_INT_OFFSET() is to be used depends on endianness, and I'm not
sure what is the endianness of the arg value. t->size would probably be involved as
well in the big endian case: https://docs.kernel.org/bpf/btf.html#btf-kind-int
>
>>
>> I assume that regardless of BTF_INT_CHAR and BTF_INT_BOOL value, BTF_INT_SIGNED is
>> set appropriately.
>>
>>> + trace_seq_printf(s, "%lld", arg);
>>> + else
>>> + trace_seq_printf(s, "0x%llx", arg);
>>> + break;
>>> + case BTF_KIND_ENUM:
>>
>> Could add as well: case BTF_KIND_ENUM64:
>
> Of course then we would need to check if this is a 32 bit infrastructure.
> I'm assuming it would be treated differently. More like a struct?
AFAIR the only point of BTF_KIND_ENUM64 is to represent enum with enumerator values that are
larger than 32 bits, since BTF_KIND_ENUM can only represent 32 bit values:
struct btf_enum {
__u32 name_off;
__s32 val;
};
In contrast, BTF_KIND_ENUM64 encodes the enumerators with this:
struct btf_enum64 {
__u32 name_off;
__u32 val_lo32;
__u32 val_hi32;
};
So each value can have 64bits, split over 2 fields. I assume this quirk is
just to have alignof(struct btf_enum64) == 4 so that the BTF wire format
can be mapped on that C struct easily.
AFAIR BTF_KIND_ENUM encoding was simply an oversight in the initial BTF spec, so they corrected it
by adding another BTF_KIND_*.
Since you make no use of the enumerators array for now, I think the handling is identical.
Wrt to 32 vs 64 bits, in ISO C, the enum size does not depend on the ABI:
https://port70.net/~nsz/c/c11/n1570.html#6.7.2.2p4
In practice, GCC seems to pick "int" as the base type unless forced to use something else:
1. C23 feature that let us explicitly set the underlying type.
2. -fshort-enums that selects the smallest type possible (not used in the kernel)
3. An enumerator value that does not fit in an int. E.g. 4294967297
The last case happens in the kernel:
enum kvm_pgtable_prot {
KVM_PGTABLE_PROT_X = BIT(0),
KVM_PGTABLE_PROT_W = BIT(1),
KVM_PGTABLE_PROT_R = BIT(2),
KVM_PGTABLE_PROT_DEVICE = BIT(3),
KVM_PGTABLE_PROT_NORMAL_NC = BIT(4),
KVM_PGTABLE_PROT_SW0 = BIT(55),
KVM_PGTABLE_PROT_SW1 = BIT(56),
KVM_PGTABLE_PROT_SW2 = BIT(57),
KVM_PGTABLE_PROT_SW3 = BIT(58),
};
https://elixir.bootlin.com/linux/v6.16/source/arch/arm64/include/asm/kvm_pgtable.h#L253
>>
>>> + trace_seq_printf(s, "%lld", arg);
>>
>> This could probably be improved to display the enum variant name, with the caveats that:
>
> If btf has it, sure! But that can come later.
>
> Thanks,
>
> -- Steve
>
>
>>
>> 1. C allows values that are not that of any enumerator AFAIR, so you'd need the
>> raw int display fallback.
>> 2. GNU C allows enum forward declaration. This is not ISO C and BTF has no specific
>> representation for that. As a result, last time I checked you end up with
>> BTF_KIND_ENUM and info.vlen == 0. Another entry with info.vlen != 0 should
>> also exist and give the actual enumerators list. Those forward decl provide an
>> incomplete type though, so you should not encounter it directly in function
>> parameters.
>>
>>> + break;
>>
>>> + default:
>>> + /* This does not handle complex arguments */
>>> + trace_seq_printf(s, "(%s)[0x%llx", btf_type_str(t), arg);
>>> + for (x = sizeof(long); x < t->size; x += sizeof(long)) {
>>> + trace_seq_putc(s, ':');
>>> + if (++a == nmem) {
>>> + trace_seq_puts(s, "...]");
>>> + return 0;
>>> + }
>>> + assign_arg(&arg, args, size, a);
>>> + trace_seq_printf(s, "0x%llx", arg);
>>> + }
>>> + trace_seq_putc(s, ']');
>>> + break;
>>> + }
>>> + }
>>> + return 0;
>>> +}
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle
2025-08-04 14:18 ` Douglas Raillard
@ 2025-08-04 14:41 ` Steven Rostedt
2025-08-04 15:01 ` Douglas Raillard
0 siblings, 1 reply; 14+ messages in thread
From: Steven Rostedt @ 2025-08-04 14:41 UTC (permalink / raw)
To: Douglas Raillard
Cc: linux-trace-devel, Masami Hiramatsu, Namhyung Kim, Takaya Saeki,
Ian Rogers, aahringo
On Mon, 4 Aug 2025 15:18:41 +0100
Douglas Raillard <douglas.raillard@arm.com> wrote:
> >>> + case BTF_KIND_INT:
> >>> + encode = *(int *)((void *)t + sizeof(*t));
> >>> + /* Print unsigned ints as hex */
> >>> + if (BTF_INT_ENCODING(encode) & BTF_INT_SIGNED)
> >>
> >> BTF_INT_OFFSET() and BTF_INT_VAL() values should also be used to shift and mask
> >> appropriately.
> >
> > You meant to the "arg" passed in?
>
> Yes, something like that:
>
> unsigned long long mask = (1 << BTF_INT_BITS(encode)) - 1;
> unsigned long long shift = BTF_INT_OFFSET(encode);
> arg = (arg >> shift) & mask;
>
> I suppose that how BTF_INT_OFFSET() is to be used depends on endianness, and I'm not
> sure what is the endianness of the arg value. t->size would probably be involved as
> well in the big endian case: https://docs.kernel.org/bpf/btf.html#btf-kind-int
Hmm, I guess I'll need to update that. The endianness is stored in the
tep_handle descriptor and will need to be used. Note, this needs to handle
big endian machine reading little endian machine data and vice versa. As
well as little and big reading their own endianness.
Too bad my big endian machine died. I used that to test all the combinations.
>
> >
> >>
> >> I assume that regardless of BTF_INT_CHAR and BTF_INT_BOOL value, BTF_INT_SIGNED is
> >> set appropriately.
> >>
> >>> + trace_seq_printf(s, "%lld", arg);
> >>> + else
> >>> + trace_seq_printf(s, "0x%llx", arg);
> >>> + break;
> >>> + case BTF_KIND_ENUM:
> >>
> >> Could add as well: case BTF_KIND_ENUM64:
> >
> > Of course then we would need to check if this is a 32 bit infrastructure.
> > I'm assuming it would be treated differently. More like a struct?
>
> AFAIR the only point of BTF_KIND_ENUM64 is to represent enum with enumerator values that are
> larger than 32 bits, since BTF_KIND_ENUM can only represent 32 bit values:
>
> struct btf_enum {
> __u32 name_off;
> __s32 val;
> };
>
> In contrast, BTF_KIND_ENUM64 encodes the enumerators with this:
>
> struct btf_enum64 {
> __u32 name_off;
> __u32 val_lo32;
> __u32 val_hi32;
> };
>
> So each value can have 64bits, split over 2 fields. I assume this quirk is
> just to have alignof(struct btf_enum64) == 4 so that the BTF wire format
> can be mapped on that C struct easily.
>
> AFAIR BTF_KIND_ENUM encoding was simply an oversight in the initial BTF spec, so they corrected it
> by adding another BTF_KIND_*.
>
> Since you make no use of the enumerators array for now, I think the handling is identical.
>
>
> Wrt to 32 vs 64 bits, in ISO C, the enum size does not depend on the ABI:
> https://port70.net/~nsz/c/c11/n1570.html#6.7.2.2p4
When I brought up 64 bit, I meant with the parameters being passed in. As
this is for parsing function parameters. On 32bit an enum64 would require
two registers whereas on 64bit it would only require one.
-- Steve
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle
2025-08-04 14:41 ` Steven Rostedt
@ 2025-08-04 15:01 ` Douglas Raillard
0 siblings, 0 replies; 14+ messages in thread
From: Douglas Raillard @ 2025-08-04 15:01 UTC (permalink / raw)
To: Steven Rostedt
Cc: linux-trace-devel, Masami Hiramatsu, Namhyung Kim, Takaya Saeki,
Ian Rogers, aahringo
On 04-08-2025 15:41, Steven Rostedt wrote:
> On Mon, 4 Aug 2025 15:18:41 +0100
> Douglas Raillard <douglas.raillard@arm.com> wrote:
>
>>>>> + case BTF_KIND_INT:
>>>>> + encode = *(int *)((void *)t + sizeof(*t));
>>>>> + /* Print unsigned ints as hex */
>>>>> + if (BTF_INT_ENCODING(encode) & BTF_INT_SIGNED)
>>>>
>>>> BTF_INT_OFFSET() and BTF_INT_VAL() values should also be used to shift and mask
>>>> appropriately.
>>>
>>> You meant to the "arg" passed in?
>>
>> Yes, something like that:
>>
>> unsigned long long mask = (1 << BTF_INT_BITS(encode)) - 1;
>> unsigned long long shift = BTF_INT_OFFSET(encode);
>> arg = (arg >> shift) & mask;
>>
>> I suppose that how BTF_INT_OFFSET() is to be used depends on endianness, and I'm not
>> sure what is the endianness of the arg value. t->size would probably be involved as
>> well in the big endian case: https://docs.kernel.org/bpf/btf.html#btf-kind-int
>
> Hmm, I guess I'll need to update that. The endianness is stored in the
> tep_handle descriptor and will need to be used. Note, this needs to handle
> big endian machine reading little endian machine data and vice versa. As
> well as little and big reading their own endianness.
>
> Too bad my big endian machine died. I used that to test all the combinations.
>
>>
>>>
>>>>
>>>> I assume that regardless of BTF_INT_CHAR and BTF_INT_BOOL value, BTF_INT_SIGNED is
>>>> set appropriately.
>>>>
>>>>> + trace_seq_printf(s, "%lld", arg);
>>>>> + else
>>>>> + trace_seq_printf(s, "0x%llx", arg);
>>>>> + break;
>>>>> + case BTF_KIND_ENUM:
>>>>
>>>> Could add as well: case BTF_KIND_ENUM64:
>>>
>>> Of course then we would need to check if this is a 32 bit infrastructure.
>>> I'm assuming it would be treated differently. More like a struct?
>>
>> AFAIR the only point of BTF_KIND_ENUM64 is to represent enum with enumerator values that are
>> larger than 32 bits, since BTF_KIND_ENUM can only represent 32 bit values:
>>
>> struct btf_enum {
>> __u32 name_off;
>> __s32 val;
>> };
>>
>> In contrast, BTF_KIND_ENUM64 encodes the enumerators with this:
>>
>> struct btf_enum64 {
>> __u32 name_off;
>> __u32 val_lo32;
>> __u32 val_hi32;
>> };
>>
>> So each value can have 64bits, split over 2 fields. I assume this quirk is
>> just to have alignof(struct btf_enum64) == 4 so that the BTF wire format
>> can be mapped on that C struct easily.
>>
>> AFAIR BTF_KIND_ENUM encoding was simply an oversight in the initial BTF spec, so they corrected it
>> by adding another BTF_KIND_*.
>>
>> Since you make no use of the enumerators array for now, I think the handling is identical.
>>
>>
>> Wrt to 32 vs 64 bits, in ISO C, the enum size does not depend on the ABI:
>> https://port70.net/~nsz/c/c11/n1570.html#6.7.2.2p4
>
> When I brought up 64 bit, I meant with the parameters being passed in. As
> this is for parsing function parameters. On 32bit an enum64 would require
> two registers whereas on 64bit it would only require one.
Ah yes hadn't thought of that. From a godbolt experiment, both C and Rust seem
to just use 2 registers in the arm 32 bits case, like a struct.
> -- Steve
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle
2025-07-31 18:52 ` [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle Steven Rostedt
2025-08-04 11:47 ` Douglas Raillard
@ 2025-08-04 16:07 ` Douglas Raillard
2025-08-04 16:26 ` Douglas Raillard
2025-08-04 17:34 ` Steven Rostedt
1 sibling, 2 replies; 14+ messages in thread
From: Douglas Raillard @ 2025-08-04 16:07 UTC (permalink / raw)
To: Steven Rostedt, linux-trace-devel
Cc: Masami Hiramatsu, Namhyung Kim, Takaya Saeki, Ian Rogers,
aahringo
On 31-07-2025 19:52, Steven Rostedt wrote:
> From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
>
> Add loading of the BTF file /sys/kernel/btf/vmlinux into the tep_handler and
> then use it to format the function tracer if it has arguments:
>
> cat-33501 [005] ..... 136155.767950: function: __x64_sys_execve(regs=0xffffc9000e3eff58)
> cat-33501 [005] ..... 136155.767951: function: getname_flags(filename=0x7ffe7d33f3d0, flags=0)
> cat-33501 [005] ..... 136155.767951: function: getname_flags.part.0(7ffe7d33f3d0, 0, 0, 0, 0, 0)
> cat-33501 [005] ..... 136155.767951: function: kmem_cache_alloc_noprof(s=0xffff8881001d3800, gfpflags=0xcc0)
> cat-33501 [005] ..... 136155.767951: function: fs_reclaim_acquire(gfp_mask=0xcc0)
> cat-33501 [005] ..... 136155.767952: function: fs_reclaim_release(gfp_mask=0xcc0)
> cat-33501 [005] ..... 136155.767953: function: kmemleak_alloc(ptr=0xffff8881114a0000, size=0x1000, min_count=1, gfp=0xcc0)
> cat-33501 [005] ..... 136155.767953: function: __create_object(ptr=0xffff8881114a0000, size=0x1000, min_count=1, gfp=0xcc0, objflags=0x0)
> cat-33501 [005] ..... 136155.767954: function: __alloc_object(gfp=0xcc0)
> cat-33501 [005] ..... 136155.767954: function: kmem_cache_alloc_noprof(s=0xffff888100045700, gfpflags=0x92cc0)
> cat-33501 [005] ..... 136155.767954: function: fs_reclaim_acquire(gfp_mask=0x92cc0)
> cat-33501 [005] ..... 136155.767955: function: fs_reclaim_release(gfp_mask=0x92cc0)
>
> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
> ---
> include/traceevent/event-parse.h | 5 +
> plugins/plugin_function.c | 15 +-
> src/Makefile | 1 +
> src/event-parse-local.h | 6 +
> src/event-parse.c | 2 +
> src/trace-btf.c | 449 +++++++++++++++++++++++++++++++
> 6 files changed, 468 insertions(+), 10 deletions(-)
> create mode 100644 src/trace-btf.c
>
> diff --git a/include/traceevent/event-parse.h b/include/traceevent/event-parse.h
> index d3ce0a43a8fc..ebfc2c7abac2 100644
> --- a/include/traceevent/event-parse.h
> +++ b/include/traceevent/event-parse.h
> @@ -580,6 +580,11 @@ int tep_get_ref(struct tep_handle *tep);
>
> struct kbuffer *tep_kbuffer(struct tep_handle *tep);
>
> +/* BTF */
> +int tep_load_btf(struct tep_handle *tep, void *raw_data, size_t data_size);
> +int tep_btf_print_args(struct tep_handle *tep, struct trace_seq *s, void *args,
> + int nmem, int size, const char *func);
> +
> /* for debugging */
> void tep_print_funcs(struct tep_handle *tep);
> void tep_print_printk(struct tep_handle *tep);
> diff --git a/plugins/plugin_function.c b/plugins/plugin_function.c
> index a337446e3462..379fc3ad0ffd 100644
> --- a/plugins/plugin_function.c
> +++ b/plugins/plugin_function.c
> @@ -135,10 +135,9 @@ static void show_function(struct trace_seq *s, struct tep_handle *tep,
>
> /* Returns true if it printed args, otherwise it returns false */
> static bool print_args(struct trace_seq *s, struct tep_event *event,
> - struct tep_record *record)
> + struct tep_record *record, const char *func)
> {
> struct tep_format_field *field;
> - unsigned long arg;
> void *args;
> int len;
>
> @@ -156,12 +155,8 @@ static bool print_args(struct trace_seq *s, struct tep_event *event,
>
> trace_seq_putc(s, '(');
>
> - for (int i = 0; i < len; i += sizeof(long), args += sizeof(long)) {
> - memcpy(&arg, args, sizeof(long));
> - trace_seq_printf(s, "%lx", arg);
> - if (i + sizeof(long) < len)
> - trace_seq_puts(s, ", ");
> - }
> + tep_btf_print_args(event->tep, s, args, len / sizeof(long), sizeof(long), func);
> +
> trace_seq_putc(s, ')');
> return true;
> }
> @@ -196,7 +191,7 @@ static int function_handler(struct trace_seq *s, struct tep_record *record,
> else
> trace_seq_printf(s, "0x%llx", function);
>
> - print_args(s, event, record);
> + print_args(s, event, record, func);
>
> if (ftrace_parent->set) {
> trace_seq_printf(s, " <-- ");
> @@ -281,7 +276,7 @@ trace_stack_handler(struct trace_seq *s, struct tep_record *record,
>
> static int
> trace_raw_data_handler(struct trace_seq *s, struct tep_record *record,
> - struct tep_event *event, void *context)
> + struct tep_event *event, void *context)
> {
> struct tep_format_field *field;
> unsigned long long id;
> diff --git a/src/Makefile b/src/Makefile
> index 53bb570182d2..1ff669ce8deb 100644
> --- a/src/Makefile
> +++ b/src/Makefile
> @@ -11,6 +11,7 @@ OBJS += parse-filter.o
> OBJS += parse-utils.o
> OBJS += tep_strerror.o
> OBJS += trace-seq.o
> +OBJS += trace-btf.o
>
> OBJS := $(OBJS:%.o=$(bdir)/%.o)
> DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d)
> diff --git a/src/event-parse-local.h b/src/event-parse-local.h
> index d9e9faf649d1..50bc4d87bbe6 100644
> --- a/src/event-parse-local.h
> +++ b/src/event-parse-local.h
> @@ -14,6 +14,7 @@ struct func_list;
> struct event_handler;
> struct func_resolver;
> struct tep_plugins_dir;
> +struct tep_btf;
>
> #define __hidden __attribute__((visibility ("hidden")))
>
> @@ -89,6 +90,8 @@ struct tep_handle {
> const char *input_buf;
> unsigned long long input_buf_ptr;
> unsigned long long input_buf_siz;
> +
> + struct tep_btf *btf;
> };
>
> enum tep_print_parse_type {
> @@ -124,4 +127,7 @@ const char *tep_get_input_buf(struct tep_handle *tep);
> enum tep_event_type tep_read_token(struct tep_handle *tep, char **tok);
> void tep_free_token(char *tok);
>
> +/* BTF routines */
> +void tep_btf_free(struct tep_btf *btf);
> +
> #endif /* _PARSE_EVENTS_INT_H */
> diff --git a/src/event-parse.c b/src/event-parse.c
> index aa16d83bb2ad..730f25c92110 100644
> --- a/src/event-parse.c
> +++ b/src/event-parse.c
> @@ -8726,6 +8726,8 @@ void tep_free(struct tep_handle *tep)
> free(tep->func_resolver);
> tep_free_plugin_paths(tep);
>
> + tep_btf_free(tep->btf);
> +
> free(tep);
> }
>
> diff --git a/src/trace-btf.c b/src/trace-btf.c
> new file mode 100644
> index 000000000000..79c5d6c93327
> --- /dev/null
> +++ b/src/trace-btf.c
> @@ -0,0 +1,449 @@
> +// SPDX-License-Identifier: LGPL-2.1
> +/*
> + * Copyright (C) 2025 Google, Steven Rostedt <rostedt@goodmis.org>
> + *
> + */
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <stdbool.h>
> +
> +#include <linux/btf.h>
> +
> +#include "event-parse.h"
> +#include "event-utils.h"
> +#include "event-parse-local.h"
> +
> +struct btf_header;
> +struct btf_type;
> +
> +struct tep_btf {
> + struct btf_header *hdr;
> + const char *strings;
> + struct btf_type **types;
> + unsigned long nr_types;
> + struct btf_type **funcs;
> + unsigned long nr_funcs;
> + void *data;
> + size_t raw_size;
> + void *raw_data;
> +};
> +
> +#define REALLOC_SIZE (1 << 10)
> +#define REALLOC_MASK (REALLOC_SIZE - 1)
> +
> +static const char *btf_name(struct tep_btf *btf, int off)
> +{
> + if (off < btf->hdr->str_len)
That assumes the input is well-behaved and str_len is set to the actual length of
the strings data, but it could be anything. tep_btf_init() should check that
btf->hdr->strings + btf->hdr->str_len point inside the provided data.
> + return btf->strings + off;> + return "";
> +}
> +
> +/* List taken from the Linux kernel */
> +static const char * const btf_kind_str[NR_BTF_KINDS] = {
> + [BTF_KIND_UNKN] = "UNKNOWN",
> + [BTF_KIND_INT] = "INT",
> + [BTF_KIND_PTR] = "PTR",
> + [BTF_KIND_ARRAY] = "ARRAY",
> + [BTF_KIND_STRUCT] = "STRUCT",
> + [BTF_KIND_UNION] = "UNION",
> + [BTF_KIND_ENUM] = "ENUM",
> + [BTF_KIND_FWD] = "FWD",
> + [BTF_KIND_TYPEDEF] = "TYPEDEF",
> + [BTF_KIND_VOLATILE] = "VOLATILE",
> + [BTF_KIND_CONST] = "CONST",
> + [BTF_KIND_RESTRICT] = "RESTRICT",
> + [BTF_KIND_FUNC] = "FUNC",
> + [BTF_KIND_FUNC_PROTO] = "FUNC_PROTO",
> + [BTF_KIND_VAR] = "VAR",
> + [BTF_KIND_DATASEC] = "DATASEC",
> + [BTF_KIND_FLOAT] = "FLOAT",
> + [BTF_KIND_DECL_TAG] = "DECL_TAG",
> + [BTF_KIND_TYPE_TAG] = "TYPE_TAG",
> + [BTF_KIND_ENUM64] = "ENUM64",
> +};
> +
> +static const char *btf_type_str(const struct btf_type *t)
> +{
> + return btf_kind_str[BTF_INFO_KIND(t->info)];
> +}
> +
> +static int insert_type(struct btf_type ***types, unsigned long *cnt, struct btf_type *type)
> +{
> + unsigned long nr_types = *cnt;
> + struct btf_type **array = *types;
> +
> + if (!(nr_types & REALLOC_MASK)) {
> + int size = nr_types + REALLOC_SIZE;
> +
> + array = realloc(array, sizeof(struct btf_type *) * size);
> + if (!array) {
> + tep_warning("Failed to alloct memory for new type");
> + return -1;
> + }
> + *types = array;
> + }
> +
> + array[nr_types++] = type;
> + *cnt = nr_types;
> + return 0;
> +}
> +
> +static int add_type(struct tep_btf *btf, struct btf_type *type)
> +{
> + return insert_type(&btf->types, &btf->nr_types, type);
> +}
> +
> +static int add_func(struct tep_btf *btf, struct btf_type *type)
> +{
> + return insert_type(&btf->funcs, &btf->nr_funcs, type);
> +}
> +
> +static int btf_type_size(struct btf_type *type)
> +{
> + int kind = BTF_INFO_KIND(type->info);
> + int size = sizeof(*type);
> +
> + switch (kind) {
> + case BTF_KIND_INT:
> + return size + sizeof(int);
> + case BTF_KIND_VAR:
> + return size + sizeof(struct btf_var);
> + case BTF_KIND_ARRAY:
> + return size + sizeof(struct btf_array);
> + case BTF_KIND_DECL_TAG:
> + return size + sizeof(struct btf_decl_tag);
> + case BTF_KIND_ENUM:
> + return size + sizeof(struct btf_enum) * BTF_INFO_VLEN(type->info);
> + case BTF_KIND_ENUM64:
> + return size + sizeof(struct btf_enum64) * BTF_INFO_VLEN(type->info);
> + case BTF_KIND_STRUCT:
> + case BTF_KIND_UNION:
> + return size + sizeof(struct btf_member) * BTF_INFO_VLEN(type->info);
> + case BTF_KIND_FUNC_PROTO:
> + return size + sizeof(struct btf_param) * BTF_INFO_VLEN(type->info);
> + case BTF_KIND_DATASEC:
> + return size + sizeof(struct btf_var_secinfo) * BTF_INFO_VLEN(type->info);
> + case BTF_KIND_PTR:
> + case BTF_KIND_FWD:
> + case BTF_KIND_TYPEDEF:
> + case BTF_KIND_VOLATILE:
> + case BTF_KIND_CONST:
> + case BTF_KIND_RESTRICT:
> + case BTF_KIND_TYPE_TAG:
> + case BTF_KIND_FUNC:
> + case BTF_KIND_FLOAT:
> + return size;
> + }
> + return -1;
> +}
> +
> +static int cmp_funcs(const void *A, const void *B, void *data)
> +{
> + struct tep_btf *btf = data;
> + const struct btf_type *a = *(const struct btf_type **)A;
> + const struct btf_type *b = *(const struct btf_type **)B;
> + const char *name_a = btf_name(btf, a->name_off);
> + const char *name_b = btf_name(btf, b->name_off);
> +
> + return strcmp(name_a, name_b);
> +}
> +
> +struct tcmd_search {
> + struct tep_btf *btf;
> + const char *name;
> +};
> +
> +static int cmp_key_func(const void *A, const void *B)
> +{
> + const struct tcmd_search *key = A;
> + const struct btf_type *b = *(const struct btf_type **)B;
> + const char *name_b = btf_name(key->btf, b->name_off);
> +
> + return strcmp(key->name, name_b);
> +}
> +
> +static struct btf_type *tep_btf_find_func(struct tep_btf *btf, const char *name)
> +{
> + struct tcmd_search tsearch;
> + struct btf_type **t;
> +
> + if (!btf || !name)
> + return NULL;
> +
> + tsearch.btf = btf;
> + tsearch.name = name;
> +
> + t = bsearch(&tsearch, btf->funcs, btf->nr_funcs, sizeof(btf->funcs[0]),
> + cmp_key_func);
> +
> + return t ? *t : NULL;
> +}
> +
> +static int load_types(struct tep_btf *btf)
> +{
> + struct btf_type *type;
> + void *start, *end;
> + int size;
> +
> + start = btf->data + btf->hdr->type_off;
> + end = start + btf->hdr->type_len;
Those should also be validated.
> +
> + if (end > btf->raw_data + btf->raw_size)
> + return -1;
> +
> + for (type = start; (void *)type < end;) {
> + if (add_type(btf, type))
> + return -1;
> + if (BTF_INFO_KIND(type->info) == BTF_KIND_FUNC) {
> + if (add_func(btf, type))
> + return -1;
> + }
> + size = btf_type_size(type);
> + if (size < 0) {
> + tep_warning("Invalid type %d\n", BTF_INFO_KIND(type->info));
> + return -1;
> + }
> + type = (void *)type + size;
> + }
> +
> + qsort_r(btf->funcs, btf->nr_funcs, sizeof(btf->funcs[0]), cmp_funcs, btf);
> + return 0;
> +}
> +
> +__hidden void tep_btf_free(struct tep_btf *btf)
> +{
> + if (!btf)
> + return;
> +
> + free((void *)btf->strings);
> + free(btf->types);
> + free(btf->funcs);
> + free(btf->raw_data);
> +}
> +
> +/** tep_btf_init - Initialize btf with raw data
> + * @raw_data: Allocated data to use for BTF (/sys/kernel/btf/vmlinux)
> + * @data_size: The amount of data in @raw_data
> + *
> + * The @raw_data ownership belongs to this function when called.
> + * It must be allocated by the glibc allocator as it will be freed
> + * when the BTF descriptor is freed. It will not be freed if this
> + * function returns failure (NULL).
> + *
> + * Returns: On success it returns the BTF descriptor, and he @raw_data
> + * will now be apart of it (it will be freed by tep_btf_free().
> + * On failure it returns NULL, and the @raw_data is NOT freed.
> + */
> +static struct tep_btf *tep_btf_init(void *raw_data, size_t data_size)
> +{
> + struct tep_btf *btf;
> +
> + btf = calloc(1, sizeof(*btf));
> + if (!btf)
> + return NULL;
> +
> + btf->raw_data = raw_data;
> + btf->raw_size = data_size;
> + btf->hdr = raw_data;
> + if (btf->hdr->hdr_len < sizeof(*btf->hdr)) {
> + tep_warning("Header (%d) smaller than expected header %zd",
> + btf->hdr->hdr_len, sizeof(*btf->hdr));
> + goto fail;
> + }
btf->hdr->magic and btf->hdr->version should be checked to catch any garbage input and
breaking change to the format:
The magic is 0xeB9F, which has different encoding for big and little endian systems,
and can be used to test whether BTF is generated for big- or little-endian target.
https://docs.kernel.org/bpf/btf.html#btf-type-and-string-encoding
> +
> + btf->data = btf->raw_data + btf->hdr->hdr_len;
> +
> + btf->strings = btf->data + btf->hdr->str_off;
This pointer crafting without input validation looks like a CVE-in-waiting.
Using libbpf would indeed add a dependency, but it seems those checks are taken care of:
https://github.com/libbpf/libbpf/blob/58dd1f58b57294b2e59482245b29e46f1812b82d/src/btf.c#L258
This is not to say libbpf is perfect, but there also seem to be some people running fuzzers on it
so issues of this kind have a higher chance of getting found, e.g.
https://nvd.nist.gov/vuln/detail/CVE-2025-29481
I suppose there is no silver bullet here, as adding dependencies to a C program is a pain
as it breaks downstream build recipes ...
Alternatively, the Rust parser has no BTF-related buffer issue, but that's because it does not
implement anything BTF-related yet :)
> + printf("str len = %d\n", btf->hdr->str_len);
> +
> + if (load_types(btf) < 0)
> + goto fail;
> +
> + return btf;
> + fail:
> + btf->raw_data = NULL;
> + tep_btf_free(btf);
> + return NULL;
> +}
> +
> +/**
> + * tep_load_btf - Load BTF information into a tep handle
> + * @tep: The tep handle to load the BTF info into
> + * @raw_data: The raw data containing the BTF file
> + * @data_size: The amount of data in @raw_data
> + *
> + * Initializes BTF into the @tep handle. If it had already had BTF
> + * loaded, it will free the previous BTF and recreate it from @raw_data.
> + *
> + * Returns: 0 on success and -1 on failure.
> + */
> +int tep_load_btf(struct tep_handle *tep, void *raw_data, size_t data_size)
> +{
> + /* If btf was already loaded, free it */
> + tep_btf_free(tep->btf);
> +
> + tep->btf = tep_btf_init(raw_data, data_size);
> + if (!tep->btf)
> + return -1;
> + return 0;
> +}
> +
> +static struct btf_type *btf_get_type(struct tep_btf *btf, int id)
> +{
> + if (!id || id > btf->nr_types)
> + return NULL;
> +
> + return btf->types[id - 1];
> +}
> +
> +static struct btf_type *btf_skip_modifiers(struct tep_btf *btf, int id)
> +{
> + struct btf_type *t = btf_get_type(btf, id);
> +
> + for (;;) {
> + switch (BTF_INFO_KIND(t->info)) {
> + case BTF_KIND_TYPEDEF:
> + case BTF_KIND_VOLATILE:
> + case BTF_KIND_CONST:
> + case BTF_KIND_RESTRICT:
> + case BTF_KIND_TYPE_TAG:
> + id = t->type;
> + t = btf_get_type(btf, t->type);
> + continue;
> + }
> + break;
> + }
> +
> + return t;
> +}
> +
> +static void assign_arg(unsigned long long *arg, void *args, int size, int a)
> +{
> + *arg = size == 4 ?
> + *(unsigned int *)(args + a * sizeof(int)) :
> + *(unsigned long long *)(args + a * sizeof(long long));
> +}
> +
> +/**
> + * tep_btf_print_args - Print function arguments from BTF info
> + * @tep: The tep descriptor to use
> + * @s: The trace_seq to write the arguments into
> + * @args: The array that holds the arguments
> + * @nmem: The number of arguments
> + * @size: The size of each item in args (4 or 8).
> + * @func: The name of the function.
> + *
> + * Loads up the @s with a list of arguments for the function based on
> + * the @args.
> + *
> + * If there's no BTF loaded or @func is not found, then it just writes
> + * the @args as raw numbers. Otherwise it will pretty print the
> + * arguments based on the function info in BTF.
> + *
> + * Returns: 0 on success and -1 on failure.
> + */
> +int tep_btf_print_args(struct tep_handle *tep, struct trace_seq *s, void *args,
> + int nmem, int size, const char *func)
> +{
> + struct tep_btf *btf = tep->btf;
> + struct btf_type *type = tep_btf_find_func(btf, func);
> + struct btf_param *param;
> + unsigned long long arg;
> + unsigned int encode;
> + const char *param_name;
> + int a, p, x, nr;
> +
> + if (size != 4 && size != 8)
> + return -1;
> +
> + if (!type) {
> + for (int i = 0; i < nmem; i++) {
> + assign_arg(&arg, args, size, i);
> + trace_seq_printf(s, "%llx", arg);
> + if (i + 1 < nmem)
> + trace_seq_puts(s, ", ");
> + }
> + return 0;
> + }
> +
> + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC) {
> + printf("Invalid func type %d %s\n", BTF_INFO_KIND(type->info),
> + btf_type_str(type));
> + return -1;
> + }
> +
> + /* Get the function proto */
> + type = btf_get_type(btf, type->type);
> +
> + /* No proto means "()" ? */
> + if (!type)
> + return 0;
> +
> + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC_PROTO) {
> + printf("Invalid func proto type %d %s\n", BTF_INFO_KIND(type->info),
> + btf_type_str(type));
> + return -1;
> + }
> +
> + /* Get the number of parameters */
> + nr = BTF_INFO_VLEN(type->info);
> +
> + /* The parameters are right after the FUNC_PROTO type */
> + param = ((void *)type) + sizeof(*type);
> +
> + for (a = 0, p = 0; p < nr; a++, p++) {
> + struct btf_type *t;
> +
> + if (p)
> + trace_seq_puts(s, ", ");
> +
> + if (a == nmem) {
> + trace_seq_puts(s, "...");
> + break;
> + }
> +
> + assign_arg(&arg, args, size, a);
> +
> + param_name = btf_name(btf, param[p].name_off);
> + if (param_name)
> + trace_seq_printf(s, "%s=", param_name);
> +
> + t = btf_skip_modifiers(btf, param[p].type);
> +
> + switch (t ? BTF_INFO_KIND(t->info) : BTF_KIND_UNKN) {
> + case BTF_KIND_UNKN:
> + trace_seq_putc(s, '?');
> + /* Still print unknown type values */
> + /* fallthough */
> + case BTF_KIND_PTR:
> + trace_seq_printf(s, "0x%llx", arg);
> + break;
> + case BTF_KIND_INT:
> + encode = *(int *)((void *)t + sizeof(*t));
> + /* Print unsigned ints as hex */
> + if (BTF_INT_ENCODING(encode) & BTF_INT_SIGNED)
> + trace_seq_printf(s, "%lld", arg);
> + else
> + trace_seq_printf(s, "0x%llx", arg);
> + break;
> + case BTF_KIND_ENUM:
> + trace_seq_printf(s, "%lld", arg);
> + break;
> + default:
> + /* This does not handle complex arguments */
> + trace_seq_printf(s, "(%s)[0x%llx", btf_type_str(t), arg);
> + for (x = sizeof(long); x < t->size; x += sizeof(long)) {
> + trace_seq_putc(s, ':');
> + if (++a == nmem) {
> + trace_seq_puts(s, "...]");
> + return 0;
> + }
> + assign_arg(&arg, args, size, a);
> + trace_seq_printf(s, "0x%llx", arg);
> + }
> + trace_seq_putc(s, ']');
> + break;
> + }
> + }
> + return 0;
> +}
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle
2025-08-04 16:07 ` Douglas Raillard
@ 2025-08-04 16:26 ` Douglas Raillard
2025-08-04 17:34 ` Steven Rostedt
1 sibling, 0 replies; 14+ messages in thread
From: Douglas Raillard @ 2025-08-04 16:26 UTC (permalink / raw)
To: Steven Rostedt, linux-trace-devel
Cc: Masami Hiramatsu, Namhyung Kim, Takaya Saeki, Ian Rogers,
aahringo
On 04-08-2025 17:07, Douglas Raillard wrote:
>
>
> On 31-07-2025 19:52, Steven Rostedt wrote:
>> From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
>>
>> Add loading of the BTF file /sys/kernel/btf/vmlinux into the tep_handler and
>> then use it to format the function tracer if it has arguments:
>>
>> cat-33501 [005] ..... 136155.767950: function: __x64_sys_execve(regs=0xffffc9000e3eff58)
>> cat-33501 [005] ..... 136155.767951: function: getname_flags(filename=0x7ffe7d33f3d0, flags=0)
>> cat-33501 [005] ..... 136155.767951: function: getname_flags.part.0(7ffe7d33f3d0, 0, 0, 0, 0, 0)
>> cat-33501 [005] ..... 136155.767951: function: kmem_cache_alloc_noprof(s=0xffff8881001d3800, gfpflags=0xcc0)
>> cat-33501 [005] ..... 136155.767951: function: fs_reclaim_acquire(gfp_mask=0xcc0)
>> cat-33501 [005] ..... 136155.767952: function: fs_reclaim_release(gfp_mask=0xcc0)
>> cat-33501 [005] ..... 136155.767953: function: kmemleak_alloc(ptr=0xffff8881114a0000, size=0x1000, min_count=1, gfp=0xcc0)
>> cat-33501 [005] ..... 136155.767953: function: __create_object(ptr=0xffff8881114a0000, size=0x1000, min_count=1, gfp=0xcc0, objflags=0x0)
>> cat-33501 [005] ..... 136155.767954: function: __alloc_object(gfp=0xcc0)
>> cat-33501 [005] ..... 136155.767954: function: kmem_cache_alloc_noprof(s=0xffff888100045700, gfpflags=0x92cc0)
>> cat-33501 [005] ..... 136155.767954: function: fs_reclaim_acquire(gfp_mask=0x92cc0)
>> cat-33501 [005] ..... 136155.767955: function: fs_reclaim_release(gfp_mask=0x92cc0)
>>
>> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
>> ---
>> include/traceevent/event-parse.h | 5 +
>> plugins/plugin_function.c | 15 +-
>> src/Makefile | 1 +
>> src/event-parse-local.h | 6 +
>> src/event-parse.c | 2 +
>> src/trace-btf.c | 449 +++++++++++++++++++++++++++++++
>> 6 files changed, 468 insertions(+), 10 deletions(-)
>> create mode 100644 src/trace-btf.c
>>
>> diff --git a/include/traceevent/event-parse.h b/include/traceevent/event-parse.h
>> index d3ce0a43a8fc..ebfc2c7abac2 100644
>> --- a/include/traceevent/event-parse.h
>> +++ b/include/traceevent/event-parse.h
>> @@ -580,6 +580,11 @@ int tep_get_ref(struct tep_handle *tep);
>> struct kbuffer *tep_kbuffer(struct tep_handle *tep);
>> +/* BTF */
>> +int tep_load_btf(struct tep_handle *tep, void *raw_data, size_t data_size);
>> +int tep_btf_print_args(struct tep_handle *tep, struct trace_seq *s, void *args,
>> + int nmem, int size, const char *func);
>> +
>> /* for debugging */
>> void tep_print_funcs(struct tep_handle *tep);
>> void tep_print_printk(struct tep_handle *tep);
>> diff --git a/plugins/plugin_function.c b/plugins/plugin_function.c
>> index a337446e3462..379fc3ad0ffd 100644
>> --- a/plugins/plugin_function.c
>> +++ b/plugins/plugin_function.c
>> @@ -135,10 +135,9 @@ static void show_function(struct trace_seq *s, struct tep_handle *tep,
>> /* Returns true if it printed args, otherwise it returns false */
>> static bool print_args(struct trace_seq *s, struct tep_event *event,
>> - struct tep_record *record)
>> + struct tep_record *record, const char *func)
>> {
>> struct tep_format_field *field;
>> - unsigned long arg;
>> void *args;
>> int len;
>> @@ -156,12 +155,8 @@ static bool print_args(struct trace_seq *s, struct tep_event *event,
>> trace_seq_putc(s, '(');
>> - for (int i = 0; i < len; i += sizeof(long), args += sizeof(long)) {
>> - memcpy(&arg, args, sizeof(long));
>> - trace_seq_printf(s, "%lx", arg);
>> - if (i + sizeof(long) < len)
>> - trace_seq_puts(s, ", ");
>> - }
>> + tep_btf_print_args(event->tep, s, args, len / sizeof(long), sizeof(long), func);
>> +
>> trace_seq_putc(s, ')');
>> return true;
>> }
>> @@ -196,7 +191,7 @@ static int function_handler(struct trace_seq *s, struct tep_record *record,
>> else
>> trace_seq_printf(s, "0x%llx", function);
>> - print_args(s, event, record);
>> + print_args(s, event, record, func);
>> if (ftrace_parent->set) {
>> trace_seq_printf(s, " <-- ");
>> @@ -281,7 +276,7 @@ trace_stack_handler(struct trace_seq *s, struct tep_record *record,
>> static int
>> trace_raw_data_handler(struct trace_seq *s, struct tep_record *record,
>> - struct tep_event *event, void *context)
>> + struct tep_event *event, void *context)
>> {
>> struct tep_format_field *field;
>> unsigned long long id;
>> diff --git a/src/Makefile b/src/Makefile
>> index 53bb570182d2..1ff669ce8deb 100644
>> --- a/src/Makefile
>> +++ b/src/Makefile
>> @@ -11,6 +11,7 @@ OBJS += parse-filter.o
>> OBJS += parse-utils.o
>> OBJS += tep_strerror.o
>> OBJS += trace-seq.o
>> +OBJS += trace-btf.o
>> OBJS := $(OBJS:%.o=$(bdir)/%.o)
>> DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d)
>> diff --git a/src/event-parse-local.h b/src/event-parse-local.h
>> index d9e9faf649d1..50bc4d87bbe6 100644
>> --- a/src/event-parse-local.h
>> +++ b/src/event-parse-local.h
>> @@ -14,6 +14,7 @@ struct func_list;
>> struct event_handler;
>> struct func_resolver;
>> struct tep_plugins_dir;
>> +struct tep_btf;
>> #define __hidden __attribute__((visibility ("hidden")))
>> @@ -89,6 +90,8 @@ struct tep_handle {
>> const char *input_buf;
>> unsigned long long input_buf_ptr;
>> unsigned long long input_buf_siz;
>> +
>> + struct tep_btf *btf;
>> };
>> enum tep_print_parse_type {
>> @@ -124,4 +127,7 @@ const char *tep_get_input_buf(struct tep_handle *tep);
>> enum tep_event_type tep_read_token(struct tep_handle *tep, char **tok);
>> void tep_free_token(char *tok);
>> +/* BTF routines */
>> +void tep_btf_free(struct tep_btf *btf);
>> +
>> #endif /* _PARSE_EVENTS_INT_H */
>> diff --git a/src/event-parse.c b/src/event-parse.c
>> index aa16d83bb2ad..730f25c92110 100644
>> --- a/src/event-parse.c
>> +++ b/src/event-parse.c
>> @@ -8726,6 +8726,8 @@ void tep_free(struct tep_handle *tep)
>> free(tep->func_resolver);
>> tep_free_plugin_paths(tep);
>> + tep_btf_free(tep->btf);
>> +
>> free(tep);
>> }
>> diff --git a/src/trace-btf.c b/src/trace-btf.c
>> new file mode 100644
>> index 000000000000..79c5d6c93327
>> --- /dev/null
>> +++ b/src/trace-btf.c
>> @@ -0,0 +1,449 @@
>> +// SPDX-License-Identifier: LGPL-2.1
>> +/*
>> + * Copyright (C) 2025 Google, Steven Rostedt <rostedt@goodmis.org>
>> + *
>> + */
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <stdbool.h>
>> +
>> +#include <linux/btf.h>
>> +
>> +#include "event-parse.h"
>> +#include "event-utils.h"
>> +#include "event-parse-local.h"
>> +
>> +struct btf_header;
>> +struct btf_type;
>> +
>> +struct tep_btf {
>> + struct btf_header *hdr;
>> + const char *strings;
>> + struct btf_type **types;
>> + unsigned long nr_types;
>> + struct btf_type **funcs;
>> + unsigned long nr_funcs;
>> + void *data;
>> + size_t raw_size;
>> + void *raw_data;
>> +};
>> +
>> +#define REALLOC_SIZE (1 << 10)
>> +#define REALLOC_MASK (REALLOC_SIZE - 1)
>> +
>> +static const char *btf_name(struct tep_btf *btf, int off)
>> +{
>> + if (off < btf->hdr->str_len)
>
> That assumes the input is well-behaved and str_len is set to the actual length of
> the strings data, but it could be anything. tep_btf_init() should check that
> btf->hdr->strings + btf->hdr->str_len point inside the provided data.
>
>> + return btf->strings + off;> + return "";
>> +}
>> +
>> +/* List taken from the Linux kernel */
>> +static const char * const btf_kind_str[NR_BTF_KINDS] = {
>> + [BTF_KIND_UNKN] = "UNKNOWN",
>> + [BTF_KIND_INT] = "INT",
>> + [BTF_KIND_PTR] = "PTR",
>> + [BTF_KIND_ARRAY] = "ARRAY",
>> + [BTF_KIND_STRUCT] = "STRUCT",
>> + [BTF_KIND_UNION] = "UNION",
>> + [BTF_KIND_ENUM] = "ENUM",
>> + [BTF_KIND_FWD] = "FWD",
>> + [BTF_KIND_TYPEDEF] = "TYPEDEF",
>> + [BTF_KIND_VOLATILE] = "VOLATILE",
>> + [BTF_KIND_CONST] = "CONST",
>> + [BTF_KIND_RESTRICT] = "RESTRICT",
>> + [BTF_KIND_FUNC] = "FUNC",
>> + [BTF_KIND_FUNC_PROTO] = "FUNC_PROTO",
>> + [BTF_KIND_VAR] = "VAR",
>> + [BTF_KIND_DATASEC] = "DATASEC",
>> + [BTF_KIND_FLOAT] = "FLOAT",
>> + [BTF_KIND_DECL_TAG] = "DECL_TAG",
>> + [BTF_KIND_TYPE_TAG] = "TYPE_TAG",
>> + [BTF_KIND_ENUM64] = "ENUM64",
>> +};
>> +
>> +static const char *btf_type_str(const struct btf_type *t)
>> +{
>> + return btf_kind_str[BTF_INFO_KIND(t->info)];
>> +}
>> +
>> +static int insert_type(struct btf_type ***types, unsigned long *cnt, struct btf_type *type)
>> +{
>> + unsigned long nr_types = *cnt;
>> + struct btf_type **array = *types;
>> +
>> + if (!(nr_types & REALLOC_MASK)) {
>> + int size = nr_types + REALLOC_SIZE;
>> +
>> + array = realloc(array, sizeof(struct btf_type *) * size);
>> + if (!array) {
>> + tep_warning("Failed to alloct memory for new type");
>> + return -1;
>> + }
>> + *types = array;
>> + }
>> +
>> + array[nr_types++] = type;
>> + *cnt = nr_types;
>> + return 0;
>> +}
>> +
>> +static int add_type(struct tep_btf *btf, struct btf_type *type)
>> +{
>> + return insert_type(&btf->types, &btf->nr_types, type);
>> +}
>> +
>> +static int add_func(struct tep_btf *btf, struct btf_type *type)
>> +{
>> + return insert_type(&btf->funcs, &btf->nr_funcs, type);
>> +}
>> +
>> +static int btf_type_size(struct btf_type *type)
>> +{
>> + int kind = BTF_INFO_KIND(type->info);
>> + int size = sizeof(*type);
>> +
>> + switch (kind) {
>> + case BTF_KIND_INT:
>> + return size + sizeof(int);
>> + case BTF_KIND_VAR:
>> + return size + sizeof(struct btf_var);
>> + case BTF_KIND_ARRAY:
>> + return size + sizeof(struct btf_array);
>> + case BTF_KIND_DECL_TAG:
>> + return size + sizeof(struct btf_decl_tag);
>> + case BTF_KIND_ENUM:
>> + return size + sizeof(struct btf_enum) * BTF_INFO_VLEN(type->info);
>> + case BTF_KIND_ENUM64:
>> + return size + sizeof(struct btf_enum64) * BTF_INFO_VLEN(type->info);
>> + case BTF_KIND_STRUCT:
>> + case BTF_KIND_UNION:
>> + return size + sizeof(struct btf_member) * BTF_INFO_VLEN(type->info);
>> + case BTF_KIND_FUNC_PROTO:
>> + return size + sizeof(struct btf_param) * BTF_INFO_VLEN(type->info);
>> + case BTF_KIND_DATASEC:
>> + return size + sizeof(struct btf_var_secinfo) * BTF_INFO_VLEN(type->info);
>> + case BTF_KIND_PTR:
>> + case BTF_KIND_FWD:
>> + case BTF_KIND_TYPEDEF:
>> + case BTF_KIND_VOLATILE:
>> + case BTF_KIND_CONST:
>> + case BTF_KIND_RESTRICT:
>> + case BTF_KIND_TYPE_TAG:
>> + case BTF_KIND_FUNC:
>> + case BTF_KIND_FLOAT:
>> + return size;
>> + }
>> + return -1;
>> +}
>> +
>> +static int cmp_funcs(const void *A, const void *B, void *data)
>> +{
>> + struct tep_btf *btf = data;
>> + const struct btf_type *a = *(const struct btf_type **)A;
>> + const struct btf_type *b = *(const struct btf_type **)B;
>> + const char *name_a = btf_name(btf, a->name_off);
>> + const char *name_b = btf_name(btf, b->name_off);
>> +
>> + return strcmp(name_a, name_b);
>> +}
>> +
>> +struct tcmd_search {
>> + struct tep_btf *btf;
>> + const char *name;
>> +};
>> +
>> +static int cmp_key_func(const void *A, const void *B)
>> +{
>> + const struct tcmd_search *key = A;
>> + const struct btf_type *b = *(const struct btf_type **)B;
>> + const char *name_b = btf_name(key->btf, b->name_off);
>> +
>> + return strcmp(key->name, name_b);
>> +}
>> +
>> +static struct btf_type *tep_btf_find_func(struct tep_btf *btf, const char *name)
>> +{
>> + struct tcmd_search tsearch;
>> + struct btf_type **t;
>> +
>> + if (!btf || !name)
>> + return NULL;
>> +
>> + tsearch.btf = btf;
>> + tsearch.name = name;
>> +
>> + t = bsearch(&tsearch, btf->funcs, btf->nr_funcs, sizeof(btf->funcs[0]),
>> + cmp_key_func);
>> +
>> + return t ? *t : NULL;
>> +}
>> +
>> +static int load_types(struct tep_btf *btf)
>> +{
>> + struct btf_type *type;
>> + void *start, *end;
>> + int size;
>> +
>> + start = btf->data + btf->hdr->type_off;
>> + end = start + btf->hdr->type_len;
>
> Those should also be validated.
>
>> +
>> + if (end > btf->raw_data + btf->raw_size)
>> + return -1;
>> +
>> + for (type = start; (void *)type < end;) {
>> + if (add_type(btf, type))
>> + return -1;
>> + if (BTF_INFO_KIND(type->info) == BTF_KIND_FUNC) {
>> + if (add_func(btf, type))
>> + return -1;
>> + }
>> + size = btf_type_size(type);
>> + if (size < 0) {
>> + tep_warning("Invalid type %d\n", BTF_INFO_KIND(type->info));
>> + return -1;
>> + }
>> + type = (void *)type + size;
>> + }
>> +
>> + qsort_r(btf->funcs, btf->nr_funcs, sizeof(btf->funcs[0]), cmp_funcs, btf);
>> + return 0;
>> +}
>> +
>> +__hidden void tep_btf_free(struct tep_btf *btf)
>> +{
>> + if (!btf)
>> + return;
>> +
>> + free((void *)btf->strings);
>> + free(btf->types);
>> + free(btf->funcs);
>> + free(btf->raw_data);
>> +}
>> +
>> +/** tep_btf_init - Initialize btf with raw data
>> + * @raw_data: Allocated data to use for BTF (/sys/kernel/btf/vmlinux)
>> + * @data_size: The amount of data in @raw_data
>> + *
>> + * The @raw_data ownership belongs to this function when called.
>> + * It must be allocated by the glibc allocator as it will be freed
>> + * when the BTF descriptor is freed. It will not be freed if this
>> + * function returns failure (NULL).
>> + *
>> + * Returns: On success it returns the BTF descriptor, and he @raw_data
>> + * will now be apart of it (it will be freed by tep_btf_free().
>> + * On failure it returns NULL, and the @raw_data is NOT freed.
>> + */
>> +static struct tep_btf *tep_btf_init(void *raw_data, size_t data_size)
>> +{
>> + struct tep_btf *btf;
>> +
>> + btf = calloc(1, sizeof(*btf));
>> + if (!btf)
>> + return NULL;
>> +
>> + btf->raw_data = raw_data;
>> + btf->raw_size = data_size;
>> + btf->hdr = raw_data;
>> + if (btf->hdr->hdr_len < sizeof(*btf->hdr)) {
>> + tep_warning("Header (%d) smaller than expected header %zd",
>> + btf->hdr->hdr_len, sizeof(*btf->hdr));
>> + goto fail;
>> + }
>
> btf->hdr->magic and btf->hdr->version should be checked to catch any garbage input and
> breaking change to the format:
>
> The magic is 0xeB9F, which has different encoding for big and little endian systems,
> and can be used to test whether BTF is generated for big- or little-endian target.
>
> https://docs.kernel.org/bpf/btf.html#btf-type-and-string-encoding
>
>> +
>> + btf->data = btf->raw_data + btf->hdr->hdr_len;
>> +
>> + btf->strings = btf->data + btf->hdr->str_off;
>
> This pointer crafting without input validation looks like a CVE-in-waiting.
> Using libbpf would indeed add a dependency, but it seems those checks are taken care of:
> https://github.com/libbpf/libbpf/blob/58dd1f58b57294b2e59482245b29e46f1812b82d/src/btf.c#L258
>
> This is not to say libbpf is perfect, but there also seem to be some people running fuzzers on it
> so issues of this kind have a higher chance of getting found, e.g.
> https://nvd.nist.gov/vuln/detail/CVE-2025-29481
>
> I suppose there is no silver bullet here, as adding dependencies to a C program is a pain
> as it breaks downstream build recipes ...
>
> Alternatively, the Rust parser has no BTF-related buffer issue, but that's because it does not
> implement anything BTF-related yet :)
Another thing that libbpf checks is that the type data are aligned on 4 bytes:
https://github.com/libbpf/libbpf/blob/58dd1f58b57294b2e59482245b29e46f1812b82d/src/btf.c#L269
This is required for soundly casting void* to the types in btf.h, which seem to be written with
that assumption in mind even though it's not explicitly documented:
struct btf_enum64 {
__u32 name_off;
__u32 val_lo32;
__u32 val_hi32;
};
libtraceevent requires an allocation from the libc which already guarantees that alignment but having an explicit
check is not a bad idea in case that were to change.
>
>> + printf("str len = %d\n", btf->hdr->str_len);
>> +
>> + if (load_types(btf) < 0)
>> + goto fail;
>> +
>> + return btf;
>> + fail:
>> + btf->raw_data = NULL;
>> + tep_btf_free(btf);
>> + return NULL;
>> +}
>> +
>> +/**
>> + * tep_load_btf - Load BTF information into a tep handle
>> + * @tep: The tep handle to load the BTF info into
>> + * @raw_data: The raw data containing the BTF file
>> + * @data_size: The amount of data in @raw_data
>> + *
>> + * Initializes BTF into the @tep handle. If it had already had BTF
>> + * loaded, it will free the previous BTF and recreate it from @raw_data.
>> + *
>> + * Returns: 0 on success and -1 on failure.
>> + */
>> +int tep_load_btf(struct tep_handle *tep, void *raw_data, size_t data_size)
>> +{
>> + /* If btf was already loaded, free it */
>> + tep_btf_free(tep->btf);
>> +
>> + tep->btf = tep_btf_init(raw_data, data_size);
>> + if (!tep->btf)
>> + return -1;
>> + return 0;
>> +}
>> +
>> +static struct btf_type *btf_get_type(struct tep_btf *btf, int id)
>> +{
>> + if (!id || id > btf->nr_types)
>> + return NULL;
>> +
>> + return btf->types[id - 1];
>> +}
>> +
>> +static struct btf_type *btf_skip_modifiers(struct tep_btf *btf, int id)
>> +{
>> + struct btf_type *t = btf_get_type(btf, id);
>> +
>> + for (;;) {
>> + switch (BTF_INFO_KIND(t->info)) {
>> + case BTF_KIND_TYPEDEF:
>> + case BTF_KIND_VOLATILE:
>> + case BTF_KIND_CONST:
>> + case BTF_KIND_RESTRICT:
>> + case BTF_KIND_TYPE_TAG:
>> + id = t->type;
>> + t = btf_get_type(btf, t->type);
>> + continue;
>> + }
>> + break;
>> + }
>> +
>> + return t;
>> +}
>> +
>> +static void assign_arg(unsigned long long *arg, void *args, int size, int a)
>> +{
>> + *arg = size == 4 ?
>> + *(unsigned int *)(args + a * sizeof(int)) :
>> + *(unsigned long long *)(args + a * sizeof(long long));
>> +}
>> +
>> +/**
>> + * tep_btf_print_args - Print function arguments from BTF info
>> + * @tep: The tep descriptor to use
>> + * @s: The trace_seq to write the arguments into
>> + * @args: The array that holds the arguments
>> + * @nmem: The number of arguments
>> + * @size: The size of each item in args (4 or 8).
>> + * @func: The name of the function.
>> + *
>> + * Loads up the @s with a list of arguments for the function based on
>> + * the @args.
>> + *
>> + * If there's no BTF loaded or @func is not found, then it just writes
>> + * the @args as raw numbers. Otherwise it will pretty print the
>> + * arguments based on the function info in BTF.
>> + *
>> + * Returns: 0 on success and -1 on failure.
>> + */
>> +int tep_btf_print_args(struct tep_handle *tep, struct trace_seq *s, void *args,
>> + int nmem, int size, const char *func)
>> +{
>> + struct tep_btf *btf = tep->btf;
>> + struct btf_type *type = tep_btf_find_func(btf, func);
>> + struct btf_param *param;
>> + unsigned long long arg;
>> + unsigned int encode;
>> + const char *param_name;
>> + int a, p, x, nr;
>> +
>> + if (size != 4 && size != 8)
>> + return -1;
>> +
>> + if (!type) {
>> + for (int i = 0; i < nmem; i++) {
>> + assign_arg(&arg, args, size, i);
>> + trace_seq_printf(s, "%llx", arg);
>> + if (i + 1 < nmem)
>> + trace_seq_puts(s, ", ");
>> + }
>> + return 0;
>> + }
>> +
>> + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC) {
>> + printf("Invalid func type %d %s\n", BTF_INFO_KIND(type->info),
>> + btf_type_str(type));
>> + return -1;
>> + }
>> +
>> + /* Get the function proto */
>> + type = btf_get_type(btf, type->type);
>> +
>> + /* No proto means "()" ? */
>> + if (!type)
>> + return 0;
>> +
>> + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC_PROTO) {
>> + printf("Invalid func proto type %d %s\n", BTF_INFO_KIND(type->info),
>> + btf_type_str(type));
>> + return -1;
>> + }
>> +
>> + /* Get the number of parameters */
>> + nr = BTF_INFO_VLEN(type->info);
>> +
>> + /* The parameters are right after the FUNC_PROTO type */
>> + param = ((void *)type) + sizeof(*type);
>> +
>> + for (a = 0, p = 0; p < nr; a++, p++) {
>> + struct btf_type *t;
>> +
>> + if (p)
>> + trace_seq_puts(s, ", ");
>> +
>> + if (a == nmem) {
>> + trace_seq_puts(s, "...");
>> + break;
>> + }
>> +
>> + assign_arg(&arg, args, size, a);
>> +
>> + param_name = btf_name(btf, param[p].name_off);
>> + if (param_name)
>> + trace_seq_printf(s, "%s=", param_name);
>> +
>> + t = btf_skip_modifiers(btf, param[p].type);
>> +
>> + switch (t ? BTF_INFO_KIND(t->info) : BTF_KIND_UNKN) {
>> + case BTF_KIND_UNKN:
>> + trace_seq_putc(s, '?');
>> + /* Still print unknown type values */
>> + /* fallthough */
>> + case BTF_KIND_PTR:
>> + trace_seq_printf(s, "0x%llx", arg);
>> + break;
>> + case BTF_KIND_INT:
>> + encode = *(int *)((void *)t + sizeof(*t));
>> + /* Print unsigned ints as hex */
>> + if (BTF_INT_ENCODING(encode) & BTF_INT_SIGNED)
>> + trace_seq_printf(s, "%lld", arg);
>> + else
>> + trace_seq_printf(s, "0x%llx", arg);
>> + break;
>> + case BTF_KIND_ENUM:
>> + trace_seq_printf(s, "%lld", arg);
>> + break;
>> + default:
>> + /* This does not handle complex arguments */
>> + trace_seq_printf(s, "(%s)[0x%llx", btf_type_str(t), arg);
>> + for (x = sizeof(long); x < t->size; x += sizeof(long)) {
>> + trace_seq_putc(s, ':');
>> + if (++a == nmem) {
>> + trace_seq_puts(s, "...]");
>> + return 0;
>> + }
>> + assign_arg(&arg, args, size, a);
>> + trace_seq_printf(s, "0x%llx", arg);
>> + }
>> + trace_seq_putc(s, ']');
>> + break;
>> + }
>> + }
>> + return 0;
>> +}
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle
2025-08-04 16:07 ` Douglas Raillard
2025-08-04 16:26 ` Douglas Raillard
@ 2025-08-04 17:34 ` Steven Rostedt
1 sibling, 0 replies; 14+ messages in thread
From: Steven Rostedt @ 2025-08-04 17:34 UTC (permalink / raw)
To: Douglas Raillard
Cc: linux-trace-devel, Masami Hiramatsu, Namhyung Kim, Takaya Saeki,
Ian Rogers, aahringo
On Mon, 4 Aug 2025 17:07:04 +0100
Douglas Raillard <douglas.raillard@arm.com> wrote:
> > +struct btf_header;
> > +struct btf_type;
> > +
> > +struct tep_btf {
> > + struct btf_header *hdr;
> > + const char *strings;
> > + struct btf_type **types;
> > + unsigned long nr_types;
> > + struct btf_type **funcs;
> > + unsigned long nr_funcs;
> > + void *data;
> > + size_t raw_size;
> > + void *raw_data;
> > +};
> > +
> > +#define REALLOC_SIZE (1 << 10)
> > +#define REALLOC_MASK (REALLOC_SIZE - 1)
> > +
> > +static const char *btf_name(struct tep_btf *btf, int off)
> > +{
> > + if (off < btf->hdr->str_len)
>
> That assumes the input is well-behaved and str_len is set to the actual length of
> the strings data, but it could be anything. tep_btf_init() should check that
> btf->hdr->strings + btf->hdr->str_len point inside the provided data.
Heh! You know I thought about that and then said to myself "heck, it's user
space, we can let it crash!" ;-)
But yeah, it is a library an we should make it a bit more robust.
>
> > + return btf->strings + off;> + return "";
> > +}
> > +
> > +/* List taken from the Linux kernel */
> > +static const char * const btf_kind_str[NR_BTF_KINDS] = {
> > + [BTF_KIND_UNKN] = "UNKNOWN",
> > + [BTF_KIND_INT] = "INT",
> > + [BTF_KIND_PTR] = "PTR",
> > + [BTF_KIND_ARRAY] = "ARRAY",
> > + [BTF_KIND_STRUCT] = "STRUCT",
> > + [BTF_KIND_UNION] = "UNION",
> > + [BTF_KIND_ENUM] = "ENUM",
> > + [BTF_KIND_FWD] = "FWD",
> > + [BTF_KIND_TYPEDEF] = "TYPEDEF",
> > + [BTF_KIND_VOLATILE] = "VOLATILE",
> > + [BTF_KIND_CONST] = "CONST",
> > + [BTF_KIND_RESTRICT] = "RESTRICT",
> > + [BTF_KIND_FUNC] = "FUNC",
> > + [BTF_KIND_FUNC_PROTO] = "FUNC_PROTO",
> > + [BTF_KIND_VAR] = "VAR",
> > + [BTF_KIND_DATASEC] = "DATASEC",
> > + [BTF_KIND_FLOAT] = "FLOAT",
> > + [BTF_KIND_DECL_TAG] = "DECL_TAG",
> > + [BTF_KIND_TYPE_TAG] = "TYPE_TAG",
> > + [BTF_KIND_ENUM64] = "ENUM64",
> > +};
> > +
> > +static int load_types(struct tep_btf *btf)
> > +{
> > + struct btf_type *type;
> > + void *start, *end;
> > + int size;
> > +
> > + start = btf->data + btf->hdr->type_off;
> > + end = start + btf->hdr->type_len;
>
> Those should also be validated.
Yeah, probably.
>
> > +
> > + if (end > btf->raw_data + btf->raw_size)
> > + return -1;
> > +
> > + for (type = start; (void *)type < end;) {
> > + if (add_type(btf, type))
> > + return -1;
> > + if (BTF_INFO_KIND(type->info) == BTF_KIND_FUNC) {
> > + if (add_func(btf, type))
> > + return -1;
> > + }
> > + size = btf_type_size(type);
> > + if (size < 0) {
> > + tep_warning("Invalid type %d\n", BTF_INFO_KIND(type->info));
> > + return -1;
> > + }
> > + type = (void *)type + size;
> > + }
> > +
> > +static struct tep_btf *tep_btf_init(void *raw_data, size_t data_size)
> > +{
> > + struct tep_btf *btf;
> > +
> > + btf = calloc(1, sizeof(*btf));
> > + if (!btf)
> > + return NULL;
> > +
> > + btf->raw_data = raw_data;
> > + btf->raw_size = data_size;
> > + btf->hdr = raw_data;
> > + if (btf->hdr->hdr_len < sizeof(*btf->hdr)) {
> > + tep_warning("Header (%d) smaller than expected header %zd",
> > + btf->hdr->hdr_len, sizeof(*btf->hdr));
> > + goto fail;
> > + }
>
> btf->hdr->magic and btf->hdr->version should be checked to catch any garbage input and
> breaking change to the format:
>
> The magic is 0xeB9F, which has different encoding for big and little endian systems,
> and can be used to test whether BTF is generated for big- or little-endian target.
>
> https://docs.kernel.org/bpf/btf.html#btf-type-and-string-encoding
>
Yep. As I stated above, the thought crossed my mind, and then my laziness
came across and said "nah".
I was more trying to get this code out to the public more than adding it.
But you are correct, it should have these validation checks.
-- Steve
> > +
> > + btf->data = btf->raw_data + btf->hdr->hdr_len;
> > +
> > + btf->strings = btf->data + btf->hdr->str_off;
>
> This pointer crafting without input validation looks like a CVE-in-waiting.
> Using libbpf would indeed add a dependency, but it seems those checks are taken care of:
> https://github.com/libbpf/libbpf/blob/58dd1f58b57294b2e59482245b29e46f1812b82d/src/btf.c#L258
>
> This is not to say libbpf is perfect, but there also seem to be some people running fuzzers on it
> so issues of this kind have a higher chance of getting found, e.g.
> https://nvd.nist.gov/vuln/detail/CVE-2025-29481
>
> I suppose there is no silver bullet here, as adding dependencies to a C program is a pain
> as it breaks downstream build recipes ...
>
> Alternatively, the Rust parser has no BTF-related buffer issue, but that's because it does not
> implement anything BTF-related yet :)
>
> > + printf("str len = %d\n", btf->hdr->str_len);
> > +
> > + if (load_types(btf) < 0)
> > + goto fail;
> > +
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2025-08-04 17:34 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-31 18:52 [PATCH 0/2] libtraceevent: Add BTF parsing for function arguments Steven Rostedt
2025-07-31 18:52 ` [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle Steven Rostedt
2025-08-04 11:47 ` Douglas Raillard
2025-08-04 12:43 ` Steven Rostedt
2025-08-04 14:18 ` Douglas Raillard
2025-08-04 14:41 ` Steven Rostedt
2025-08-04 15:01 ` Douglas Raillard
2025-08-04 16:07 ` Douglas Raillard
2025-08-04 16:26 ` Douglas Raillard
2025-08-04 17:34 ` Steven Rostedt
2025-07-31 18:52 ` [PATCH 2/2] libtraceevent: Add man page for the new BTF functions Steven Rostedt
2025-08-04 11:09 ` Douglas Raillard
2025-08-04 12:32 ` Steven Rostedt
2025-08-04 13:19 ` Douglas Raillard
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).