From: Yang Bo <bo@xxxxxxxx> implement function. refactor code. Signed-off-by: Yang Bo <bo@xxxxxxxx> --- tools/lib/bpf/bpf.c | 808 +++++++++++++++++++++++++++++++++++++++ tools/lib/bpf/bpf.h | 9 + tools/lib/bpf/libbpf.map | 2 + 3 files changed, 819 insertions(+) diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c index 128ac723c4ea..3b6ff6508712 100644 --- a/tools/lib/bpf/bpf.c +++ b/tools/lib/bpf/bpf.c @@ -26,10 +26,12 @@ #include <memory.h> #include <unistd.h> #include <asm/unistd.h> +#include <asm/byteorder.h> #include <errno.h> #include <linux/bpf.h> #include <linux/filter.h> #include <linux/kernel.h> +#include <linux/bitops.h> #include <limits.h> #include <sys/resource.h> #include "bpf.h" @@ -1190,3 +1192,809 @@ int bpf_prog_bind_map(int prog_fd, int map_fd, ret = sys_bpf(BPF_PROG_BIND_MAP, &attr, attr_sz); return libbpf_err_errno(ret); } + + +#define BITS_PER_BYTE_MASK (BITS_PER_BYTE - 1) +#define BITS_PER_BYTE_MASKED(bits) ((bits) & BITS_PER_BYTE_MASK) +#define BITS_ROUNDDOWN_BYTES(bits) ((bits) >> 3) +#define BITS_ROUNDUP_BYTES(bits) \ + (BITS_ROUNDDOWN_BYTES(bits) + !!BITS_PER_BYTE_MASKED(bits)) + +static struct btf *bpf_map_get_btf(const struct bpf_map_info *info) +{ + struct btf *btf, *btf_vmlinux = NULL; + + if (info->btf_vmlinux_value_type_id) { + btf_vmlinux = libbpf_find_kernel_btf(); + if (!btf_vmlinux) { + pr_debug("cannot find kernel btf"); + return NULL; + } + + return btf_vmlinux; + } + + if (info->btf_value_type_id) { + btf = btf__load_from_kernel_by_id(info->btf_id); + if (!btf) { + pr_debug("cannot load btf"); + return NULL; + } + + return btf; + } + + return NULL; +} + +static void bpf_map_free_btf(struct btf * btf) +{ + btf__free(btf); +} + +static struct member *btf_handle_bitfield(__u32 nr_bits, __u8 bit_offset, void * data, bool update, const char *value); +static struct member *search_key(struct btf *btf, __u32 id, __u8 bit_offset, void *data, char *keyword, bool update, const char *value, int index); + +static struct member *btf_handle_int_bits(__u32 int_type, __u8 bit_offset, void *data, bool update, const char *value) { + int nr_bits = BTF_INT_BITS(int_type); + int total_bits_offset; + + /* bits_offset is at most 7. + * BTF_INT_OFFSET() cannot exceed 128 bits. + */ + total_bits_offset = bit_offset + BTF_INT_OFFSET(int_type); + data += BITS_ROUNDDOWN_BYTES(total_bits_offset); + bit_offset = BITS_PER_BYTE_MASKED(total_bits_offset); + return btf_handle_bitfield(nr_bits, bit_offset, data, update, value); +} + +static struct member *btf_handle_int(struct btf *btf, __u32 id, __u8 bit_offset, void *data, char *keyword, bool update, const char *value, int index) { + const struct btf_type *t = btf__type_by_id(btf, id); + long long v; + char *end; + bool vv = false, vv1 = false; + + printf("int\n"); + + if (index >= 0) { + pr_debug("index on primitive type, only on array"); + errno = EINVAL; + return NULL; + } + + if (keyword == NULL) { + __u32 *int_type; + __u32 nr_bits; + int encoding; + size_t size; + struct member *member; + + int_type = (__u32 *)(t + 1); + nr_bits = BTF_INT_BITS(*int_type); + + // don't support bits for now + if (bit_offset || BTF_INT_OFFSET(*int_type) || BITS_PER_BYTE_MASKED(nr_bits)) { + return btf_handle_int_bits(*int_type, bit_offset, data, update, value); + } + + encoding = BTF_INT_ENCODING(*int_type); + size = BITS_ROUNDUP_BYTES(nr_bits); + + member = malloc(sizeof(struct member)); + if (!member) { + pr_debug("can not alloc member for int"); + errno = ENOMEM; + return NULL; + } + + member->data = malloc(size); + if (!member->data) { + free(member); + pr_debug("cannot alloc data field"); + errno = ENOMEM; + return NULL; + } + + member->type = BTF_KIND_INT; + member->size = nr_bits; + + switch (encoding) { + case 0: + case BTF_INT_SIGNED: + if (nr_bits == 64 || nr_bits == 32 || nr_bits == 16 || nr_bits == 8) { + size = nr_bits / 8; + memcpy(member->data, data, size); + } else { + //handle bits + free(member->data); + free(member); + return btf_handle_int_bits(*int_type, bit_offset, data, update, value); + } + + // update for non-bits int + if (update) { + errno = 0; + v = strtoll(value, &end, 0); + if (errno || value == end) { + pr_debug("can not convert to long long"); + goto free_data; + } + + if (*end != '\0') { + pr_debug("value contains non-digits"); + } + +#if defined(__BYTE_ORDER) ? __BYTE_ORDER == __BIG_ENDIAN : defined(__BIG_ENDIAN) + printf("big endian?\n"); + memcpy(data, (void *)&v + 8 - size, size); +#else + memcpy(data, &v, size); +#endif + } + + return member; + + case BTF_INT_CHAR: + memcpy(member->data, data, size); + if (update) { + if (strlen(value) == 1) { + *(char *)data = value[0]; + } else { + pr_debug("invalid char"); + errno = EINVAL; + goto free_data; + } + } + return member; + + case BTF_INT_BOOL: + memcpy(member->data, data, size); + if (update) { + vv = strcasecmp(value, "yes") == 0 || strcasecmp(value, "y") == 0 || strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0; + vv1 = strcasecmp(value, "no") == 0 || strcasecmp(value, "n") == 0 || strcasecmp(value, "false") == 0 || strcasecmp(value, "0") == 0; + if (!vv && !vv1) { + pr_debug("invalid bool"); + errno = EINVAL; + goto free_data; + } + + *(bool *)data = vv; + } + return member; + + default: + pr_debug("unknown encoding"); + errno = EINVAL; + goto free_data; + } + + return member; + + free_data: + free(member->data); + free(member); + return NULL; + } + + pr_debug("primitive type found, still remain keyword"); + errno = EINVAL; + return NULL; +} + +static void btf_int128_shift(__u64 *print_num, __u16 left_shift_bits, + __u16 right_shift_bits) +{ + __u64 upper_num, lower_num; + +#ifdef __BIG_ENDIAN_BITFIELD + upper_num = print_num[0]; + lower_num = print_num[1]; +#else + upper_num = print_num[1]; + lower_num = print_num[0]; +#endif + + /* shake out un-needed bits by shift/or operations */ + if (left_shift_bits > 0) { + if (left_shift_bits >= 64) { + upper_num = lower_num << (left_shift_bits - 64); + lower_num = 0; + } else { + upper_num = (upper_num << left_shift_bits) | + (lower_num >> (64 - left_shift_bits)); + lower_num = lower_num << left_shift_bits; + } + } + + if (right_shift_bits > 0) { + if (right_shift_bits >= 64) { + lower_num = upper_num >> (right_shift_bits - 64); + upper_num = 0; + } else { + lower_num = (lower_num >> right_shift_bits) | + (upper_num << (64 - right_shift_bits)); + upper_num = upper_num >> right_shift_bits; + } + } + +#ifdef __BIG_ENDIAN_BITFIELD + print_num[0] = upper_num; + print_num[1] = lower_num; +#else + print_num[0] = lower_num; + print_num[1] = upper_num; +#endif +} + +static void update_bitfield_value(void *data, __u64 *mask, __u64 *val, int bytes_to_copy) { + __u64 new[2] = {}; + + memcpy(new, data, bytes_to_copy); + + printf("mask: %llx, %llx\n", mask[0], mask[1]); + printf("val: %llx, %llx\n", val[0], val[1]); + + new[0] &= mask[0]; + new[1] &= mask[1]; + printf("old: %llx, %llx\n", new[0], new[1]); + + new[0] |= val[0]; + new[1] |= val[1]; + printf("new: %llx, %llx\n", new[0], new[1]); + + memcpy(data, new, bytes_to_copy); +} + +static struct member *btf_handle_bitfield(__u32 nr_bits, __u8 bit_offset, void *data, bool update, const char *value) { + int left_shift_bits, right_shift_bits; + int left_shift_bits2; + __u64 print_num[2] = {}; + __u64 mask[2] = {0xffffffffffffffff, 0xffffffffffffffff}; + int bytes_to_copy; + int bits_to_copy; + struct member *ret; + + bits_to_copy = bit_offset + nr_bits; + bytes_to_copy = BITS_ROUNDUP_BYTES(bits_to_copy); + + memcpy(print_num, data, bytes_to_copy); + +#if defined(__BIG_ENDIAN_BITFIELD) + left_shift_bits = bit_offset; + left_shift_bits2 = 128 - bits_to_copy; +#elif defined(__LITTLE_ENDIAN_BITFIELD) + left_shift_bits = 128 - bits_to_copy; + left_shift_bits2 = bit_offset; +#else +#error neither big nor little endian +#endif + right_shift_bits = 128 - nr_bits; + + printf("left: %d, right: %d, left2: %d\n", left_shift_bits, right_shift_bits, left_shift_bits2); + + btf_int128_shift(print_num, left_shift_bits, right_shift_bits); + + btf_int128_shift(mask, left_shift_bits, right_shift_bits); + printf("revert mask: %llx, %llx\n", mask[0], mask[1]); + btf_int128_shift(mask, left_shift_bits2, 0); + printf("revert mask2: %llx, %llx\n", mask[0], mask[1]); + mask[0] = ~mask[0]; + mask[1] = ~mask[1]; + + ret = malloc(sizeof(struct member)); + if (!ret) { + pr_debug("no memory!"); + errno = ENOMEM; + return NULL; + } + + ret->data = malloc(bytes_to_copy); + if (!ret->data) { + pr_debug("no memory!"); + errno = ENOMEM; + return NULL; + } + + memcpy(ret->data, print_num, bytes_to_copy); + ret->type = BTF_KIND_INT; + ret->size = nr_bits; // size in bits + + if (update) { + long long val; + char *end; + __u64 tmp[2] = {}; + + errno = 0; + val = strtoll(value, &end, 0); + if (errno || value == end) { + pr_debug("cannot convert string to int!"); + free(ret->data); + free(ret); + return NULL; + } + + if (*end != '\0') { + pr_debug("value has non-digits!"); + } + printf("value in bitfield: %lld\n", val); +#ifdef __BIG_ENDIAN_BITFIELD + tmp[1] = val; +#else + tmp[0] = val; +#endif + //btf_int128_shift(tmp, left_shift_bits, right_shift_bits); + btf_int128_shift(tmp, left_shift_bits2, 0); + tmp[0] &= ~mask[0]; + tmp[1] &= ~mask[1]; + + update_bitfield_value(data, mask, tmp, bytes_to_copy); + } + + return ret; +} + +static struct member *btf_handle_struct_and_union(struct btf *btf, __u32 id, __u8 pre_bit_offset, void *data, char *keyword, bool update, const char *value, char *token, int index) { + const struct btf_type *t = btf__type_by_id(btf, id); + int kind_flag, vlen, i; + struct btf_member *m; + const char *name; + void *data_off; + + printf("struct\n"); + + kind_flag = BTF_INFO_KFLAG(t->info); + vlen = BTF_INFO_VLEN(t->info); + m = (struct btf_member *)(t + 1); + + for (i = 0; i < vlen; i++) { + __u32 bitfield_size = 0; + __u8 offset = 0; + __u32 bit_offset = m[i].offset; + + if (kind_flag) { + bitfield_size = BTF_MEMBER_BITFIELD_SIZE(bit_offset); + bit_offset = BTF_MEMBER_BIT_OFFSET(bit_offset); + } + name = btf__name_by_offset(btf, m[i].name_off); + data_off = data + BITS_ROUNDDOWN_BYTES(bit_offset); + offset = BITS_PER_BYTE_MASKED(bit_offset); + + printf("name: %s, kind_flag: %d, bitfield_size: %d, m[i].offset: %x\n", name, kind_flag, bitfield_size, m[i].offset); + + if (strcmp(name, token) == 0) { + if (bitfield_size) { + // already here, calculate and copy bits out + if (index >= 0) { + pr_debug("index on primitive type, only on array"); + errno = EINVAL; + return NULL; + } + + if (keyword) { + pr_debug("primitive type found, still remain keyword"); + errno = EINVAL; + return NULL; + } + return btf_handle_bitfield(bitfield_size, offset, data_off, update, value); + } else { + return search_key(btf, m[i].type, offset, data_off, keyword, update, value, index); + } + } + } + + return NULL; +} + +static struct member *btf_handle_array(struct btf *btf, __u32 id, __u8 bit_offset, void *data, char *keyword, bool update, const char *value, int index) { + // FIXME: implement array + const struct btf_type *t; + struct btf_array *arr; + long long elem_size; + + printf("array\n"); + if (index < 0) { + pr_debug("index array with negative index!"); + errno = EINVAL; + return NULL; + } + + t = btf__type_by_id(btf, id); + arr = (struct btf_array *)(t + 1); + elem_size = btf__resolve_size(btf, arr->type); + if (elem_size < 0) { + pr_debug("array element size less than 0!"); + errno = EINVAL; + return NULL; + } + + if (index >= arr->nelems) { + pr_debug("index out of range, max: %d", arr->nelems - 1); + errno = EINVAL; + return NULL; + } + + return search_key(btf, arr->type, bit_offset, data + index * elem_size, keyword, update, value, -1); +} + +static struct member *btf_handle_enum(struct btf *btf, __u32 id, __u8 bit_offset, void *data, char *keyword, bool update, const char *value, int index) { + const struct btf_type *t = btf__type_by_id(btf, id); + int kind = btf_kind(t); + + // FIXME: byteorder consideration. little endian is ok, but + // probably not work for big endian. + printf("enum\n"); + if (index >= 0) { + errno = EINVAL; + return NULL; + } + + if (keyword == NULL) { + struct member *member = malloc(sizeof(struct member)); + + if (!member) { + pr_debug("cannot allocate memory"); + errno = ENOMEM; + return NULL; + } + + member->data = malloc(t->size); + if (!member->data) { + free(member); + pr_debug("cannot allocate dat memory"); + errno = ENOMEM; + return NULL; + } + + memcpy(member->data, data, t->size); + if (kind == BTF_KIND_ENUM) { + member->type = BTF_KIND_ENUM; + } else { + member->type = BTF_KIND_ENUM64; + } + member->size = t->size * 8; // to bits + + if (update) { + char *end; + long long v; + + errno = 0; + v = strtoll(value, &end, 0); + + if (errno || value == end) { + pr_debug("can not convert to number"); + free(member->data); + free(member); + return NULL; + } + + if (*end != '\0') { + pr_debug("value contains non-digits"); + } + +#if defined(__BYTE_ORDER) ? __BYTE_ORDER == __BIG_ENDIAN : defined(__BIG_ENDIAN) + memcpy(data, (void *)&v + 8 - t->size, t->size); +#else + memcpy(data, &v, t->size); +#endif + } + + return member; + } + + pr_debug("primitive type found, still have keyword"); + errno = EINVAL; + return NULL; +} + +static struct member *btf_handle_modifier(struct btf *btf, __u32 id, __u8 bit_offset, void *data, char *keyword, bool update, const char *value, int index) { + + int actual_type_id; + printf("modifier\n"); + + actual_type_id = btf__resolve_type(btf, id); + if (actual_type_id < 0) { + return NULL; + } + + return search_key(btf, actual_type_id, bit_offset, data, keyword, update, value, index); +} + +static struct member *btf_handle_var(struct btf *btf, __u32 id, __u8 bit_offset, void *data, char *keyword, bool update, const char *value, char *token, int index) { + const struct btf_type *t = btf__type_by_id(btf, id); + const char *name = btf__name_by_offset(btf, t->name_off); + + // FIXME: for array, var/struct/union is in the format var[index], need + // to handle it here. + printf("var\n"); + printf("name: %s, key: %s\n", name, token); + + if (strcmp(name, token) == 0) { + // found the name, continue search + return search_key(btf, t->type, bit_offset, data, keyword, update, value, index); + } + + return NULL; +} + +static struct member *btf_handle_datasec(struct btf *btf, __u32 id, __u8 bit_offset, void *data, char *keyword, bool update, const char *value, int index) { + const struct btf_type *t = btf__type_by_id(btf, id); + int vlen, i; + const struct btf_var_secinfo *vsi; + struct member *ret; + + printf("datasec\n"); + + vlen = BTF_INFO_VLEN(t->info); + vsi = (const struct btf_var_secinfo *)(t + 1); + + for (i = 0; i < vlen; i++) { + ret = search_key(btf, vsi[i].type, 0, data + vsi[i].offset, keyword, update, value, index); + + if (ret) { + return ret; + } + } + + pr_debug("key not found"); + errno = EINVAL; + + return NULL; + +} + +static int count = 0; + +static struct member *search_key(struct btf *btf, __u32 id, __u8 bit_offset, void *data, char *keyword, bool update, const char *value, int index) +{ + char *token = NULL; + char *old, *end; + const struct btf_type *t = btf__type_by_id(btf, id); + int kind; + char *dup = NULL; + char *orig_dup = NULL; + struct member *ret; + + kind = BTF_INFO_KIND(t->info); + + printf("iteration: %d, key: %s\n", count, keyword); + + if (kind == BTF_KIND_VAR || kind == BTF_KIND_STRUCT || kind == BTF_KIND_UNION) { + if (keyword) { + dup = strdup(keyword); + if (!dup) { + pr_debug("no memory!"); + return NULL; + } + + orig_dup = dup; + } + + token = strsep(&dup, "."); + if (token == NULL) { + pr_debug("null token"); + goto fail; + } + + old = token; + token = strsep(&old, "["); + if (old != NULL) { + // have array presentaion, "number]" is the remaining + errno = 0; + index = strtol(old, &end, 0); + if (errno != 0) { + pr_debug("strtol error!"); + printf("convert error!\n"); + goto fail; + } + + errno = EINVAL; + if (old == end) { + pr_debug("no digits for index!"); + goto fail; + } + + // validate representation, remaining must be ']' + if (strlen(end) != 1 || *end != ']') { + pr_debug("invalid array representation!"); + goto fail; + } + + if (index < 0) { + pr_debug("invalid index!"); + goto fail; + } + } + } + + printf("iteration: %d, key: %s, token: %s\n", count++, keyword, token ? : "(null)"); + + switch (kind) { + case BTF_KIND_INT: + ret = btf_handle_int(btf, id, bit_offset, data, keyword, update, value, index); + goto success; + + case BTF_KIND_STRUCT: + case BTF_KIND_UNION: + ret = btf_handle_struct_and_union(btf, id, bit_offset, data, dup, update, value, token, index); + goto success; + + case BTF_KIND_ARRAY: + ret = btf_handle_array(btf, id, bit_offset, data, keyword, update, value, index); + goto success; + + case BTF_KIND_ENUM: + case BTF_KIND_ENUM64: + ret = btf_handle_enum(btf, id, bit_offset, data, keyword, update, value, index); + goto success; + + case BTF_KIND_PTR: + pr_debug("pointer, don't known what to do with it"); + goto fail; + + case BTF_KIND_UNKN: + case BTF_KIND_FWD: + pr_debug("unknown type"); + goto fail; + + case BTF_KIND_TYPEDEF: + case BTF_KIND_VOLATILE: + case BTF_KIND_CONST: + case BTF_KIND_RESTRICT: + // modifier, find actual type id + ret = btf_handle_modifier(btf, id, bit_offset, data, keyword, update, value, index); + goto success; + + case BTF_KIND_VAR: + ret = btf_handle_var(btf, id, bit_offset, data, dup, update, value, token, index); + goto success; + + case BTF_KIND_DATASEC: + ret = btf_handle_datasec(btf, id, bit_offset, data, keyword, update, value, index); + goto success; + + default: + goto fail; + } + +success: + if (orig_dup) { + free(orig_dup); + } + return ret; + +fail: + if (orig_dup) { + free(orig_dup); + } + return NULL; +} + +static struct member *bpf_global_query_and_update_key(__u32 id, const char *identifier, bool update, const char *data) +{ + int fd, err; + struct bpf_map_info info = {}; + __u32 len = sizeof(info); + struct btf *btf; + void *key, *value; + __u32 value_id; + const struct btf_type *t; + __u32 kind; + char *keyword, *origin; + struct member *member = NULL; + + fd = bpf_map_get_fd_by_id(id); + if (fd < 0) { + pr_debug("get map by id (%u): %s\n", id, strerror(errno)); + return NULL; + } + + err = bpf_map_get_info_by_fd(fd, &info, &len); + if (err) { + pr_debug("get map info by fd(%d): %s\n", fd, strerror(errno)); + return NULL; + } + + if (!info.btf_id) { + pr_debug("no btf associated with this map"); + errno = ENOTSUP; + return NULL; + } + + if (info.type != BPF_MAP_TYPE_ARRAY) { + pr_debug("global variables must be in array map"); + errno = ENOTSUP; + return NULL; + } + + // lookup the key + btf = bpf_map_get_btf(&info); + if (!btf) { + pr_debug("cannot get btf: %s\n", strerror(errno)); + return NULL; + } + + key = malloc(info.key_size); + value = malloc(info.value_size); + + if (!key || !value) { + pr_debug("no memory"); + errno = ENOMEM; + goto out_free_btf; + } + + memset(key, 0, info.key_size); + memset(value, 0, info.value_size); + + if (bpf_map_lookup_elem(fd, key, value)) { + pr_debug("cannot find element 0"); + errno = EINVAL; + goto out_free_kv; + } + + // found value, parse btf + value_id = info.btf_vmlinux_value_type_id ? : + info.btf_value_type_id; + + t = btf__type_by_id(btf, value_id); + + // must be datasec + kind = BTF_INFO_KIND(t->info); + if (kind != BTF_KIND_DATASEC) { + pr_debug("not datasec"); + errno = EINVAL; + goto out_free_kv; + } + + keyword = strdup(identifier); + origin = keyword; + + member = search_key(btf, value_id, 0, value, keyword, update, data, -1); + + if (update) { + err = bpf_map_update_elem(fd, key, value, 0); + if (err) { + pr_debug("update failed: %s", strerror(errno)); + free(member->data); + free(member); + goto out_free_keyword; + } + } + + free(origin); + free(key); + free(value); + bpf_map_free_btf(btf); + + return member; + +out_free_keyword: + free(origin); + +out_free_kv: + free(key); + free(value); + +out_free_btf: + bpf_map_free_btf(btf); + return NULL; +} + +struct member *bpf_global_query_key(__u32 id, const char *key) +{ + return bpf_global_query_and_update_key(id, key, false, NULL); +} + +int bpf_global_update_key(__u32 id, const char *key, const char *value) +{ + struct member *member; + int err =0; + member = bpf_global_query_and_update_key(id, key, true, value); + if (!member) { + err = -1; + } + + free(member->data); + free(member); + + return err; +} diff --git a/tools/lib/bpf/bpf.h b/tools/lib/bpf/bpf.h index a2c091389b18..ca47b9354b12 100644 --- a/tools/lib/bpf/bpf.h +++ b/tools/lib/bpf/bpf.h @@ -117,6 +117,12 @@ LIBBPF_API int bpf_prog_load(enum bpf_prog_type prog_type, /* Recommended log buffer size */ #define BPF_LOG_BUF_SIZE (UINT32_MAX >> 8) /* verifier maximum in kernels <= 5.1 */ +struct member { + void *data; + __u32 type; + size_t size; +}; + struct bpf_btf_load_opts { size_t sz; /* size of this struct for forward/backward compatibility */ @@ -152,6 +158,9 @@ LIBBPF_API int bpf_map_delete_elem_flags(int fd, const void *key, __u64 flags); LIBBPF_API int bpf_map_get_next_key(int fd, const void *key, void *next_key); LIBBPF_API int bpf_map_freeze(int fd); +LIBBPF_API struct member *bpf_global_query_key(__u32 id, const char *key); +LIBBPF_API int bpf_global_update_key(__u32 id, const char *key, const char *value); + struct bpf_map_batch_opts { size_t sz; /* size of this struct for forward/backward compatibility */ __u64 elem_flags; diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map index a5aa3a383d69..d056ab0e2ffb 100644 --- a/tools/lib/bpf/libbpf.map +++ b/tools/lib/bpf/libbpf.map @@ -390,4 +390,6 @@ LIBBPF_1.2.0 { bpf_link_get_info_by_fd; bpf_map_get_info_by_fd; bpf_prog_get_info_by_fd; + bpf_global_query_key; + bpf_global_update_key; } LIBBPF_1.1.0; -- 2.40.0