Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'libbpf-bpftool-support-merging-split-btfs'

Josef Bacik says:

====================
libbpf/bpftool: support merging split BTFs

v1: https://lore.kernel.org/bpf/cover.1771605821.git.josef@toxicpanda.com/
v2: https://lore.kernel.org/bpf/cover.1771616227.git.josef@toxicpanda.com/
v3: https://lore.kernel.org/bpf/cover.1771622266.git.josef@toxicpanda.com/
v4: https://lore.kernel.org/bpf/cover.1771625484.git.josef@toxicpanda.com/
v5: https://lore.kernel.org/bpf/cover.1771950922.git.josef@toxicpanda.com/

v1->v2:
- Added a btf__dedup() call to btf__add_btf() to ensure that we don't have
duplicate types in the merged BTF.
v2->v3:
- AI review got confused about the UAF comment, so the comment was expanded to
clarify the UAF potential.
- Fixed potential clobbering of errno in the error path.
v3->v4:
- Fixed a potential silent corruption pointed out by the AI review bot.
v4->v5:
- Addressed Andrii's comments for 1/3.
- Addressed Alan and Quentin's comments for 2/3.
- Addressed Alan's comments for 3/3.
- Added my Signed-off-by for the third patch.
- Made sure to validate everything still worked.
v5->v6:
- Fixed the missed is_prefix comment.
- Fixed the removed warning about skipping vmlinux.

--- Original email ---

Hello,

I'm extending systing to do introspection on vfio devices, which requires having
the structs I need from the kernel available in userspace. Normally these are
loadable modules, but in the case of vfio there's multiple structs across
multiple modules. Normally you'd do the following to generate your vmlinux.h
with a module

bpftool btf dump file /sys/kernel/btf/<module> format c \
--base /sys/kernel/btf/vmlinux > vmlinux.h

but if you need multiple modules you have to hack together multiple dumps and
merge them together. This patch series adds support for merging multiple BTF
sources together, so you can do

bpftool btf dump file /sys/kernel/btf/<module1> \
file /sys/kernel/btf/<module2> format c \
--base /sys/kernel/btf/vmlinux > vmlinux.h

I tested this with my usecase and it works. Thanks,

Josef
====================

Link: https://patch.msgid.link/cover.1772657690.git.josef@toxicpanda.com
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>

+250 -26
+7 -4
tools/bpf/bpftool/Documentation/bpftool-btf.rst
··· 27 27 | **bpftool** **btf dump** *BTF_SRC* [**format** *FORMAT*] [**root_id** *ROOT_ID*] 28 28 | **bpftool** **btf help** 29 29 | 30 - | *BTF_SRC* := { **id** *BTF_ID* | **prog** *PROG* | **map** *MAP* [{**key** | **value** | **kv** | **all**}] | **file** *FILE* } 30 + | *BTF_SRC* := { **id** *BTF_ID* | **prog** *PROG* | **map** *MAP* [{**key** | **value** | **kv** | **all**}] | **file** *FILE* [**file** *FILE*]... } 31 31 | *FORMAT* := { **raw** | **c** [**unsorted**] } 32 32 | *MAP* := { **id** *MAP_ID* | **pinned** *FILE* } 33 33 | *PROG* := { **id** *PROG_ID* | **pinned** *FILE* | **tag** *PROG_TAG* | **name** *PROG_NAME* } ··· 58 58 When **prog** is provided, it's expected that program has associated BTF 59 59 object with BTF types. 60 60 61 - When specifying *FILE*, an ELF file is expected, containing .BTF section 62 - with well-defined BTF binary format data, typically produced by clang or 63 - pahole. 61 + When specifying *FILE*, an ELF file or a raw BTF file (e.g. from 62 + ``/sys/kernel/btf/``) is expected. Multiple **file** arguments may be 63 + given to merge BTF from several kernel modules into a single output. 64 + When sysfs paths are used, vmlinux BTF is loaded automatically as the 65 + base; if vmlinux itself appears in the file list it is skipped. 66 + A base BTF can also be specified explicitly with **-B**. 64 67 65 68 **format** option can be used to override default (raw) output format. Raw 66 69 (**raw**) or C-syntax (**c**) output formats are supported. With C-style
+5 -1
tools/bpf/bpftool/bash-completion/bpftool
··· 961 961 *) 962 962 # emit extra options 963 963 case ${words[3]} in 964 - id|file) 964 + id) 965 965 COMPREPLY=( $( compgen -W "root_id" -- "$cur" ) ) 966 + _bpftool_once_attr 'format' 967 + ;; 968 + file) 969 + COMPREPLY=( $( compgen -W "root_id file" -- "$cur" ) ) 966 970 _bpftool_once_attr 'format' 967 971 ;; 968 972 map|prog)
+109 -12
tools/bpf/bpftool/btf.c
··· 28 28 #define FASTCALL_DECL_TAG "bpf_fastcall" 29 29 30 30 #define MAX_ROOT_IDS 16 31 + #define MAX_BTF_FILES 64 31 32 32 33 static const char * const btf_kind_str[NR_BTF_KINDS] = { 33 34 [BTF_KIND_UNKN] = "UNKNOWN", ··· 879 878 return btf_info.kernel_btf && strncmp(btf_name, "vmlinux", sizeof(btf_name)) != 0; 880 879 } 881 880 881 + static struct btf *merge_btf_files(const char **files, int nr_files, 882 + struct btf *vmlinux_base) 883 + { 884 + struct btf *combined, *mod; 885 + int ret; 886 + 887 + combined = btf__new_empty_split(vmlinux_base); 888 + if (!combined) { 889 + p_err("failed to create combined BTF: %s", strerror(errno)); 890 + return NULL; 891 + } 892 + 893 + for (int j = 0; j < nr_files; j++) { 894 + mod = btf__parse_split(files[j], vmlinux_base); 895 + if (!mod) { 896 + p_err("failed to load BTF from %s: %s", files[j], strerror(errno)); 897 + btf__free(combined); 898 + return NULL; 899 + } 900 + 901 + ret = btf__add_btf(combined, mod); 902 + btf__free(mod); 903 + if (ret < 0) { 904 + p_err("failed to merge BTF from %s: %s", files[j], strerror(-ret)); 905 + btf__free(combined); 906 + return NULL; 907 + } 908 + } 909 + 910 + ret = btf__dedup(combined, NULL); 911 + if (ret) { 912 + p_err("failed to dedup combined BTF: %s", strerror(-ret)); 913 + btf__free(combined); 914 + return NULL; 915 + } 916 + 917 + return combined; 918 + } 919 + 882 920 static int do_dump(int argc, char **argv) 883 921 { 884 922 bool dump_c = false, sort_dump_c = true; ··· 998 958 NEXT_ARG(); 999 959 } else if (is_prefix(src, "file")) { 1000 960 const char sysfs_prefix[] = "/sys/kernel/btf/"; 961 + struct btf *vmlinux_base = base_btf; 962 + const char *files[MAX_BTF_FILES]; 963 + int nr_files = 0; 1001 964 1002 - if (!base_btf && 1003 - strncmp(*argv, sysfs_prefix, sizeof(sysfs_prefix) - 1) == 0 && 1004 - strcmp(*argv, sysfs_vmlinux) != 0) 1005 - base = get_vmlinux_btf_from_sysfs(); 1006 - 1007 - btf = btf__parse_split(*argv, base ?: base_btf); 1008 - if (!btf) { 1009 - err = -errno; 1010 - p_err("failed to load BTF from %s: %s", 1011 - *argv, strerror(errno)); 1012 - goto done; 965 + /* First grab our argument, filtering out the sysfs_vmlinux. */ 966 + if (strcmp(*argv, sysfs_vmlinux) != 0) { 967 + files[nr_files++] = *argv; 968 + } else { 969 + p_info("skipping %s (will be loaded as base)", *argv); 1013 970 } 1014 971 NEXT_ARG(); 972 + 973 + while (argc && is_prefix(*argv, "file")) { 974 + NEXT_ARG(); 975 + if (!REQ_ARGS(1)) { 976 + err = -EINVAL; 977 + goto done; 978 + } 979 + /* Filter out any sysfs vmlinux entries. */ 980 + if (strcmp(*argv, sysfs_vmlinux) == 0) { 981 + p_info("skipping %s (will be loaded as base)", *argv); 982 + NEXT_ARG(); 983 + continue; 984 + } 985 + if (nr_files >= MAX_BTF_FILES) { 986 + p_err("too many BTF files (max %d)", MAX_BTF_FILES); 987 + err = -E2BIG; 988 + goto done; 989 + } 990 + files[nr_files++] = *argv; 991 + NEXT_ARG(); 992 + } 993 + 994 + /* Auto-detect vmlinux base if any file is from sysfs */ 995 + if (!vmlinux_base) { 996 + for (int j = 0; j < nr_files; j++) { 997 + if (strncmp(files[j], sysfs_prefix, sizeof(sysfs_prefix) - 1) == 0) { 998 + base = get_vmlinux_btf_from_sysfs(); 999 + vmlinux_base = base; 1000 + break; 1001 + } 1002 + } 1003 + } 1004 + 1005 + /* All files were the sysfs_vmlinux, handle it like we used to */ 1006 + if (nr_files == 0) { 1007 + nr_files = 1; 1008 + files[0] = sysfs_vmlinux; 1009 + } 1010 + 1011 + if (nr_files == 1) { 1012 + btf = btf__parse_split(files[0], base ?: base_btf); 1013 + if (!btf) { 1014 + err = -errno; 1015 + p_err("failed to load BTF from %s: %s", files[0], strerror(errno)); 1016 + goto done; 1017 + } 1018 + } else { 1019 + if (!vmlinux_base) { 1020 + p_err("base BTF is required when merging multiple BTF files; use -B/--base-btf or use sysfs paths"); 1021 + err = -EINVAL; 1022 + goto done; 1023 + } 1024 + 1025 + btf = merge_btf_files(files, nr_files, vmlinux_base); 1026 + if (!btf) { 1027 + err = -errno; 1028 + goto done; 1029 + } 1030 + } 1015 1031 } else { 1016 1032 err = -1; 1017 1033 p_err("unrecognized BTF source specifier: '%s'", src); ··· 1541 1445 " %1$s %2$s dump BTF_SRC [format FORMAT] [root_id ROOT_ID]\n" 1542 1446 " %1$s %2$s help\n" 1543 1447 "\n" 1544 - " BTF_SRC := { id BTF_ID | prog PROG | map MAP [{key | value | kv | all}] | file FILE }\n" 1448 + " BTF_SRC := { id BTF_ID | prog PROG | map MAP [{key | value | kv | all}] |\n" 1449 + " file FILE [file FILE]... }\n" 1545 1450 " FORMAT := { raw | c [unsorted] }\n" 1546 1451 " " HELP_SPEC_MAP "\n" 1547 1452 " " HELP_SPEC_PROGRAM "\n"
+18 -9
tools/lib/bpf/btf.c
··· 2004 2004 { 2005 2005 struct btf_pipe p = { .src = src_btf, .dst = btf }; 2006 2006 int data_sz, sz, cnt, i, err, old_strs_len; 2007 + __u32 src_start_id; 2007 2008 __u32 *off; 2008 2009 void *t; 2009 2010 2010 - /* appending split BTF isn't supported yet */ 2011 - if (src_btf->base_btf) 2012 - return libbpf_err(-ENOTSUP); 2011 + /* 2012 + * When appending split BTF, the destination must share the same base 2013 + * BTF so that base type ID references remain valid. 2014 + */ 2015 + if (src_btf->base_btf && src_btf->base_btf != btf->base_btf) 2016 + return libbpf_err(-EOPNOTSUPP); 2017 + 2018 + src_start_id = src_btf->base_btf ? btf__type_cnt(src_btf->base_btf) : 1; 2013 2019 2014 2020 /* deconstruct BTF, if necessary, and invalidate raw_data */ 2015 2021 if (btf_ensure_modifiable(btf)) ··· 2027 2021 old_strs_len = btf->hdr->str_len; 2028 2022 2029 2023 data_sz = src_btf->hdr->type_len; 2030 - cnt = btf__type_cnt(src_btf) - 1; 2024 + cnt = src_btf->nr_types; 2031 2025 2032 2026 /* pre-allocate enough memory for new types */ 2033 2027 t = btf_add_type_mem(btf, data_sz); ··· 2066 2060 if (err) 2067 2061 goto err_out; 2068 2062 while ((str_off = btf_field_iter_next(&it))) { 2063 + /* don't remap strings from shared base BTF */ 2064 + if (*str_off < src_btf->start_str_off) 2065 + continue; 2069 2066 err = btf_rewrite_str(&p, str_off); 2070 2067 if (err) 2071 2068 goto err_out; ··· 2083 2074 if (!*type_id) /* nothing to do for VOID references */ 2084 2075 continue; 2085 2076 2086 - /* we haven't updated btf's type count yet, so 2087 - * btf->start_id + btf->nr_types - 1 is the type ID offset we should 2088 - * add to all newly added BTF types 2089 - */ 2090 - *type_id += btf->start_id + btf->nr_types - 1; 2077 + /* don't remap types from shared base BTF */ 2078 + if (*type_id < src_start_id) 2079 + continue; 2080 + 2081 + *type_id += btf->start_id + btf->nr_types - src_start_id; 2091 2082 } 2092 2083 2093 2084 /* go to next type data and type offset index entry */
+111
tools/testing/selftests/bpf/prog_tests/btf_write.c
··· 497 497 btf__free(btf2); 498 498 } 499 499 500 + static void test_btf_add_btf_split() 501 + { 502 + struct btf *base = NULL, *split1 = NULL, *split2 = NULL; 503 + struct btf *combined = NULL; 504 + int id, err; 505 + 506 + /* Create a base BTF with an INT and a PTR to it */ 507 + base = btf__new_empty(); 508 + if (!ASSERT_OK_PTR(base, "base")) 509 + return; 510 + 511 + id = btf__add_int(base, "int", 4, BTF_INT_SIGNED); 512 + ASSERT_EQ(id, 1, "base_int_id"); 513 + id = btf__add_ptr(base, 1); 514 + ASSERT_EQ(id, 2, "base_ptr_id"); 515 + 516 + /* base has 2 types, type IDs 1..2 */ 517 + ASSERT_EQ(btf__type_cnt(base), 3, "base_type_cnt"); 518 + 519 + /* Create split1 on base: a STRUCT referencing base's int (ID 1) */ 520 + split1 = btf__new_empty_split(base); 521 + if (!ASSERT_OK_PTR(split1, "split1")) 522 + goto cleanup; 523 + 524 + id = btf__add_struct(split1, "s1", 4); 525 + /* split types start at base_type_cnt = 3 */ 526 + ASSERT_EQ(id, 3, "split1_struct_id"); 527 + btf__add_field(split1, "x", 1, 0, 0); /* refers to base int */ 528 + 529 + id = btf__add_ptr(split1, 3); 530 + ASSERT_EQ(id, 4, "split1_ptr_id"); /* ptr to the struct (split self-ref) */ 531 + 532 + /* Add a typedef "int_alias" -> base int in split1, which will be 533 + * duplicated in split2 to test that btf__dedup() merges them. 534 + */ 535 + id = btf__add_typedef(split1, "int_alias", 1); 536 + ASSERT_EQ(id, 5, "split1_typedef_id"); 537 + 538 + /* Create split2 on base: a TYPEDEF referencing base's ptr (ID 2) */ 539 + split2 = btf__new_empty_split(base); 540 + if (!ASSERT_OK_PTR(split2, "split2")) 541 + goto cleanup; 542 + 543 + id = btf__add_typedef(split2, "int_ptr", 2); /* refers to base ptr */ 544 + ASSERT_EQ(id, 3, "split2_typedef_id"); 545 + 546 + id = btf__add_struct(split2, "s2", 8); 547 + ASSERT_EQ(id, 4, "split2_struct_id"); 548 + btf__add_field(split2, "p", 3, 0, 0); /* refers to split2's own typedef */ 549 + 550 + /* Same "int_alias" typedef as split1 - should be deduped away */ 551 + id = btf__add_typedef(split2, "int_alias", 1); 552 + ASSERT_EQ(id, 5, "split2_dup_typedef_id"); 553 + 554 + /* Create combined split BTF on same base and merge both */ 555 + combined = btf__new_empty_split(base); 556 + if (!ASSERT_OK_PTR(combined, "combined")) 557 + goto cleanup; 558 + 559 + /* Merge split1: its types (3,4,5) should land at IDs 3,4,5 */ 560 + id = btf__add_btf(combined, split1); 561 + if (!ASSERT_GE(id, 0, "add_split1")) 562 + goto cleanup; 563 + ASSERT_EQ(id, 3, "split1_first_id"); 564 + 565 + /* Merge split2: its types (3,4,5) should be remapped to 6,7,8 */ 566 + id = btf__add_btf(combined, split2); 567 + if (!ASSERT_GE(id, 0, "add_split2")) 568 + goto cleanup; 569 + ASSERT_EQ(id, 6, "split2_first_id"); 570 + 571 + /* Before dedup: base (2) + split1 (3) + split2 (3) = 8 types + void */ 572 + ASSERT_EQ(btf__type_cnt(combined), 9, "pre_dedup_type_cnt"); 573 + 574 + VALIDATE_RAW_BTF( 575 + combined, 576 + /* base types (IDs 1-2) */ 577 + "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED", 578 + "[2] PTR '(anon)' type_id=1", 579 + 580 + /* split1 types (IDs 3-5): base refs unchanged */ 581 + "[3] STRUCT 's1' size=4 vlen=1\n" 582 + "\t'x' type_id=1 bits_offset=0", /* refers to base int=1 */ 583 + "[4] PTR '(anon)' type_id=3", /* refers to split1's struct=3 */ 584 + "[5] TYPEDEF 'int_alias' type_id=1", /* refers to base int=1 */ 585 + 586 + /* split2 types (IDs 6-8): remapped from 3,4,5 to 6,7,8 */ 587 + "[6] TYPEDEF 'int_ptr' type_id=2", /* base ptr=2, unchanged */ 588 + "[7] STRUCT 's2' size=8 vlen=1\n" 589 + "\t'p' type_id=6 bits_offset=0", /* split2 typedef: 3->6 */ 590 + "[8] TYPEDEF 'int_alias' type_id=1"); /* dup of [5] */ 591 + 592 + /* Dedup to mirror the bpftool merge flow; should remove the 593 + * duplicate "int_alias" typedef. 594 + */ 595 + err = btf__dedup(combined, NULL); 596 + if (!ASSERT_OK(err, "dedup")) 597 + goto cleanup; 598 + 599 + /* After dedup: one int_alias removed, so 7 types + void */ 600 + ASSERT_EQ(btf__type_cnt(combined), 8, "dedup_type_cnt"); 601 + 602 + cleanup: 603 + btf__free(combined); 604 + btf__free(split2); 605 + btf__free(split1); 606 + btf__free(base); 607 + } 608 + 500 609 void test_btf_write() 501 610 { 502 611 if (test__start_subtest("btf_add")) 503 612 test_btf_add(); 504 613 if (test__start_subtest("btf_add_btf")) 505 614 test_btf_add_btf(); 615 + if (test__start_subtest("btf_add_btf_split")) 616 + test_btf_add_btf_split(); 506 617 }