Skip to content

Commit

Permalink
libbpf-tools: Add experimental integration with BTFGen
Browse files Browse the repository at this point in the history
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/[email protected]/

Signed-off-by: Mauricio Vásquez <[email protected]>
  • Loading branch information
mauriciovasquezbernal committed Apr 6, 2022
1 parent 6230695 commit 37d703d
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 0 deletions.
1 change: 1 addition & 0 deletions libbpf-tools/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/.output
/btfhub-archive
/bashreadline
/bindsnoop
/biolatency
Expand Down
19 changes: 19 additions & 0 deletions libbpf-tools/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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,$@)
Expand All @@ -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
Expand Down
40 changes: 40 additions & 0 deletions libbpf-tools/Makefile.btfgen
Original file line number Diff line number Diff line change
@@ -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:
247 changes: 247 additions & 0 deletions libbpf-tools/btf_helpers.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>
#include <zlib.h>

#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);
}
11 changes: 11 additions & 0 deletions libbpf-tools/btf_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */

#ifndef __BTF_HELPERS_H
#define __BTF_HELPERS_H

#include <bpf/libbpf.h>

int ensure_core_btf(struct bpf_object_open_opts *opts);
void cleanup_core_btf(struct bpf_object_open_opts *opts);

#endif /* __BTF_HELPERS_H */

0 comments on commit 37d703d

Please sign in to comment.