From 37d703d00e41443420f5ba2dc6b46dfa1a3b54ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20V=C3=A1squez?= Date: Fri, 25 Feb 2022 17:01:37 -0500 Subject: [PATCH] libbpf-tools: Add experimental integration with BTFGen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds an experimental BTFGen[0] integration to allow some of the libbpf-tools to run in systems that don't provide BTF information. The whole process consist of two parts: (1) generating and embedding the BTF file within the tools binary and (2) using those when the tool is run. The first part is done by using the Makefile, it generates the reduced BTF files for the different eBPF objects of the tools, those files are then compressed and a C header file with its content is created. The second part is handled by a new C file that provides the logic to uncompress and save the BTF file according to the Linux distribution and kernel version where the tools is being run. [0] https://lore.kernel.org/bpf/20220215225856.671072-1-mauricio@kinvolk.io/ Signed-off-by: Mauricio Vásquez --- libbpf-tools/.gitignore | 1 + libbpf-tools/Makefile | 19 +++ libbpf-tools/Makefile.btfgen | 40 ++++++ libbpf-tools/btf_helpers.c | 247 +++++++++++++++++++++++++++++++++++ libbpf-tools/btf_helpers.h | 11 ++ 5 files changed, 318 insertions(+) create mode 100644 libbpf-tools/Makefile.btfgen create mode 100644 libbpf-tools/btf_helpers.c create mode 100644 libbpf-tools/btf_helpers.h diff --git a/libbpf-tools/.gitignore b/libbpf-tools/.gitignore index ce95db7173ec..561f94ecef1c 100644 --- a/libbpf-tools/.gitignore +++ b/libbpf-tools/.gitignore @@ -1,4 +1,5 @@ /.output +/btfhub-archive /bashreadline /bindsnoop /biolatency diff --git a/libbpf-tools/Makefile b/libbpf-tools/Makefile index 0b92f238cdc0..e60ec409aa3f 100644 --- a/libbpf-tools/Makefile +++ b/libbpf-tools/Makefile @@ -12,6 +12,7 @@ BPFCFLAGS := -g -O2 -Wall INSTALL ?= install prefix ?= /usr/local ARCH := $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/' | sed 's/ppc64le/powerpc/' | sed 's/mips.*/mips/') +BTFHUB_ARCHIVE ?= $(abspath btfhub-archive) ifeq ($(wildcard $(ARCH)/),) $(error Architecture $(ARCH) is not supported yet. Please open an issue) @@ -61,6 +62,9 @@ APPS = \ vfsstat \ # +# export variables that are used in Makefile.btfgen as well. +export OUTPUT BPFTOOL ARCH BTFHUB_ARCHIVE APPS + FSDIST_ALIASES = btrfsdist ext4dist nfsdist xfsdist FSSLOWER_ALIASES = btrfsslower ext4slower nfsslower xfsslower APP_ALIASES = $(FSDIST_ALIASES) $(FSSLOWER_ALIASES) @@ -71,6 +75,8 @@ COMMON_OBJ = \ $(OUTPUT)/errno_helpers.o \ $(OUTPUT)/map_helpers.o \ $(OUTPUT)/uprobe_helpers.o \ + $(OUTPUT)/btf_helpers.o \ + $(if $(ENABLE_MIN_CORE_BTFS),$(OUTPUT)/min_core_btf_tar.o) \ # .PHONY: all @@ -119,6 +125,16 @@ $(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(ARCH)/vmlinux.h | $(O -I$(ARCH)/ $(INCLUDES) -c $(filter %.c,$^) -o $@ && \ $(LLVM_STRIP) -g $@ +btfhub-archive: force + $(call msg,GIT,$@) + $(Q)[ -d "$(BTFHUB_ARCHIVE)" ] || git clone -q https://github.com/aquasecurity/btfhub-archive/ $(BTFHUB_ARCHIVE) + $(Q)cd $(BTFHUB_ARCHIVE) && git pull + +ifdef ENABLE_MIN_CORE_BTFS +$(OUTPUT)/min_core_btf_tar.o: $(patsubst %,$(OUTPUT)/%.bpf.o,$(APPS)) btfhub-archive | bpftool + $(Q)$(MAKE) -f Makefile.btfgen +endif + # Build libbpf.a $(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch]) | $(OUTPUT)/libbpf $(call msg,LIB,$@) @@ -141,6 +157,9 @@ install: $(APPS) $(APP_ALIASES) $(Q)$(INSTALL) $(APPS) $(DESTDIR)$(prefix)/bin $(Q)cp -a $(APP_ALIASES) $(DESTDIR)$(prefix)/bin +.PHONY: force +force: + # delete failed targets .DELETE_ON_ERROR: # keep intermediate (.skel.h, .bpf.o, etc) targets diff --git a/libbpf-tools/Makefile.btfgen b/libbpf-tools/Makefile.btfgen new file mode 100644 index 000000000000..bd58692edca5 --- /dev/null +++ b/libbpf-tools/Makefile.btfgen @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +SOURCE_BTF_FILES = $(shell find $(BTFHUB_ARCHIVE)/ -iregex ".*$(subst x86,x86_64,$(ARCH)).*" -type f -name '*.btf.tar.xz') +MIN_CORE_BTF_FILES = $(patsubst $(BTFHUB_ARCHIVE)/%.btf.tar.xz, $(OUTPUT)/min_core_btfs/%.btf, $(SOURCE_BTF_FILES)) +BPF_O_FILES = $(patsubst %,$(OUTPUT)/%.bpf.o,$(APPS)) + +.PHONY: all +all: $(OUTPUT)/min_core_btf_tar.o + +ifeq ($(V),1) +Q = +msg = +else +Q = @ +msg = @printf ' %-8s %s%s\n' "$(1)" "$(notdir $(2))" "$(if $(3), $(3))"; +MAKEFLAGS += --no-print-directory +endif + +$(BTFHUB_ARCHIVE)/%.btf: $(BTFHUB_ARCHIVE)/%.btf.tar.xz + $(call msg,UNTAR,$@) + $(Q)tar xvfJ $< -C "$(@D)" > /dev/null + $(Q)touch $@ + +$(MIN_CORE_BTF_FILES): $(BPF_O_FILES) + +# Create reduced version of BTF files to be embedded within the tools executables +$(OUTPUT)/min_core_btfs/%.btf: $(BTFHUB_ARCHIVE)/%.btf + $(call msg,BTFGEN,$@) + $(Q)mkdir -p "$(@D)" + $(Q)$(BPFTOOL) gen min_core_btf $< $@ $(OUTPUT)/*.bpf.o + +# Compress reduced BTF files and create an object file with its content +$(OUTPUT)/min_core_btf_tar.o: $(MIN_CORE_BTF_FILES) + $(call msg,TAR,$@) + $(Q)tar c --gz -f $(OUTPUT)/min_core_btfs.tar.gz -C $(OUTPUT)/min_core_btfs/ . + $(Q)cd $(OUTPUT) && ld -r -b binary min_core_btfs.tar.gz -o $@ + +# delete failed targets +.DELETE_ON_ERROR: +# keep intermediate (.skel.h, .bpf.o, etc) targets +.SECONDARY: diff --git a/libbpf-tools/btf_helpers.c b/libbpf-tools/btf_helpers.c new file mode 100644 index 000000000000..9e4228b1be30 --- /dev/null +++ b/libbpf-tools/btf_helpers.c @@ -0,0 +1,247 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ + +#include +#include +#include +#include +#include +#include + +#include "trace_helpers.h" +#include "btf_helpers.h" + +extern unsigned char _binary_min_core_btfs_tar_gz_start[] __attribute__((weak)); +extern unsigned char _binary_min_core_btfs_tar_gz_end[] __attribute__((weak)); + +#define FIELD_LEN 65 +#define ID_FMT "ID=%64s" +#define VERSION_FMT "VERSION_ID=\"%64s" + +struct os_info { + char id[FIELD_LEN]; + char version[FIELD_LEN]; + char arch[FIELD_LEN]; + char kernel_release[FIELD_LEN]; +}; + +static struct os_info * get_os_info() +{ + struct os_info *info = NULL; + struct utsname u; + size_t len = 0; + ssize_t read; + char *line = NULL; + FILE *f; + + if (uname(&u) == -1) + return NULL; + + f = fopen("/etc/os-release", "r"); + if (!f) + return NULL; + + info = calloc(1, sizeof(*info)); + if (!info) + goto out; + + strncpy(info->kernel_release, u.release, FIELD_LEN); + strncpy(info->arch, u.machine, FIELD_LEN); + + while ((read = getline(&line, &len, f)) != -1) { + if (sscanf(line, ID_FMT, info->id) == 1) + continue; + + if (sscanf(line, VERSION_FMT, info->version) == 1) { + /* remove '"' suffix */ + info->version[strlen(info->version) - 1] = 0; + continue; + } + } + +out: + free(line); + fclose(f); + + return info; +} + +#define INITIAL_BUF_SIZE (1024 * 1024 * 4) /* 4MB */ + +/* adapted from https://zlib.net/zlib_how.html */ +static int +inflate_gz(unsigned char *src, int src_size, unsigned char **dst, int *dst_size) +{ + size_t size = INITIAL_BUF_SIZE; + size_t next_size = size; + z_stream strm; + void *tmp; + int ret; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + + ret = inflateInit2(&strm, 16 + MAX_WBITS); + if (ret != Z_OK) + return -EINVAL; + + *dst = malloc(size); + if (!*dst) + return -ENOMEM; + + strm.next_in = src; + strm.avail_in = src_size; + + /* run inflate() on input until it returns Z_STREAM_END */ + do { + strm.next_out = *dst + strm.total_out; + strm.avail_out = next_size; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) + goto out_err; + /* we need more space */ + if (strm.avail_out == 0) { + next_size = size; + size *= 2; + tmp = realloc(*dst, size); + if (!tmp) { + ret = -ENOMEM; + goto out_err; + } + *dst = tmp; + } + } while (ret != Z_STREAM_END); + + *dst_size = strm.total_out; + + /* clean up and return */ + ret = inflateEnd(&strm); + if (ret != Z_OK) { + ret = -EINVAL; + goto out_err; + } + return 0; + +out_err: + free(*dst); + *dst = NULL; + return ret; +} + +/* tar header from https://github.com/tklauser/libtar/blob/v1.2.20/lib/libtar.h#L39-L60 */ +struct tar_header { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char chksum[8]; + char typeflag; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + char padding[12]; +}; + +static char *tar_file_start(struct tar_header *tar, const char *name, int *length) +{ + while (tar->name[0]) { + sscanf(tar->size, "%o", length); + if (!strcmp(tar->name, name)) + return (char *)(tar + 1); + tar += 1 + (*length + 511)/512; + } + return NULL; +} + +int ensure_core_btf(struct bpf_object_open_opts *opts) +{ + char name_fmt[] = "./%s/%s/%s/%s.btf"; + char btf_path[] = "/tmp/bcc-libbpf-tools.btf.XXXXXX"; + struct os_info *info = NULL; + unsigned char *dst_buf = NULL; + char *file_start; + int dst_size = 0; + char name[100]; + FILE *dst = NULL; + int ret; + + /* do nothing if the system provides BTF */ + if (vmlinux_btf_exists()) + return 0; + + /* compiled without min core btfs */ + if (!_binary_min_core_btfs_tar_gz_start) + return -EOPNOTSUPP; + + info = get_os_info(); + if (!info) + return -errno; + + ret = mkstemp(btf_path); + if (ret < 0) { + ret = -errno; + goto out; + } + + dst = fdopen(ret, "wb"); + if (!dst) { + ret = -errno; + goto out; + } + + ret = snprintf(name, sizeof(name), name_fmt, info->id, info->version, + info->arch, info->kernel_release); + if (ret < 0 || ret == sizeof(name)) { + ret = -EINVAL; + goto out; + } + + ret = inflate_gz(_binary_min_core_btfs_tar_gz_start, + _binary_min_core_btfs_tar_gz_end - _binary_min_core_btfs_tar_gz_start, + &dst_buf, &dst_size); + if (ret < 0) + goto out; + + ret = 0; + file_start = tar_file_start((struct tar_header *)dst_buf, name, &dst_size); + if (!file_start) { + ret = -EINVAL; + goto out; + } + + if (fwrite(file_start, 1, dst_size, dst) != dst_size) { + ret = -ferror(dst); + goto out; + } + + opts->btf_custom_path = strdup(btf_path); + if (!opts->btf_custom_path) + ret = -ENOMEM; + +out: + free(info); + fclose(dst); + free(dst_buf); + + return ret; +} + +void cleanup_core_btf(struct bpf_object_open_opts *opts) { + if (!opts) + return; + + if (!opts->btf_custom_path) + return; + + unlink(opts->btf_custom_path); + free((void *)opts->btf_custom_path); +} diff --git a/libbpf-tools/btf_helpers.h b/libbpf-tools/btf_helpers.h new file mode 100644 index 000000000000..5d43e5dad921 --- /dev/null +++ b/libbpf-tools/btf_helpers.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ + +#ifndef __BTF_HELPERS_H +#define __BTF_HELPERS_H + +#include + +int ensure_core_btf(struct bpf_object_open_opts *opts); +void cleanup_core_btf(struct bpf_object_open_opts *opts); + +#endif /* __BTF_HELPERS_H */