diff --git a/src/libs/btf_helpers.c b/src/libs/btf_helpers.c new file mode 100644 index 0000000000000000000000000000000000000000..9e4228b1be302b036fbb5f1a3ef4f266f3532127 --- /dev/null +++ b/src/libs/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); +}