diff mbox series

libtracecmd: Support changing /proc/kallsyms

Message ID 20250416231325.14113-1-iii@linux.ibm.com (mailing list archive)
State New
Headers show
Series libtracecmd: Support changing /proc/kallsyms | expand

Commit Message

Ilya Leoshkevich April 16, 2025, 11:13 p.m. UTC
Running BPF selftests under trace-cmd intermittently fails with:

    error in size of file '/proc/kallsyms'

This is because these selftests load and unload BPF programs.
bpf_prog_put() uses workqueues and RCU, so these programs disappear
from /proc/kallsyms after a delay.

trace-cmd reads /proc/kallsyms twice: the first time to compute its
size, and the second time to copy it into the trace file. If the
resulting sizes don't match, which is what happens in this case,
recording fails.

Fix by first copying /proc/kallsyms into a temporary file, and then
into the trace file. An alternative would be to read it into a
malloc()-ed buffer, but this would increase trace-cmd memory usage,
since /proc/kallsyms can be a few dozen megabytes large. In case
/tmp is tmpfs, both solutions are almost equivalent.

Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
---
 lib/trace-cmd/trace-output.c | 66 ++++++++++++++++++++++++++++--------
 1 file changed, 52 insertions(+), 14 deletions(-)
diff mbox series

Patch

diff --git a/lib/trace-cmd/trace-output.c b/lib/trace-cmd/trace-output.c
index 00b87a19..d85021be 100644
--- a/lib/trace-cmd/trace-output.c
+++ b/lib/trace-cmd/trace-output.c
@@ -1129,14 +1129,52 @@  err:
 		tracecmd_warning("can't set kptr_restrict");
 }
 
+static int copy_to_temp_fd(const char *path, tsize_t *size)
+{
+	char temp_path[] = "/tmp/trace_XXXXXX";
+	char buf[BUFSIZ];
+	int fd, temp_fd;
+	stsize_t r;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		goto fail;
+
+	temp_fd = mkstemp(temp_path);
+	if (temp_fd < 0)
+		goto fail_close;
+	unlink(temp_path);
+
+	*size = 0;
+	for (;;) {
+		r = read(fd, buf, sizeof(buf));
+		if (r == 0)
+			break;
+		if (r < 0 || __do_write_check(temp_fd, buf, r))
+			goto fail_close_temp;
+		*size += r;
+	}
+
+	if (lseek(temp_fd, 0, SEEK_SET) == (off_t)-1)
+		goto fail_close_temp;
+
+	close(fd);
+	return temp_fd;
+
+fail_close_temp:
+	close(temp_fd);
+fail_close:
+	close(fd);
+fail:
+	return -1;
+}
+
 static int read_proc_kallsyms(struct tracecmd_output *handle, bool compress)
 {
 	enum tracecmd_section_flags flags = 0;
-	unsigned int size, check_size, endian4;
 	const char *path = "/proc/kallsyms";
-	tsize_t offset;
-	struct stat st;
-	int ret;
+	tsize_t check_size, offset, size;
+	int endian4, fd, ret;
 
 	if (!tcmd_check_out_state(handle, TRACECMD_FILE_KALLSYMS)) {
 		tracecmd_warning("Cannot read kallsyms, unexpected state 0x%X",
@@ -1155,36 +1193,36 @@  static int read_proc_kallsyms(struct tracecmd_output *handle, bool compress)
 		return -1;
 
 	tcmd_out_compression_start(handle, compress);
-	ret = stat(path, &st);
-	if (ret < 0) {
+	set_proc_kptr_restrict(0);
+	fd = copy_to_temp_fd(path, &size);
+	set_proc_kptr_restrict(1);
+	if (fd < 0) {
 		/* not found */
 		size = 0;
 		endian4 = convert_endian_4(handle, size);
 		ret = tcmd_do_write_check(handle, &endian4, 4);
 		goto out;
 	}
-	size = get_size(path);
 	endian4 = convert_endian_4(handle, size);
 	ret = tcmd_do_write_check(handle, &endian4, 4);
 	if (ret)
-		goto out;
+		goto out_close;
 
-	set_proc_kptr_restrict(0);
-	check_size = copy_file(handle, path);
+	check_size = copy_file_fd(handle, fd, 0);
 	if (size != check_size) {
 		errno = EINVAL;
 		tracecmd_warning("error in size of file '%s'", path);
-		set_proc_kptr_restrict(1);
 		ret = -1;
-		goto out;
+		goto out_close;
 	}
-	set_proc_kptr_restrict(1);
 
 	ret = tcmd_out_compression_end(handle, compress);
 	if (ret)
-		goto out;
+		goto out_close;
 
 	ret = tcmd_out_update_section_header(handle, offset);
+out_close:
+	close(fd);
 out:
 	if (!ret)
 		handle->file_state = TRACECMD_FILE_KALLSYMS;