btf loader: Support raw BTF as available in /sys/kernel/btf/vmlinux
Be it automatically when no -F option is passed and
/sys/kernel/btf/vmlinux is available, or when /sys/kernel/btf/vmlinux is
passed as the filename to the tool, i.e.:
$ pahole -C list_head
struct list_head {
struct list_head * next; /* 0 8 */
struct list_head * prev; /* 8 8 */
/* size: 16, cachelines: 1, members: 2 */
/* last cacheline: 16 bytes */
};
$ strace -e openat pahole -C list_head |& grep /sys/kernel/btf/
openat(AT_FDCWD, "/sys/kernel/btf/vmlinux", O_RDONLY) = 3
$
$ pahole -C list_head /sys/kernel/btf/vmlinux
struct list_head {
struct list_head * next; /* 0 8 */
struct list_head * prev; /* 8 8 */
/* size: 16, cachelines: 1, members: 2 */
/* last cacheline: 16 bytes */
};
$
If one wants to grab the matching vmlinux to use its DWARF info instead,
which is useful to compare the results with what we have from BTF, for
instance, its just a matter of using '-F dwarf'.
This in turn shows something that at first came as a surprise, but then
has a simple explanation:
For very common data structures, that will probably appear in all of the
DWARF CUs (Compilation Units), like 'struct list_head', using '-F dwarf'
is faster:
[acme@quaco pahole]$ perf stat -e cycles pahole -F btf -C list_head > /dev/null
Performance counter stats for 'pahole -F btf -C list_head':
45,722,518 cycles:u
0.023717300 seconds time elapsed
0.016474000 seconds user
0.007212000 seconds sys
[acme@quaco pahole]$ perf stat -e cycles pahole -F dwarf -C list_head > /dev/null
Performance counter stats for 'pahole -F dwarf -C list_head':
14,170,321 cycles:u
0.006668904 seconds time elapsed
0.005562000 seconds user
0.001109000 seconds sys
[acme@quaco pahole]$
But for something that is more specific to a subsystem, the DWARF loader
will have to process way more stuff till it gets to that struct:
$ perf stat -e cycles pahole -F dwarf -C tcp_sock > /dev/null
Performance counter stats for 'pahole -F dwarf -C tcp_sock':
31,579,795,238 cycles:u
8.332272930 seconds time elapsed
8.032124000 seconds user
0.286537000 seconds sys
$
While using the BTF loader the time should be constant, as it loads
everything from /sys/kernel/btf/vmlinux:
$ perf stat -e cycles pahole -F btf -C tcp_sock > /dev/null
Performance counter stats for 'pahole -F btf -C tcp_sock':
48,823,488 cycles:u
0.024102760 seconds time elapsed
0.012035000 seconds user
0.012046000 seconds sys
$
Above I used '-F btf' just to show that it can be used, but its not
really needed, i.e. those are equivalent:
$ strace -e openat pahole -F btf -C list_head |& grep /sys/kernel/btf/vmlinux
openat(AT_FDCWD, "/sys/kernel/btf/vmlinux", O_RDONLY) = 3
$ strace -e openat pahole -C list_head |& grep /sys/kernel/btf/vmlinux
openat(AT_FDCWD, "/sys/kernel/btf/vmlinux", O_RDONLY) = 3
$
The btf_raw__load() function that ends up being grafted into the
preexisting btf_elf routines was based on libbpf's btf_load_raw().
Acked-by: Alexei Starovoitov <ast@fb.com>
Cc: Andrii Nakryiko <andriin@fb.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
diff --git a/dwarves.c b/dwarves.c
index 833109a..8b2887f 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -2074,6 +2074,19 @@
int i, err = 0;
char running_sbuild_id[SBUILD_ID_SIZE];
+ if ((!conf || conf->format_path == NULL || strncmp(conf->format_path, "btf", 3) == 0) &&
+ access("/sys/kernel/btf/vmlinux", R_OK) == 0) {
+ int loader = debugging_formats__loader("btf");
+ if (loader == -1)
+ goto try_elf;
+
+ if (conf->conf_fprintf)
+ conf->conf_fprintf->has_alignment_info = debug_fmt_table[loader]->has_alignment_info;
+
+ if (debug_fmt_table[loader]->load_file(cus, conf, "/sys/kernel/btf/vmlinux") == 0)
+ return 0;
+ }
+try_elf:
elf_version(EV_CURRENT);
vmlinux_path__init();
diff --git a/libbtf.c b/libbtf.c
index 54a5e6a..2fbce40 100644
--- a/libbtf.c
+++ b/libbtf.c
@@ -12,6 +12,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
@@ -58,8 +60,43 @@
return val;
}
+static int btf_raw__load(struct btf_elf *btfe)
+{
+ size_t read_cnt;
+ struct stat st;
+ void *data;
+ FILE *fp;
+
+ if (stat(btfe->filename, &st))
+ return -1;
+
+ data = malloc(st.st_size);
+ if (!data)
+ return -1;
+
+ fp = fopen(btfe->filename, "rb");
+ if (!fp)
+ goto cleanup;
+
+ read_cnt = fread(data, 1, st.st_size, fp);
+ fclose(fp);
+ if (read_cnt < st.st_size)
+ goto cleanup;
+
+ btfe->swapped = 0;
+ btfe->data = data;
+ btfe->size = read_cnt;
+ return 0;
+cleanup:
+ free(data);
+ return -1;
+}
+
int btf_elf__load(struct btf_elf *btfe)
{
+ if (btfe->raw_btf)
+ return btf_raw__load(btfe);
+
int err = -ENOTSUP;
GElf_Shdr shdr;
Elf_Scn *sec = elf_section_by_name(btfe->elf, &btfe->ehdr, &shdr, ".BTF", NULL);
@@ -109,6 +146,13 @@
if (btfe->filename == NULL)
goto errout;
+ if (strcmp(filename, "/sys/kernel/btf/vmlinux") == 0) {
+ btfe->raw_btf = true;
+ btfe->wordsize = sizeof(long);
+ btfe->is_big_endian = BYTE_ORDER == BIG_ENDIAN;
+ return btfe;
+ }
+
if (elf != NULL) {
btfe->elf = elf;
} else {
diff --git a/libbtf.h b/libbtf.h
index 4e4b78d..f3c8500 100644
--- a/libbtf.h
+++ b/libbtf.h
@@ -28,6 +28,7 @@
int in_fd;
uint8_t wordsize;
bool is_big_endian;
+ bool raw_btf; // "/sys/kernel/btf/vmlinux"
uint32_t type_index;
};