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;
 };