[PATCH v4 4/6] trace-cmd record / report: Record BTF file if function arguments are recorded

Steven Rostedt <[email protected]> Tue, 3 Feb 2026 18:32:27 -0500
Newsgroups org.kernel.vger.linux-trace-devel
Message-ID <[email protected]>
From: "Steven Rostedt (Google)" <[email protected]>

If function tracer is recorded with func-args option set, or the
function_graph tracer is recorded with the funcgraph-args option set, then
record the BTF file in a new BTF_FILE section in the trace.dat file.

This is compressed on disk, and when the trace.dat file is read, it is
loaded into memory and sent to the libtraceevent tep_load_btf() function.

This will be used to show arguments of the function tracer:

             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)

Note, the parsing of BTF from the trace.dat file requires libtraceevent
version 1.9 or higher.

Signed-off-by: Steven Rostedt (Google) <[email protected]>
---
Changes since v3: https://lore.kernel.org/[email protected]

- Put in "BTF" name for added section

 Makefile                                      |  9 +++
 .../include/private/trace-cmd-private.h       |  3 +
 lib/trace-cmd/trace-compress.c                | 26 ++++++++
 lib/trace-cmd/trace-input.c                   | 26 ++++++++
 lib/trace-cmd/trace-output.c                  | 64 +++++++++++++++++++
 meson.build                                   |  3 +
 tracecmd/trace-record.c                       | 42 +++++++++++-
 7 files changed, 172 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index c594633b210b..aea6a1aae111 100644
--- a/Makefile
+++ b/Makefile
@@ -230,6 +230,8 @@ LIBTRACEFS=libtracefs
 TEST_LIBTRACEEVENT := $(shell sh -c "$(PKG_CONFIG) --atleast-version $(LIBTRACEEVENT_MIN_VERSION) $(LIBTRACEEVENT) > /dev/null 2>&1 && echo y")
 TEST_LIBTRACEFS := $(shell sh -c "$(PKG_CONFIG) --atleast-version $(LIBTRACEFS_MIN_VERSION) $(LIBTRACEFS) > /dev/null 2>&1 && echo y")
 
+TEST_LIBTRACEEVENT_BTF := $(shell sh -c "$(PKG_CONFIG) --atleast-version 1.9 $(LIBTRACEEVENT) > /dev/null 2>&1 && echo y")
+
 ifeq ("$(TEST_LIBTRACEEVENT)", "y")
 LIBTRACEEVENT_CFLAGS := $(shell sh -c "$(PKG_CONFIG) --cflags $(LIBTRACEEVENT)")
 LIBTRACEEVENT_LDLAGS := $(shell sh -c "$(PKG_CONFIG) --libs $(LIBTRACEEVENT)")
@@ -247,6 +249,13 @@ warning:
 	@echo "********************************************"
 endif
 
+ifeq ("$(TEST_LIBTRACEEVENT_BTF)", "y")
+CFLAGS += -DHAVE_KERNEL_BTF
+else
+.PHONY: warning
+$(info    Min version libtraceevent 1.9 not found. Will not use BTF for function args)
+endif
+
 export LIBTRACEEVENT_CFLAGS LIBTRACEEVENT_LDLAGS
 
 ifeq ("$(TEST_LIBTRACEFS)", "y")
diff --git a/lib/trace-cmd/include/private/trace-cmd-private.h b/lib/trace-cmd/include/private/trace-cmd-private.h
index 809e244b2d1c..4743bbc62048 100644
--- a/lib/trace-cmd/include/private/trace-cmd-private.h
+++ b/lib/trace-cmd/include/private/trace-cmd-private.h
@@ -159,6 +159,7 @@ enum {
 	TRACECMD_OPTION_PRINTK,
 	TRACECMD_OPTION_CMDLINES,
 	TRACECMD_OPTION_BUFFER_TEXT,
+	TRACECMD_OPTION_BTF_FILE,
 	TRACECMD_OPTION_MAX,
 };
 
@@ -340,6 +341,7 @@ int tracecmd_append_buffer_cpu_data(struct tracecmd_output *handle,
 struct tracecmd_output *tracecmd_get_output_handle_fd(int fd);
 unsigned long tracecmd_get_out_file_version(struct tracecmd_output *handle);
 size_t tracecmd_get_out_file_offset(struct tracecmd_output *handle);
+int tracecmd_append_btf_file(struct tracecmd_output *handle);
 
 /* --- Reading the Fly Recorder Trace --- */
 
@@ -568,6 +570,7 @@ int tracecmd_uncompress_chunk(struct tracecmd_compression *handle,
 			      struct tracecmd_compress_chunk *chunk, char *data);
 int tracecmd_load_chunks_info(struct tracecmd_compression *handle,
 			      struct tracecmd_compress_chunk **chunks_info);
+void *tracecmd_uncompress_buffer(struct tracecmd_compression *handle, size_t *size);
 /* --- Plugin handling --- */
 extern struct tep_plugin_option trace_ftrace_options[];
 
diff --git a/lib/trace-cmd/trace-compress.c b/lib/trace-cmd/trace-compress.c
index 3e10cf5bc385..fe4ab84a7e27 100644
--- a/lib/trace-cmd/trace-compress.c
+++ b/lib/trace-cmd/trace-compress.c
@@ -366,6 +366,32 @@ out:
 	free(buf);
 	return ret;
 }
+/**
+ * tracecmd_uncompress_buffer - Returned the current uncompressed buffer
+ * @handle: compression handle
+ * @size: A pointer to place the size of the uncompressed buffer in
+ *
+ * Returns the current uncompressed buffer and resets the compression
+ * @handle.
+ *
+ * Returns the buffer (must be freed with free()) or NULL.
+ *  @size (if not NULL) will contain the size of the uncompressed buffer.
+ */
+void *tracecmd_uncompress_buffer(struct tracecmd_compression *handle, size_t *size)
+{
+	void *buffer;
+
+	if (!handle)
+		return NULL;
+
+	if (size)
+		*size = handle->capacity;
+
+	buffer = handle->buffer;
+	handle->buffer = NULL;
+	tracecmd_compress_reset(handle);
+	return buffer;
+}
 
 /**
  * tracecmd_compress_buffer_write - write() to compression buffer
diff --git a/lib/trace-cmd/trace-input.c b/lib/trace-cmd/trace-input.c
index fba9c5b6a7fc..7e03c0da5485 100644
--- a/lib/trace-cmd/trace-input.c
+++ b/lib/trace-cmd/trace-input.c
@@ -1026,6 +1026,28 @@ static int read_ftrace_printk(struct tracecmd_input *handle)
 	return 0;
 }
 
+#ifdef HAVE_KERNEL_BTF
+static int read_btf(struct tracecmd_input *handle)
+{
+	void *raw_data;
+	size_t size;
+
+	raw_data = tracecmd_uncompress_buffer(handle->compress, &size);
+	if (!raw_data)
+		return -1;
+
+	tep_load_btf(handle->pevent, raw_data, size);
+
+	free(raw_data);
+	return 0;
+}
+#else
+static inline int read_btf(struct tracecmd_input *handle)
+{
+	return 0;
+}
+#endif
+
 static int read_and_parse_cmdlines(struct tracecmd_input *handle);
 
 /**
@@ -1203,6 +1225,9 @@ static int handle_section(struct tracecmd_input *handle, struct file_section *se
 	case TRACECMD_OPTION_CMDLINES:
 		ret = read_and_parse_cmdlines(handle);
 		break;
+	case TRACECMD_OPTION_BTF_FILE:
+		ret = read_btf(handle);
+		break;
 	default:
 		ret = 0;
 		break;
@@ -4208,6 +4233,7 @@ static int handle_options(struct tracecmd_input *handle)
 		case TRACECMD_OPTION_KALLSYMS:
 		case TRACECMD_OPTION_PRINTK:
 		case TRACECMD_OPTION_CMDLINES:
+		case TRACECMD_OPTION_BTF_FILE:
 			if (size < 8)
 				break;
 			section_add_or_update(handle, option, -1,
diff --git a/lib/trace-cmd/trace-output.c b/lib/trace-cmd/trace-output.c
index ec72011dc2ad..c239f50b10e9 100644
--- a/lib/trace-cmd/trace-output.c
+++ b/lib/trace-cmd/trace-output.c
@@ -2324,6 +2324,70 @@ int tracecmd_write_cmdlines(struct tracecmd_output *handle)
 	return 0;
 }
 
+#define BTF_FILE "/sys/kernel/btf/vmlinux"
+
+int tracecmd_append_btf_file(struct tracecmd_output *handle)
+{
+	tsize_t offset, size, check_size;
+	struct tracecmd_option *option;
+	struct iovec *vect;
+	struct stat st;
+	int ret;
+
+	if (!HAS_SECTIONS(handle))
+		return -1;
+
+	ret = stat(BTF_FILE, &st);
+	if (ret < 0)
+		return -1;
+
+	offset = write_compress_section_header(handle, TRACECMD_OPTION_BTF_FILE, "BTF", true);
+	if (offset == (off_t)-1)
+		return -1;
+
+	tcmd_out_compression_start(handle);
+
+	check_size = copy_file(handle, BTF_FILE);
+	if (st.st_size != check_size) {
+		errno = EINVAL;
+		tracecmd_warning("error in size of file '%s'", BTF_FILE);
+		return -1;
+	}
+
+	if (tcmd_out_compression_end(handle))
+		return -1;
+
+	if (tcmd_out_update_section_header(handle, offset))
+		return -1;
+
+	return 0;
+
+	/*
+	 * BTF file
+	 *  - data offset in the file
+	 *  - data size
+	 */
+
+	vect = calloc(2, sizeof(struct iovec));
+	if (!vect)
+		return -1;
+	vect[0].iov_base = &offset;
+	vect[0].iov_len = 8;
+	vect[1].iov_base = &size;
+	vect[1].iov_len = 8;
+
+	option = tracecmd_add_option_v(handle, TRACECMD_OPTION_BTF_FILE, vect, 2);
+	free(vect);
+
+	if (!option)
+		return -1;
+
+	if (do_lseek(handle, 0, SEEK_END) == (off_t)-1)
+		return -1;
+
+	return 0;
+}
+
 static char *get_clock(struct tracecmd_output *handle)
 {
 	struct tracefs_instance *inst;
diff --git a/meson.build b/meson.build
index e7a0398761f3..c86d1a6b7f0b 100644
--- a/meson.build
+++ b/meson.build
@@ -25,8 +25,11 @@ htmldir = join_paths(prefixdir, get_option('htmldir'))
 conf = configuration_data()
 
 libtraceevent_dep = dependency('libtraceevent', version: '>= 1.5.0', required: true)
+libtraceevent_btf_dep = dependency('libtraceevent', version: '>= 1.9', required: false)
 libtracefs_dep = dependency('libtracefs', version: '>= 1.8.0', required: true)
 
+conf.set('HAVE_KERNEL_BTF', libtraceevent_btf_dep.found(), description: 'Is BTF parsing available?')
+
 threads_dep = dependency('threads', required: true)
 dl_dep = cc.find_library('dl', required : false)
 
diff --git a/tracecmd/trace-record.c b/tracecmd/trace-record.c
index 4e1157c99294..4872d0d77c8e 100644
--- a/tracecmd/trace-record.c
+++ b/tracecmd/trace-record.c
@@ -4633,12 +4633,46 @@ error:
 	return NULL;
 }
 
+static void check_need_btf(bool *need_btf, struct tracefs_instance *instance)
+{
+	bool func_graph_trace;
+	bool func_trace;
+	char *current;
+
+	/* Nothing to do if it's already set */
+	if (*need_btf)
+		return;
+
+	current = tracefs_instance_file_read(instance, "current_tracer", NULL);
+	if (!current)
+		return;
+
+	func_trace = strcmp(current, "function\n") == 0;
+	func_graph_trace = strcmp(current, "function_graph\n") == 0;
+
+	free(current);
+	if (!func_trace && !func_graph_trace)
+		return;
+
+	if (func_trace)
+		current = tracefs_instance_file_read(instance, "options/func-args", NULL);
+	else
+		current = tracefs_instance_file_read(instance, "options/funcgraph-args", NULL);
+	if (!current)
+		return;
+
+	*need_btf = strncmp(current, "1", 1) == 0;
+
+	free(current);
+}
+
 static void record_data(struct common_record_context *ctx)
 {
 	struct tracecmd_output *handle;
 	struct buffer_instance *instance;
 	bool have_proxy = false;
 	bool local = false;
+	bool need_btf = false;
 	int max_cpu_count = local_cpu_count;
 	char **temp_files;
 	int i;
@@ -4734,11 +4768,17 @@ static void record_data(struct common_record_context *ctx)
 							tracefs_instance_get_name(instance->tracefs),
 							cpus);
 				add_buffer_stat(handle, instance);
+				check_need_btf(&need_btf, instance->tracefs);
 			}
 		}
 
-		if (!no_top_instance() && !top_instance.msg_handle)
+		if (!no_top_instance() && !top_instance.msg_handle) {
 			print_stat(&top_instance);
+			check_need_btf(&need_btf, top_instance.tracefs);
+		}
+
+		if (need_btf)
+			tracecmd_append_btf_file(handle);
 
 		for_all_instances(instance) {
 			add_pid_maps(handle, instance);
-- 
2.51.0