diff --git a/include/uapi/linux/btf.h b/include/uapi/linux/btf.h index 266d4ffa6c077..64dd681274f4d 100644 --- a/include/uapi/linux/btf.h +++ b/include/uapi/linux/btf.h @@ -8,6 +8,14 @@ #define BTF_MAGIC 0xeB9F #define BTF_VERSION 1 +/* kind layout section consists of a struct btf_kind_layout for each known + * kind at BTF encoding time. + */ +struct btf_kind_layout { + __u8 info_sz; /* size of singular element after btf_type */ + __u8 elem_sz; /* size of each of btf_vlen(t) elements */ +}; + struct btf_header { __u16 magic; __u8 version; @@ -19,6 +27,8 @@ struct btf_header { __u32 type_len; /* length of type section */ __u32 str_off; /* offset of string section */ __u32 str_len; /* length of string section */ + __u32 kind_layout_off;/* offset of kind layout section */ + __u32 kind_layout_len;/* length of kind layout section */ }; /* Max # of type identifier */ diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 0de8fc8a0e0b3..d6221d3e7893f 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -268,6 +268,7 @@ struct btf { struct btf_id_dtor_kfunc_tab *dtor_kfunc_tab; struct btf_struct_metas *struct_meta_tab; struct btf_struct_ops_tab *struct_ops_tab; + struct btf_kind_layout *kind_layout; /* split BTF support */ struct btf *base_btf; @@ -5215,23 +5216,36 @@ static s32 btf_check_meta(struct btf_verifier_env *env, return -EINVAL; } - if (BTF_INFO_KIND(t->info) > BTF_KIND_MAX || - BTF_INFO_KIND(t->info) == BTF_KIND_UNKN) { + if (!btf_name_offset_valid(env->btf, t->name_off)) { + btf_verifier_log(env, "[%u] Invalid name_offset:%u", + env->log_type_id, t->name_off); + return -EINVAL; + } + + if (BTF_INFO_KIND(t->info) == BTF_KIND_UNKN) { btf_verifier_log(env, "[%u] Invalid kind:%u", env->log_type_id, BTF_INFO_KIND(t->info)); return -EINVAL; } - if (!btf_name_offset_valid(env->btf, t->name_off)) { - btf_verifier_log(env, "[%u] Invalid name_offset:%u", - env->log_type_id, t->name_off); + if (BTF_INFO_KIND(t->info) > BTF_KIND_MAX && env->btf->kind_layout && + (BTF_INFO_KIND(t->info) * sizeof(struct btf_kind_layout)) < + env->btf->hdr.kind_layout_len) { + btf_verifier_log(env, "[%u] unknown but required kind %u", + env->log_type_id, + BTF_INFO_KIND(t->info)); return -EINVAL; + } else { + if (BTF_INFO_KIND(t->info) > BTF_KIND_MAX) { + btf_verifier_log(env, "[%u] Invalid kind:%u", + env->log_type_id, BTF_INFO_KIND(t->info)); + return -EINVAL; + } + var_meta_size = btf_type_ops(t)->check_meta(env, t, meta_left); + if (var_meta_size < 0) + return var_meta_size; } - var_meta_size = btf_type_ops(t)->check_meta(env, t, meta_left); - if (var_meta_size < 0) - return var_meta_size; - meta_left -= var_meta_size; return saved_meta_left - meta_left; @@ -5405,7 +5419,8 @@ static int btf_parse_str_sec(struct btf_verifier_env *env) start = btf->nohdr_data + hdr->str_off; end = start + hdr->str_len; - if (end != btf->data + btf->data_size) { + if (hdr->hdr_len < sizeof(struct btf_header) && + end != btf->data + btf->data_size) { btf_verifier_log(env, "String section is not at the end"); return -EINVAL; } @@ -5426,9 +5441,41 @@ static int btf_parse_str_sec(struct btf_verifier_env *env) return 0; } +static int btf_parse_kind_layout_sec(struct btf_verifier_env *env) +{ + const struct btf_header *hdr = &env->btf->hdr; + struct btf *btf = env->btf; + void *start, *end; + + if (hdr->hdr_len < sizeof(struct btf_header) || + hdr->kind_layout_len == 0) + return 0; + + /* Kind layout section must align to 4 bytes */ + if (hdr->kind_layout_off & (sizeof(u32) - 1)) { + btf_verifier_log(env, "Unaligned kind_layout_off"); + return -EINVAL; + } + start = btf->nohdr_data + hdr->kind_layout_off; + end = start + hdr->kind_layout_len; + + if (hdr->kind_layout_len < sizeof(struct btf_kind_layout)) { + btf_verifier_log(env, "Kind layout section is too small"); + return -EINVAL; + } + if (end > btf->data + btf->data_size) { + btf_verifier_log(env, "Kind layout section is too big"); + return -EINVAL; + } + btf->kind_layout = start; + + return 0; +} + static const size_t btf_sec_info_offset[] = { offsetof(struct btf_header, type_off), offsetof(struct btf_header, str_off), + offsetof(struct btf_header, kind_layout_off), }; static int btf_sec_info_cmp(const void *a, const void *b) @@ -5443,44 +5490,49 @@ static int btf_check_sec_info(struct btf_verifier_env *env, u32 btf_data_size) { struct btf_sec_info secs[ARRAY_SIZE(btf_sec_info_offset)]; - u32 total, expected_total, i; + u32 nr_secs = ARRAY_SIZE(btf_sec_info_offset); + u32 total, expected_total, gap, i; const struct btf_header *hdr; const struct btf *btf; btf = env->btf; hdr = &btf->hdr; + if (hdr->hdr_len < sizeof(struct btf_header)) + nr_secs--; + /* Populate the secs from hdr */ - for (i = 0; i < ARRAY_SIZE(btf_sec_info_offset); i++) + for (i = 0; i < nr_secs; i++) secs[i] = *(struct btf_sec_info *)((void *)hdr + btf_sec_info_offset[i]); - sort(secs, ARRAY_SIZE(btf_sec_info_offset), + sort(secs, nr_secs, sizeof(struct btf_sec_info), btf_sec_info_cmp, NULL); /* Check for gaps and overlap among sections */ total = 0; expected_total = btf_data_size - hdr->hdr_len; - for (i = 0; i < ARRAY_SIZE(btf_sec_info_offset); i++) { + for (i = 0; i < nr_secs; i++) { if (expected_total < secs[i].off) { btf_verifier_log(env, "Invalid section offset"); return -EINVAL; } - if (total < secs[i].off) { - /* gap */ - btf_verifier_log(env, "Unsupported section found"); - return -EINVAL; - } if (total > secs[i].off) { btf_verifier_log(env, "Section overlap found"); return -EINVAL; } + gap = secs[i].off - total; + if (gap >= 4) { + /* gap larger than alignment gap */ + btf_verifier_log(env, "Unsupported section found"); + return -EINVAL; + } if (expected_total - total < secs[i].len) { btf_verifier_log(env, "Total section length too long"); return -EINVAL; } - total += secs[i].len; + total += secs[i].len + gap; } /* There is data other than hdr and known sections */ @@ -5816,6 +5868,10 @@ static struct btf *btf_parse(const union bpf_attr *attr, bpfptr_t uattr, u32 uat if (err) goto errout; + err = btf_parse_kind_layout_sec(env); + if (err) + goto errout; + err = btf_parse_type_sec(env); if (err) goto errout; diff --git a/scripts/Makefile.btf b/scripts/Makefile.btf index db76335dd9176..c20f9bbcabeb3 100644 --- a/scripts/Makefile.btf +++ b/scripts/Makefile.btf @@ -25,6 +25,8 @@ pahole-flags-$(call test-ge, $(pahole-ver), 126) = -j$(JOBS) --btf_features=enc pahole-flags-$(call test-ge, $(pahole-ver), 130) += --btf_features=attributes +pahole-flags-$(call test-ge, $(pahole-ver), 131) += --btf_features=kind_layout + ifneq ($(KBUILD_EXTMOD),) module-pahole-flags-$(call test-ge, $(pahole-ver), 128) += --btf_features=distilled_base endif diff --git a/tools/bpf/bpftool/Documentation/bpftool-btf.rst b/tools/bpf/bpftool/Documentation/bpftool-btf.rst index d47dddc2b4ee3..1c11b5647ab7b 100644 --- a/tools/bpf/bpftool/Documentation/bpftool-btf.rst +++ b/tools/bpf/bpftool/Documentation/bpftool-btf.rst @@ -28,7 +28,7 @@ BTF COMMANDS | **bpftool** **btf help** | | *BTF_SRC* := { **id** *BTF_ID* | **prog** *PROG* | **map** *MAP* [{**key** | **value** | **kv** | **all**}] | **file** *FILE* } -| *FORMAT* := { **raw** | **c** [**unsorted**] } +| *FORMAT* := { **raw** | **c** [**unsorted**] | **meta** } | *MAP* := { **id** *MAP_ID* | **pinned** *FILE* } | *PROG* := { **id** *PROG_ID* | **pinned** *FILE* | **tag** *PROG_TAG* | **name** *PROG_NAME* } @@ -65,7 +65,8 @@ bpftool btf dump *BTF_SRC* [format *FORMAT*] [root_id *ROOT_ID*] **format** option can be used to override default (raw) output format. Raw (**raw**) or C-syntax (**c**) output formats are supported. With C-style formatting, the output is sorted by default. Use the **unsorted** option - to avoid sorting the output. + to avoid sorting the output. BTF metadata can be displayed with the + **meta** option. **root_id** option can be used to filter a dump to a single type and all its dependent types. It cannot be used with any other types of filtering @@ -267,3 +268,26 @@ All the standard ways to specify map or program are supported: [104859] FUNC 'smbalert_work' type_id=9695 linkage=static [104860] FUNC 'smbus_alert' type_id=71367 linkage=static [104861] FUNC 'smbus_do_alert' type_id=84827 linkage=static + +Display BTF metadata from file vmlinux + +**# bpftool btf dump file vmlinux format meta** + +:: + + size 5161076 + magic 0xeb9f + version 1 + flags 0x1 + hdr_len 40 + type_len 3036368 + type_off 0 + str_len 2124588 + str_off 3036368 + kind_layout_len 80 + kind_layout_off 5160956 + kind 0 UNKNOWN info_sz 0 elem_sz 0 + kind 1 INT info_sz 0 elem_sz 0 + kind 2 PTR info_sz 0 elem_sz 0 + kind 3 ARRAY info_sz 0 elem_sz 0 + kind 4 STRUCT info_sz 0 elem_sz 0 diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool index 53bcfeb1a76e6..a331172cf8de3 100644 --- a/tools/bpf/bpftool/bash-completion/bpftool +++ b/tools/bpf/bpftool/bash-completion/bpftool @@ -950,7 +950,7 @@ _bpftool() return 0 ;; format) - COMPREPLY=( $( compgen -W "c raw" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "c raw meta" -- "$cur" ) ) ;; root_id) return 0; diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c index 946612029deee..fdb9b36fe106d 100644 --- a/tools/bpf/bpftool/btf.c +++ b/tools/bpf/bpftool/btf.c @@ -835,6 +835,90 @@ static int dump_btf_c(const struct btf *btf, return err; } +static int dump_btf_meta(const struct btf *btf) +{ + const struct btf_header *hdr; + const struct btf_kind_layout *k; + __u8 i, nr_kinds = 0; + const void *data; + __u32 data_sz; + + data = btf__raw_data(btf, &data_sz); + if (!data) + return -ENOMEM; + hdr = data; + if (json_output) { + jsonw_start_object(json_wtr); /* metadata object */ + jsonw_uint_field(json_wtr, "size", data_sz); + jsonw_name(json_wtr, "header"); + jsonw_start_object(json_wtr); /* header object */ + jsonw_uint_field(json_wtr, "magic", hdr->magic); + jsonw_uint_field(json_wtr, "version", hdr->version); + jsonw_uint_field(json_wtr, "flags", hdr->flags); + jsonw_uint_field(json_wtr, "hdr_len", hdr->hdr_len); + jsonw_uint_field(json_wtr, "type_len", hdr->type_len); + jsonw_uint_field(json_wtr, "type_off", hdr->type_off); + jsonw_uint_field(json_wtr, "str_len", hdr->str_len); + jsonw_uint_field(json_wtr, "str_off", hdr->str_off); + } else { + printf("size %-10u\n", data_sz); + printf("magic 0x%-10x\nversion %-10d\nflags 0x%-10x\nhdr_len %-10u\n", + hdr->magic, hdr->version, hdr->flags, hdr->hdr_len); + printf("type_len %-10u\ntype_off %-10u\n", hdr->type_len, hdr->type_off); + printf("str_len %-10u\nstr_off %-10u\n", hdr->str_len, hdr->str_off); + } + + if (hdr->hdr_len < sizeof(struct btf_header)) { + if (json_output) { + jsonw_end_object(json_wtr); /* end header object */ + jsonw_end_object(json_wtr); /* end metadata object */ + } + return 0; + } + + data_sz -= hdr->hdr_len; + + if (hdr->kind_layout_len > 0 && hdr->kind_layout_off > 0) { + if (hdr->kind_layout_off + hdr->kind_layout_len <= data_sz) { + k = (void *)hdr + hdr->hdr_len + hdr->kind_layout_off; + nr_kinds = hdr->kind_layout_len / sizeof(*k); + } + } + if (json_output) { + jsonw_uint_field(json_wtr, "kind_layout_len", hdr->kind_layout_len); + jsonw_uint_field(json_wtr, "kind_layout_offset", hdr->kind_layout_off); + jsonw_end_object(json_wtr); /* end header object */ + + if (nr_kinds > 0) { + jsonw_name(json_wtr, "kind_layouts"); + jsonw_start_array(json_wtr); + for (i = 0; i < nr_kinds; i++) { + jsonw_start_object(json_wtr); + jsonw_uint_field(json_wtr, "kind", i); + if (i < NR_BTF_KINDS) + jsonw_string_field(json_wtr, "name", btf_kind_str[i]); + else + jsonw_null_field(json_wtr, "name"); + jsonw_uint_field(json_wtr, "info_sz", k[i].info_sz); + jsonw_uint_field(json_wtr, "elem_sz", k[i].elem_sz); + jsonw_end_object(json_wtr); + } + jsonw_end_array(json_wtr); + } + jsonw_end_object(json_wtr); /* end metadata object */ + } else { + printf("kind_layout_len %-10u\nkind_layout_off %-10u\n", + hdr->kind_layout_len, hdr->kind_layout_off); + for (i = 0; i < nr_kinds; i++) { + printf("kind %-4d %-10s info_sz %-4d elem_sz %-4d\n", + i, i < NR_BTF_KINDS ? btf_kind_str[i] : "?", + k[i].info_sz, k[i].elem_sz); + } + } + + return 0; +} + static const char sysfs_vmlinux[] = "/sys/kernel/btf/vmlinux"; static struct btf *get_vmlinux_btf_from_sysfs(void) @@ -880,7 +964,7 @@ static bool btf_is_kernel_module(__u32 btf_id) static int do_dump(int argc, char **argv) { - bool dump_c = false, sort_dump_c = true; + bool dump_c = false, sort_dump_c = true, dump_meta = false; struct btf *btf = NULL, *base = NULL; __u32 root_type_ids[MAX_ROOT_IDS]; bool have_id_filtering; @@ -990,10 +1074,12 @@ static int do_dump(int argc, char **argv) } if (strcmp(*argv, "c") == 0) { dump_c = true; + } else if (is_prefix(*argv, "meta")) { + dump_meta = true; } else if (strcmp(*argv, "raw") == 0) { dump_c = false; } else { - p_err("unrecognized format specifier: '%s', possible values: raw, c", + p_err("unrecognized format specifier: '%s', possible values: raw, c, meta", *argv); err = -EINVAL; goto done; @@ -1072,6 +1158,8 @@ static int do_dump(int argc, char **argv) goto done; } err = dump_btf_c(btf, root_type_ids, root_type_cnt, sort_dump_c); + } else if (dump_meta) { + err = dump_btf_meta(btf); } else { err = dump_btf_raw(btf, root_type_ids, root_type_cnt); } @@ -1446,7 +1534,7 @@ static int do_help(int argc, char **argv) " %1$s %2$s help\n" "\n" " BTF_SRC := { id BTF_ID | prog PROG | map MAP [{key | value | kv | all}] | file FILE }\n" - " FORMAT := { raw | c [unsorted] }\n" + " FORMAT := { raw | c [unsorted] | meta }\n" " " HELP_SPEC_MAP "\n" " " HELP_SPEC_PROGRAM "\n" " " HELP_SPEC_OPTIONS " |\n" diff --git a/tools/include/uapi/linux/btf.h b/tools/include/uapi/linux/btf.h index 266d4ffa6c077..64dd681274f4d 100644 --- a/tools/include/uapi/linux/btf.h +++ b/tools/include/uapi/linux/btf.h @@ -8,6 +8,14 @@ #define BTF_MAGIC 0xeB9F #define BTF_VERSION 1 +/* kind layout section consists of a struct btf_kind_layout for each known + * kind at BTF encoding time. + */ +struct btf_kind_layout { + __u8 info_sz; /* size of singular element after btf_type */ + __u8 elem_sz; /* size of each of btf_vlen(t) elements */ +}; + struct btf_header { __u16 magic; __u8 version; @@ -19,6 +27,8 @@ struct btf_header { __u32 type_len; /* length of type section */ __u32 str_off; /* offset of string section */ __u32 str_len; /* length of string section */ + __u32 kind_layout_off;/* offset of kind layout section */ + __u32 kind_layout_len;/* length of kind layout section */ }; /* Max # of type identifier */ diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c index b136572e889ab..440cd451c3403 100644 --- a/tools/lib/bpf/btf.c +++ b/tools/lib/bpf/btf.c @@ -29,6 +29,35 @@ static struct btf_type btf_void; +/* Describe how kinds are laid out; some have a singular element following the "struct btf_type", + * some have BTF_INFO_VLEN(t->info) elements. Specify sizes for both. Flags are currently unused. + * Kind layout can be optionally added to the BTF representation in a dedicated section to + * facilitate parsing. New kinds must be added here. + */ +struct btf_kind_layout kind_layouts[NR_BTF_KINDS] = { +/* singular element size vlen element(s) size */ +{ 0, 0 }, /* _UNKN */ +{ sizeof(__u32), 0 }, /* _INT */ +{ 0, 0 }, /* _PTR */ +{ sizeof(struct btf_array), 0 }, /* _ARRAY */ +{ 0, sizeof(struct btf_member) }, /* _STRUCT */ +{ 0, sizeof(struct btf_member) }, /* _UNION */ +{ 0, sizeof(struct btf_enum) }, /* _ENUM */ +{ 0, 0 }, /* _FWD */ +{ 0, 0 }, /* _TYPEDEF */ +{ 0, 0 }, /* _VOLATILE */ +{ 0, 0 }, /* _CONST */ +{ 0, 0 }, /* _RESTRICT */ +{ 0, 0 }, /* _FUNC */ +{ 0, sizeof(struct btf_param) }, /* _FUNC_PROTO */ +{ sizeof(struct btf_var), 0 }, /* _VAR */ +{ 0, sizeof(struct btf_var_secinfo) }, /* _DATASEC */ +{ 0, 0 }, /* _FLOAT */ +{ sizeof(struct btf_decl_tag), 0 }, /* _DECL_TAG */ +{ 0, 0 }, /* _TYPE_TAG */ +{ 0, sizeof(struct btf_enum64) }, /* _ENUM64 */ +}; + struct btf { /* raw BTF data in native endianness */ void *raw_data; @@ -40,42 +69,53 @@ struct btf { /* * When BTF is loaded from an ELF or raw memory it is stored - * in a contiguous memory block. The hdr, type_data, and, strs_data + * in a contiguous memory block. The type_data, and, strs_data * point inside that memory region to their respective parts of BTF * representation: * - * +--------------------------------+ - * | Header | Types | Strings | - * +--------------------------------+ - * ^ ^ ^ - * | | | - * hdr | | - * types_data-+ | - * strs_data------------+ + * +--------------------------------+---------------------+ + * | Header | Types | Strings |Optional kind layout | + * +--------------------------------+---------------------+ + * ^ ^ ^ ^ + * | | | | + * raw_data | | | + * types_data-+ | | + * strs_data------------+ | + * kind_layout----------------------+ + * + * A separate struct btf_header is allocated for btf->hdr, + * and header information is copied into it. This allows us + * to handle header data for various header formats; the original, + * the extended header with kind layout, etc. * * If BTF data is later modified, e.g., due to types added or * removed, BTF deduplication performed, etc, this contiguous - * representation is broken up into three independently allocated - * memory regions to be able to modify them independently. + * representation is broken up into four independent memory + * regions. + * * raw_data is nulled out at that point, but can be later allocated * and cached again if user calls btf__raw_data(), at which point - * raw_data will contain a contiguous copy of header, types, and - * strings: + * raw_data will contain a contiguous copy of header, types, strings + * and optionally kind_layout. kind_layout optionally points to a + * kind_layout array - this allows us to encode information about + * the kinds known at encoding time. If kind_layout is NULL no + * kind information is encoded. * - * +----------+ +---------+ +-----------+ - * | Header | | Types | | Strings | - * +----------+ +---------+ +-----------+ - * ^ ^ ^ - * | | | - * hdr | | - * types_data----+ | - * strset__data(strs_set)-----+ + * +----------+ +---------+ +-----------+ +-----------+ + * | Header | | Types | | Strings | |kind_layout| + * +----------+ +---------+ +-----------+ +-----------+ + * ^ ^ ^ ^ + * | | | | + * hdr | | | + * types_data----+ | | + * strset__data(strs_set)-----+ | + * kind_layout--------------------------------+ * - * +----------+---------+-----------+ - * | Header | Types | Strings | - * raw_data----->+----------+---------+-----------+ + * +----------+---------+-----------+---------------------+ + * | Header | Types | Strings | Optional kind layout| + * raw_data----->+----------+---------+-----------+---------------------+ */ - struct btf_header *hdr; + struct btf_header *hdr; /* separately-allocated header data */ void *types_data; size_t types_data_cap; /* used size stored in hdr->type_len */ @@ -123,6 +163,13 @@ struct btf { /* whether raw_data is a (read-only) mmap */ bool raw_data_is_mmap; + /* is BTF modifiable? i.e. is it split into separate sections as described above? */ + bool modifiable; + /* Points either at raw kind layout data in parsed BTF (if present), or + * at an allocated kind layout array when BTF is modifiable. + */ + void *kind_layout; + /* BTF object FD, if loaded into kernel */ int fd; @@ -214,7 +261,7 @@ static int btf_add_type_idx_entry(struct btf *btf, __u32 type_off) return 0; } -static void btf_bswap_hdr(struct btf_header *h) +static void btf_bswap_hdr(struct btf_header *h, __u32 hdr_len) { h->magic = bswap_16(h->magic); h->hdr_len = bswap_32(h->hdr_len); @@ -222,54 +269,89 @@ static void btf_bswap_hdr(struct btf_header *h) h->type_len = bswap_32(h->type_len); h->str_off = bswap_32(h->str_off); h->str_len = bswap_32(h->str_len); + /* May be operating on raw data with hdr_len that does not include below fields */ + if (hdr_len >= sizeof(struct btf_header)) { + h->kind_layout_off = bswap_32(h->kind_layout_off); + h->kind_layout_len = bswap_32(h->kind_layout_len); + } } static int btf_parse_hdr(struct btf *btf) { - struct btf_header *hdr = btf->hdr; + struct btf_header *hdr = btf->raw_data; + __u32 hdr_len = hdr->hdr_len; __u32 meta_left; - if (btf->raw_size < sizeof(struct btf_header)) { + if (btf->raw_size < offsetofend(struct btf_header, str_len)) { pr_debug("BTF header not found\n"); return -EINVAL; } if (hdr->magic == bswap_16(BTF_MAGIC)) { btf->swapped_endian = true; - if (bswap_32(hdr->hdr_len) != sizeof(struct btf_header)) { + hdr_len = bswap_32(hdr->hdr_len); + if (hdr_len < offsetofend(struct btf_header, str_len)) { pr_warn("Can't load BTF with non-native endianness due to unsupported header length %u\n", - bswap_32(hdr->hdr_len)); + hdr_len); return -ENOTSUP; } - btf_bswap_hdr(hdr); } else if (hdr->magic != BTF_MAGIC) { pr_debug("Invalid BTF magic: %x\n", hdr->magic); return -EINVAL; } - if (btf->raw_size < hdr->hdr_len) { + if (btf->raw_size < hdr_len) { pr_debug("BTF header len %u larger than data size %u\n", - hdr->hdr_len, btf->raw_size); + hdr_len, btf->raw_size); return -EINVAL; } - meta_left = btf->raw_size - hdr->hdr_len; - if (meta_left < (long long)hdr->str_off + hdr->str_len) { + /* At this point, we have basic header information, so allocate btf->hdr */ + btf->hdr = calloc(1, sizeof(struct btf_header)); + if (!btf->hdr) { + pr_debug("BTF header allocation failed\n"); + return -ENOMEM; + } + if (btf->swapped_endian) + btf_bswap_hdr(hdr, hdr_len); + memcpy(btf->hdr, hdr, hdr_len < sizeof(struct btf_header) ? hdr_len : + sizeof(struct btf_header)); + + meta_left = btf->raw_size - hdr_len; + if (meta_left < (long long)btf->hdr->str_off + btf->hdr->str_len) { pr_debug("Invalid BTF total size: %u\n", btf->raw_size); return -EINVAL; } - if ((long long)hdr->type_off + hdr->type_len > hdr->str_off) { + if ((long long)btf->hdr->type_off + btf->hdr->type_len > btf->hdr->str_off) { pr_debug("Invalid BTF data sections layout: type data at %u + %u, strings data at %u + %u\n", - hdr->type_off, hdr->type_len, hdr->str_off, hdr->str_len); + btf->hdr->type_off, btf->hdr->type_len, btf->hdr->str_off, + btf->hdr->str_len); return -EINVAL; } - if (hdr->type_off % 4) { + if (btf->hdr->type_off % 4) { pr_debug("BTF type section is not aligned to 4 bytes\n"); return -EINVAL; } + if (btf->hdr->kind_layout_len == 0) + return 0; + + if (btf->hdr->kind_layout_off % 4) { + pr_debug("BTF kind_layout section is not aligned to 4 bytes\n"); + return -EINVAL; + } + if (btf->hdr->kind_layout_off < btf->hdr->str_off + btf->hdr->str_len) { + pr_debug("Invalid BTF data sections layout: strings data at %u + %u, kind layout data at %u + %u\n", + btf->hdr->str_off, btf->hdr->str_len, + btf->hdr->kind_layout_off, btf->hdr->kind_layout_len); + return -EINVAL; + } + if (btf->hdr->kind_layout_off + btf->hdr->kind_layout_len > meta_left) { + pr_debug("Invalid BTF total size: %u\n", btf->raw_size); + return -EINVAL; + } return 0; } @@ -292,7 +374,43 @@ static int btf_parse_str_sec(struct btf *btf) return 0; } -static int btf_type_size(const struct btf_type *t) +static int btf_parse_kind_layout_sec(struct btf *btf) +{ + const struct btf_header *hdr = btf->hdr; + + if (!hdr->kind_layout_len) + return 0; + + if (hdr->kind_layout_len % sizeof(struct btf_kind_layout) != 0) { + pr_debug("Invalid BTF kind layout section\n"); + return -EINVAL; + } + btf->kind_layout = btf->raw_data + btf->hdr->hdr_len + btf->hdr->kind_layout_off; + + return 0; +} + +/* for unknown kinds, consult kind layout. */ +static int btf_type_size_unknown(const struct btf *btf, const struct btf_type *t) +{ + int size = sizeof(struct btf_type); + struct btf_kind_layout *k = NULL; + __u16 vlen = btf_vlen(t); + __u8 kind = btf_kind(t); + + if (!btf->kind_layout || kind >= (btf->hdr->kind_layout_len / sizeof(*k))) { + pr_debug("Unsupported BTF_KIND: %u\n", btf_kind(t)); + return -EINVAL; + } + k = &((struct btf_kind_layout *)btf->kind_layout)[kind]; + + size += k->info_sz; + size += vlen * k->elem_sz; + + return size; +} + +static int btf_type_size(const struct btf *btf, const struct btf_type *t) { const int base_size = sizeof(struct btf_type); __u16 vlen = btf_vlen(t); @@ -328,8 +446,7 @@ static int btf_type_size(const struct btf_type *t) case BTF_KIND_DECL_TAG: return base_size + sizeof(struct btf_decl_tag); default: - pr_debug("Unsupported BTF_KIND:%u\n", btf_kind(t)); - return -EINVAL; + return btf_type_size_unknown(btf, t); } } @@ -428,7 +545,7 @@ static int btf_parse_type_sec(struct btf *btf) if (btf->swapped_endian) btf_bswap_type_base(next_type); - type_size = btf_type_size(next_type); + type_size = btf_type_size(btf, next_type); if (type_size < 0) return type_size; if (next_type + type_size > end_type) { @@ -589,8 +706,12 @@ static int btf_validate_type(const struct btf *btf, const struct btf_type *t, __ break; } default: - pr_warn("btf: type [%u]: unrecognized kind %u\n", id, kind); - return -EINVAL; + /* Kind may be represented in kind layout information. */ + if (btf_type_size_unknown(btf, t) < 0) { + pr_warn("btf: type [%u]: unrecognized kind %u\n", id, kind); + return -EINVAL; + } + break; } return 0; } @@ -951,7 +1072,8 @@ __s32 btf__find_by_name_kind(const struct btf *btf, const char *type_name, static bool btf_is_modifiable(const struct btf *btf) { - return (void *)btf->hdr != btf->raw_data; + /* BTF is modifiable if split into multiple sections */ + return btf->modifiable; } static void btf_free_raw_data(struct btf *btf) @@ -980,10 +1102,11 @@ void btf__free(struct btf *btf) * might still have a cached contiguous raw data present, * which will be unconditionally freed below. */ - free(btf->hdr); free(btf->types_data); strset__free(btf->strs_set); + free(btf->kind_layout); } + free(btf->hdr); btf_free_raw_data(btf); free(btf->raw_data_swapped); free(btf->type_offs); @@ -992,8 +1115,11 @@ void btf__free(struct btf *btf) free(btf); } -static struct btf *btf_new_empty(struct btf *base_btf) +static struct btf *btf_new_empty(struct btf_new_opts *opts) { + bool add_kind_layout = OPTS_GET(opts, add_kind_layout, false); + struct btf *base_btf = OPTS_GET(opts, base_btf, NULL); + struct btf_header *hdr; struct btf *btf; btf = calloc(1, sizeof(*btf)); @@ -1016,32 +1142,62 @@ static struct btf *btf_new_empty(struct btf *base_btf) /* +1 for empty string at offset 0 */ btf->raw_size = sizeof(struct btf_header) + (base_btf ? 0 : 1); + if (add_kind_layout) + btf->raw_size = roundup(btf->raw_size, 4) + sizeof(kind_layouts); btf->raw_data = calloc(1, btf->raw_size); if (!btf->raw_data) { free(btf); return ERR_PTR(-ENOMEM); } - btf->hdr = btf->raw_data; - btf->hdr->hdr_len = sizeof(struct btf_header); - btf->hdr->magic = BTF_MAGIC; - btf->hdr->version = BTF_VERSION; + hdr = btf->raw_data; + hdr->hdr_len = sizeof(struct btf_header); + hdr->magic = BTF_MAGIC; + hdr->version = BTF_VERSION; - btf->types_data = btf->raw_data + btf->hdr->hdr_len; - btf->strs_data = btf->raw_data + btf->hdr->hdr_len; - btf->hdr->str_len = base_btf ? 0 : 1; /* empty string at offset 0 */ + btf->types_data = btf->raw_data + hdr->hdr_len; + btf->strs_data = btf->raw_data + hdr->hdr_len; + hdr->str_len = base_btf ? 0 : 1; /* empty string at offset 0 */ + btf->hdr = calloc(1, sizeof(struct btf_header)); + if (!btf->hdr) { + free(btf->raw_data); + free(btf); + return ERR_PTR(-ENOMEM); + } + + if (add_kind_layout) { + hdr->kind_layout_len = sizeof(kind_layouts); + hdr->kind_layout_off = roundup(hdr->str_len, 4); + btf->kind_layout = btf->raw_data + hdr->hdr_len + hdr->kind_layout_off; + memcpy(btf->kind_layout, kind_layouts, sizeof(kind_layouts)); + } + memcpy(btf->hdr, hdr, sizeof(*hdr)); return btf; } struct btf *btf__new_empty(void) { - return libbpf_ptr(btf_new_empty(NULL)); + LIBBPF_OPTS(btf_new_opts, opts); + + return libbpf_ptr(btf_new_empty(&opts)); } struct btf *btf__new_empty_split(struct btf *base_btf) { - return libbpf_ptr(btf_new_empty(base_btf)); + LIBBPF_OPTS(btf_new_opts, opts); + + opts.base_btf = base_btf; + + return libbpf_ptr(btf_new_empty(&opts)); +} + +struct btf *btf__new_empty_opts(struct btf_new_opts *opts) +{ + if (!OPTS_VALID(opts, btf_new_opts)) + return libbpf_err_ptr(-EINVAL); + + return libbpf_ptr(btf_new_empty(opts)); } static struct btf *btf_new(const void *data, __u32 size, struct btf *base_btf, bool is_mmap) @@ -1078,7 +1234,6 @@ static struct btf *btf_new(const void *data, __u32 size, struct btf *base_btf, b btf->raw_size = size; - btf->hdr = btf->raw_data; err = btf_parse_hdr(btf); if (err) goto done; @@ -1087,6 +1242,7 @@ static struct btf *btf_new(const void *data, __u32 size, struct btf *base_btf, b btf->types_data = btf->raw_data + btf->hdr->hdr_len + btf->hdr->type_off; err = btf_parse_str_sec(btf); + err = err ?: btf_parse_kind_layout_sec(btf); err = err ?: btf_parse_type_sec(btf); err = err ?: btf_sanity_check(btf); if (err) @@ -1550,6 +1706,11 @@ static void *btf_get_raw_data(const struct btf *btf, __u32 *size, bool swap_endi } data_sz = hdr->hdr_len + hdr->type_len + hdr->str_len; + if (btf->kind_layout) { + data_sz = roundup(data_sz, 4); + data_sz += hdr->kind_layout_len; + hdr->kind_layout_off = roundup(hdr->type_len + hdr->str_len, 4); + } data = calloc(1, data_sz); if (!data) return NULL; @@ -1557,7 +1718,7 @@ static void *btf_get_raw_data(const struct btf *btf, __u32 *size, bool swap_endi memcpy(p, hdr, hdr->hdr_len); if (swap_endian) - btf_bswap_hdr(p); + btf_bswap_hdr(p, hdr->hdr_len); p += hdr->hdr_len; memcpy(p, btf->types_data, hdr->type_len); @@ -1576,7 +1737,11 @@ static void *btf_get_raw_data(const struct btf *btf, __u32 *size, bool swap_endi p += hdr->type_len; memcpy(p, btf_strs_data(btf), hdr->str_len); - p += hdr->str_len; + + if (btf->kind_layout) { + p = data + hdr->hdr_len + hdr->kind_layout_off; + memcpy(p, btf->kind_layout, hdr->kind_layout_len); + } *size = data_sz; return data; @@ -1717,13 +1882,13 @@ static void btf_invalidate_raw_data(struct btf *btf) } } -/* Ensure BTF is ready to be modified (by splitting into a three memory - * regions for header, types, and strings). Also invalidate cached - * raw_data, if any. +/* Ensure BTF is ready to be modified (by splitting into memory regions + * for types and strings, with kind layout section if needed (btf->hdr + * is already a separate region). Also invalidate cached raw_data, if any. */ static int btf_ensure_modifiable(struct btf *btf) { - void *hdr, *types; + void *types, *kind_layout = NULL; struct strset *set = NULL; int err = -ENOMEM; @@ -1733,15 +1898,20 @@ static int btf_ensure_modifiable(struct btf *btf) return 0; } - /* split raw data into three memory regions */ - hdr = malloc(btf->hdr->hdr_len); + /* split raw data into memory regions; btf->hdr is done already. */ types = malloc(btf->hdr->type_len); - if (!hdr || !types) + if (!types) goto err_out; - - memcpy(hdr, btf->hdr, btf->hdr->hdr_len); memcpy(types, btf->types_data, btf->hdr->type_len); + if (btf->hdr->kind_layout_len) { + kind_layout = malloc(btf->hdr->kind_layout_len); + if (!kind_layout) + goto err_out; + memcpy(kind_layout, btf->raw_data + btf->hdr->hdr_len + btf->hdr->kind_layout_off, + btf->hdr->kind_layout_len); + } + /* build lookup index for all strings */ set = strset__new(BTF_MAX_STR_OFFSET, btf->strs_data, btf->hdr->str_len); if (IS_ERR(set)) { @@ -1750,11 +1920,12 @@ static int btf_ensure_modifiable(struct btf *btf) } /* only when everything was successful, update internal state */ - btf->hdr = hdr; btf->types_data = types; btf->types_data_cap = btf->hdr->type_len; btf->strs_data = NULL; btf->strs_set = set; + if (kind_layout) + btf->kind_layout = kind_layout; /* if BTF was created from scratch, all strings are guaranteed to be * unique and deduplicated */ @@ -1766,12 +1937,14 @@ static int btf_ensure_modifiable(struct btf *btf) /* invalidate raw_data representation */ btf_invalidate_raw_data(btf); + btf->modifiable = true; + return 0; err_out: strset__free(set); - free(hdr); free(types); + free(kind_layout); return err; } @@ -1898,7 +2071,7 @@ static int btf_add_type(struct btf_pipe *p, const struct btf_type *src_type) __u32 *str_off; int sz, err; - sz = btf_type_size(src_type); + sz = btf_type_size(p->src, src_type); if (sz < 0) return libbpf_err(sz); @@ -1980,7 +2153,7 @@ int btf__add_btf(struct btf *btf, const struct btf *src_btf) struct btf_field_iter it; __u32 *type_id, *str_off; - sz = btf_type_size(t); + sz = btf_type_size(src_btf, t); if (sz < 0) { /* unlikely, has to be corrupted src_btf */ err = sz; @@ -5321,7 +5494,7 @@ static int btf_dedup_compact_types(struct btf_dedup *d) continue; t = btf__type_by_id(d->btf, id); - len = btf_type_size(t); + len = btf_type_size(d->btf, t); if (len < 0) return len; diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h index cc01494d62107..dcc1668349371 100644 --- a/tools/lib/bpf/btf.h +++ b/tools/lib/bpf/btf.h @@ -109,6 +109,26 @@ LIBBPF_API struct btf *btf__new_empty(void); */ LIBBPF_API struct btf *btf__new_empty_split(struct btf *base_btf); +struct btf_new_opts { + size_t sz; + struct btf *base_btf; /* optional base BTF */ + bool add_kind_layout; /* add BTF kind layout information */ + size_t:0; +}; +#define btf_new_opts__last_field add_kind_layout + +/** + * @brief **btf__new_empty_opts()** creates an unpopulated BTF object with + * optional *base_btf* and BTF kind layout description if *add_kind_layout* + * is set + * @return new BTF object instance which has to be eventually freed with + * **btf__free()** + * + * On error, NULL is returned and the thread-local `errno` variable is + * set to the error code. + */ +LIBBPF_API struct btf *btf__new_empty_opts(struct btf_new_opts *opts); + /** * @brief **btf__distill_base()** creates new versions of the split BTF * *src_btf* and its base BTF. The new base BTF will only contain the types diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map index 84fb90a016c9c..0fb9a1f70e721 100644 --- a/tools/lib/bpf/libbpf.map +++ b/tools/lib/bpf/libbpf.map @@ -453,4 +453,5 @@ LIBBPF_1.7.0 { bpf_map__exclusive_program; bpf_prog_assoc_struct_ops; bpf_program__assoc_struct_ops; + btf__new_empty_opts; } LIBBPF_1.6.0; diff --git a/tools/testing/selftests/bpf/prog_tests/btf_kind.c b/tools/testing/selftests/bpf/prog_tests/btf_kind.c new file mode 100644 index 0000000000000..322f207873c2f --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/btf_kind.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025, Oracle and/or its affiliates. */ + +#include +#include +#include + +/* verify kind encoding exists for each kind */ +static void test_btf_kind_encoding(void) +{ + LIBBPF_OPTS(btf_new_opts, opts); + const struct btf_header *hdr; + const void *raw_btf; + struct btf *btf; + __u32 raw_size; + + opts.add_kind_layout = true; + btf = btf__new_empty_opts(&opts); + if (!ASSERT_OK_PTR(btf, "btf_new")) + return; + + raw_btf = btf__raw_data(btf, &raw_size); + if (!ASSERT_OK_PTR(raw_btf, "btf__raw_data")) + return; + + hdr = raw_btf; + + ASSERT_EQ(hdr->kind_layout_off % 4, 0, "kind_layout aligned"); + ASSERT_EQ(hdr->kind_layout_len, sizeof(struct btf_kind_layout) * NR_BTF_KINDS, + "kind_layout_len"); + btf__free(btf); +} + +static void write_raw_btf(const char *btf_path, void *raw_btf, size_t raw_size) +{ + int fd = open(btf_path, O_WRONLY | O_CREAT); + + write(fd, raw_btf, raw_size); + close(fd); +} + +/* fabricate an unrecognized kind at BTF_KIND_MAX + 1, and after adding + * the appropriate struct/typedefs to the BTF such that it recognizes + * this kind, ensure that parsing of BTF containing the unrecognized kind + * can succeed. + */ +void test_btf_kind_decoding(void) +{ + __s32 int_id, unrec_id, id, id2; + LIBBPF_OPTS(btf_new_opts, opts); + struct btf *btf, *new_btf; + struct btf_kind_layout *k; + struct btf_header *hdr; + const void *raw_btf; + struct btf_type *t; + char btf_path[64]; + void *new_raw_btf; + __u32 raw_size; + + opts.add_kind_layout = true; + btf = btf__new_empty_opts(&opts); + if (!ASSERT_OK_PTR(btf, "btf_new")) + return; + + int_id = btf__add_int(btf, "test_char", 1, BTF_INT_CHAR); + if (!ASSERT_GT(int_id, 0, "add_int_id")) + return; + + /* now create our type with unrecognized kind by adding a typedef kind + * we will overwrite it with our unrecognized kind value. + */ + unrec_id = btf__add_typedef(btf, "unrec_kind", int_id); + if (!ASSERT_GT(unrec_id, 0, "add_unrec_id")) + return; + + /* add an id after it that we will look up to verify we can parse + * beyond unrecognized kinds. + */ + id = btf__add_typedef(btf, "test_lookup", int_id); + if (!ASSERT_GT(id, 0, "add_test_lookup_id")) + return; + id2 = btf__add_typedef(btf, "test_lookup2", int_id); + if (!ASSERT_GT(id2, 0, "add_test_lookup_id2")) + return; + + raw_btf = (void *)btf__raw_data(btf, &raw_size); + if (!ASSERT_OK_PTR(raw_btf, "btf__raw_data")) + return; + + new_raw_btf = calloc(1, raw_size + sizeof(*k)); + if (!ASSERT_OK_PTR(new_raw_btf, "calloc_raw_btf")) + return; + memcpy(new_raw_btf, raw_btf, raw_size); + + /* add new layout description */ + hdr = new_raw_btf; + hdr->kind_layout_len += sizeof(*k); + k = new_raw_btf + hdr->hdr_len + hdr->kind_layout_off; + k[NR_BTF_KINDS].info_sz = 0; + k[NR_BTF_KINDS].elem_sz = 0; + + /* now modify our typedef added above to be an unrecognized kind. */ + t = (void *)hdr + hdr->hdr_len + hdr->type_off + sizeof(struct btf_type) + + sizeof(__u32); + t->info = (NR_BTF_KINDS << 24); + + /* now write our BTF to a raw file, ready for parsing. */ + snprintf(btf_path, sizeof(btf_path), "/tmp/btf_kind.%d", getpid()); + + write_raw_btf(btf_path, new_raw_btf, raw_size + sizeof(*k)); + + /* verify parsing succeeds, and that we can read type info past + * the unrecognized kind. + */ + new_btf = btf__parse_raw(btf_path); + if (ASSERT_OK_PTR(new_btf, "btf__parse_raw")) { + ASSERT_EQ(btf__find_by_name_kind(new_btf, "test_lookup", + BTF_KIND_TYPEDEF), id, + "verify_id_lookup"); + ASSERT_EQ(btf__find_by_name_kind(new_btf, "test_lookup2", + BTF_KIND_TYPEDEF), id2, + "verify_id2_lookup"); + } + btf__free(new_btf); + + /* next, change info_sz to equal sizeof(struct btf_type); this means the + * "test_lookup" kind will be reinterpreted as a singular info element + * following the unrecognized kind. + */ + k[NR_BTF_KINDS].info_sz = sizeof(struct btf_type); + write_raw_btf(btf_path, new_raw_btf, raw_size + sizeof(*k)); + + new_btf = btf__parse_raw(btf_path); + if (ASSERT_OK_PTR(new_btf, "btf__parse_raw")) { + ASSERT_EQ(btf__find_by_name_kind(new_btf, "test_lookup", + BTF_KIND_TYPEDEF), -ENOENT, + "verify_id_not_found"); + /* id of "test_lookup2" will be id2 -1 as we have removed one type */ + ASSERT_EQ(btf__find_by_name_kind(new_btf, "test_lookup2", + BTF_KIND_TYPEDEF), id2 - 1, + "verify_id_lookup2"); + + } + btf__free(new_btf); + + /* next, change elem_sz to equal sizeof(struct btf_type)/2 and set + * vlen associated with unrecognized type to 2; this allows us to verify + * vlen-specified BTF can still be parsed. + */ + k[NR_BTF_KINDS].info_sz = 0; + k[NR_BTF_KINDS].elem_sz = sizeof(struct btf_type)/2; + t->info |= 2; + write_raw_btf(btf_path, new_raw_btf, raw_size + sizeof(*k)); + + new_btf = btf__parse_raw(btf_path); + if (ASSERT_OK_PTR(new_btf, "btf__parse_raw")) { + ASSERT_EQ(btf__find_by_name_kind(new_btf, "test_lookup", + BTF_KIND_TYPEDEF), -ENOENT, + "verify_id_not_found"); + /* id of "test_lookup2" will be id2 -1 as we have removed one type */ + ASSERT_EQ(btf__find_by_name_kind(new_btf, "test_lookup2", + BTF_KIND_TYPEDEF), id2 - 1, + "verify_id_lookup2"); + + } + btf__free(new_btf); + free(new_raw_btf); + unlink(btf_path); + btf__free(btf); +} + +void test_btf_kind(void) +{ + if (test__start_subtest("btf_kind_encoding")) + test_btf_kind_encoding(); + if (test__start_subtest("btf_kind_decoding")) + test_btf_kind_decoding(); +}