* [RFC PATCH 0/2] Add btf__permute API
@ 2025-11-14 9:02 Donglin Peng
2025-11-14 9:02 ` [RFC PATCH 1/2] libbpf: Add BTF permutation support for type reordering Donglin Peng
2025-11-14 9:02 ` [RFC PATCH 2/2] selftests/bpf: Add test cases for btf__permute functionality Donglin Peng
0 siblings, 2 replies; 7+ messages in thread
From: Donglin Peng @ 2025-11-14 9:02 UTC (permalink / raw)
To: ast; +Cc: eddyz87, andrii.nakryiko, zhangxiaoqin, linux-kernel, bpf,
Donglin Peng
From: Donglin Peng <pengdonglin@xiaomi.com>
This patch adds btf__permute() function that reorganizes BTF types according
to a provided ID mapping array, updating all internal type references to
maintain consistency after permutation.
The API enables custom BTF type layouts for specialized use cases such as
BTF sorting optimizations.
This series is extracted from the previous patch set:
https://lore.kernel.org/all/20251106131956.1222864-1-dolinux.peng@gmail.com/
Key difference: this implementation adopts the ID mapping approach
suggested by Andrii Nakryiko.
Link to v4:
https://lore.kernel.org/all/20251104134033.344807-1-dolinux.peng@gmail.com/
Link to v3:
https://lore.kernel.org/all/20251027135423.3098490-1-dolinux.peng@gmail.com/
Link to v2:
https://lore.kernel.org/all/20251020093941.548058-1-dolinux.peng@gmail.com/
Link to v1:
https://lore.kernel.org/all/20251013131537.1927035-1-dolinux.peng@gmail.com/
Donglin Peng (2):
libbpf: Add BTF permutation support for type reordering
selftests/bpf: Add test cases for btf__permute functionality
tools/lib/bpf/btf.c | 186 ++++++
tools/lib/bpf/btf.h | 43 ++
tools/lib/bpf/libbpf.map | 1 +
.../selftests/bpf/prog_tests/btf_permute.c | 626 ++++++++++++++++++
4 files changed, 856 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_permute.c
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH 1/2] libbpf: Add BTF permutation support for type reordering
2025-11-14 9:02 [RFC PATCH 0/2] Add btf__permute API Donglin Peng
@ 2025-11-14 9:02 ` Donglin Peng
2025-11-14 9:47 ` bot+bpf-ci
2025-11-14 9:02 ` [RFC PATCH 2/2] selftests/bpf: Add test cases for btf__permute functionality Donglin Peng
1 sibling, 1 reply; 7+ messages in thread
From: Donglin Peng @ 2025-11-14 9:02 UTC (permalink / raw)
To: ast
Cc: eddyz87, andrii.nakryiko, zhangxiaoqin, linux-kernel, bpf,
Donglin Peng, Alan Maguire, Song Liu
From: Donglin Peng <pengdonglin@xiaomi.com>
Introduce btf__permute() API to allow in-place rearrangement of BTF types.
This function reorganizes BTF type order according to a provided array of
type IDs, updating all type references to maintain consistency.
Cc: Eduard Zingerman <eddyz87@gmail.com>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Andrii Nakryiko <andrii.nakryiko@gmail.com>
Cc: Alan Maguire <alan.maguire@oracle.com>
Cc: Song Liu <song@kernel.org>
Cc: Xiaoqin Zhang <zhangxiaoqin@xiaomi.com>
Signed-off-by: Donglin Peng <pengdonglin@xiaomi.com>
---
tools/lib/bpf/btf.c | 186 +++++++++++++++++++++++++++++++++++++++
tools/lib/bpf/btf.h | 43 +++++++++
tools/lib/bpf/libbpf.map | 1 +
3 files changed, 230 insertions(+)
diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
index 18907f0fcf9f..d22a2b0581b3 100644
--- a/tools/lib/bpf/btf.c
+++ b/tools/lib/bpf/btf.c
@@ -5829,3 +5829,189 @@ int btf__relocate(struct btf *btf, const struct btf *base_btf)
btf->owns_base = false;
return libbpf_err(err);
}
+
+struct btf_permute {
+ struct btf *btf;
+ __u32 *id_map;
+ __u32 offs;
+};
+
+/* Callback function to remap individual type ID references */
+static int btf_permute_remap_type_id(__u32 *type_id, void *ctx)
+{
+ struct btf_permute *p = ctx;
+ __u32 new_type_id = *type_id;
+
+ /* skip references that point into the base BTF */
+ if (new_type_id < p->btf->start_id)
+ return 0;
+
+ /* invalid reference id */
+ if (new_type_id >= btf__type_cnt(p->btf))
+ return -EINVAL;
+
+ new_type_id = p->id_map[new_type_id - p->offs];
+ /* reference a dropped type is not allowed */
+ if (new_type_id == 0)
+ return -EINVAL;
+
+ *type_id = new_type_id;
+ return 0;
+}
+
+int btf__permute(struct btf *btf, __u32 *id_map, __u32 id_map_cnt,
+ const struct btf_permute_opts *opts)
+{
+ struct btf_permute p;
+ struct btf_ext *btf_ext;
+ void *next_type, *end_type;
+ void *nt, *new_types = NULL;
+ int err = 0, n, i, new_type_len;
+ __u32 *order_map = NULL;
+ __u32 offs, id, new_nr_types = 0;
+
+ if (btf__base_btf(btf)) {
+ /*
+ * For split BTF, the number of types added on the
+ * top of base BTF
+ */
+ n = btf->nr_types;
+ offs = btf->start_id;
+ } else if (id_map[0] != 0) {
+ /* id_map[0] must be 0 for base BTF */
+ err = -EINVAL;
+ goto done;
+ } else {
+ /* include VOID type 0 for base BTF */
+ n = btf__type_cnt(btf);
+ offs = 0;
+ }
+
+ if (!OPTS_VALID(opts, btf_permute_opts) || (id_map_cnt != n))
+ return libbpf_err(-EINVAL);
+
+ /* used to record the storage sequence of types */
+ order_map = calloc(n, sizeof(*id_map));
+ if (!order_map) {
+ err = -ENOMEM;
+ goto done;
+ }
+
+ new_types = calloc(btf->hdr->type_len, 1);
+ if (!new_types) {
+ err = -ENOMEM;
+ goto done;
+ }
+
+ if (btf_ensure_modifiable(btf)) {
+ err = -ENOMEM;
+ goto done;
+ }
+
+ for (i = 0; i < id_map_cnt; i++) {
+ id = id_map[i];
+ /*
+ * 0: Drop the specified type (exclude base BTF type 0).
+ * For base BTF, type 0 is always preserved.
+ */
+ if (id == 0)
+ continue;
+ /* Invalid id */
+ if (id < btf->start_id || id >= btf__type_cnt(btf)) {
+ err = -EINVAL;
+ goto done;
+ }
+ id -= offs;
+ /* Multiple types cannot be mapped to the same ID */
+ if (order_map[id]) {
+ err = -EINVAL;
+ goto done;
+ }
+ order_map[id] = i + offs;
+ new_nr_types = max(id + 1, new_nr_types);
+ }
+
+ /* Check for missing IDs */
+ for (i = offs ? 0 : 1; i < new_nr_types; i++) {
+ if (order_map[i] == 0) {
+ err = -EINVAL;
+ goto done;
+ }
+ }
+
+ p.btf = btf;
+ p.id_map = id_map;
+ p.offs = offs;
+ nt = new_types;
+ for (i = offs ? 0 : 1; i < new_nr_types; i++) {
+ struct btf_field_iter it;
+ const struct btf_type *t;
+ __u32 *type_id;
+ int type_size;
+
+ id = order_map[i];
+ /* must be a valid type ID */
+ t = btf__type_by_id(btf, id);
+ if (!t) {
+ err = -EINVAL;
+ goto done;
+ }
+ type_size = btf_type_size(t);
+ memcpy(nt, t, type_size);
+
+ /* Fix up referenced IDs for BTF */
+ err = btf_field_iter_init(&it, nt, BTF_FIELD_ITER_IDS);
+ if (err)
+ goto done;
+ while ((type_id = btf_field_iter_next(&it))) {
+ err = btf_permute_remap_type_id(type_id, &p);
+ if (err)
+ goto done;
+ }
+
+ nt += type_size;
+ }
+
+ /* Fix up referenced IDs for btf_ext */
+ btf_ext = OPTS_GET(opts, btf_ext, NULL);
+ if (btf_ext) {
+ err = btf_ext_visit_type_ids(btf_ext, btf_permute_remap_type_id, &p);
+ if (err)
+ goto done;
+ }
+
+ new_type_len = nt - new_types;
+ next_type = new_types;
+ end_type = next_type + new_type_len;
+ i = 0;
+ while (next_type + sizeof(struct btf_type) <= end_type) {
+ btf->type_offs[i++] = next_type - new_types;
+ next_type += btf_type_size(next_type);
+ }
+
+ /* Resize */
+ if (new_type_len < btf->hdr->type_len) {
+ void *tmp_types;
+
+ tmp_types = realloc(new_types, new_type_len);
+ if (new_type_len && !tmp_types) {
+ err = -ENOMEM;
+ goto done;
+ }
+ new_types = tmp_types;
+ btf->nr_types = new_nr_types - (offs ? 0 : 1);
+ btf->type_offs_cap = btf->nr_types;
+ btf->types_data_cap = new_type_len;
+ btf->hdr->type_len = new_type_len;
+ btf->hdr->str_off = new_type_len;
+ btf->raw_size = btf->hdr->hdr_len + btf->hdr->type_len + btf->hdr->str_len;
+ }
+ free(btf->types_data);
+ btf->types_data = new_types;
+ return 0;
+
+done:
+ free(order_map);
+ free(new_types);
+ return libbpf_err(err);
+}
diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h
index ccfd905f03df..ec4e31e918c3 100644
--- a/tools/lib/bpf/btf.h
+++ b/tools/lib/bpf/btf.h
@@ -273,6 +273,49 @@ LIBBPF_API int btf__dedup(struct btf *btf, const struct btf_dedup_opts *opts);
*/
LIBBPF_API int btf__relocate(struct btf *btf, const struct btf *base_btf);
+struct btf_permute_opts {
+ size_t sz;
+ /* optional .BTF.ext info along the main BTF info */
+ struct btf_ext *btf_ext;
+ size_t :0;
+};
+#define btf_permute_opts__last_field btf_ext
+
+/**
+ * @brief **btf__permute()** performs in-place BTF type rearrangement
+ * @param btf BTF object to permute
+ * @param id_map Array mapping original type IDs to new IDs
+ * @param id_map_cnt Number of elements in @id_map
+ * @param opts Optional parameters for BTF extension updates
+ * @return 0 on success, negative error code on failure
+ *
+ * **btf__permute()** rearranges BTF types according to the specified ID mapping.
+ * The @id_map array defines the new type ID for each original type ID.
+ *
+ * For **base BTF**:
+ * - @id_map must include all types from ID 0 to `btf__type_cnt(btf)-1`
+ * - @id_map_cnt should be `btf__type_cnt(btf)`
+ * - Mapping uses `id_map[original_id] = new_id`
+ *
+ * For **split BTF**:
+ * - @id_map should cover only split types
+ * - @id_map_cnt should be `btf__type_cnt(btf) - btf__type_cnt(btf__base_btf(btf))`
+ * - Mapping uses `id_map[original_id - btf__type_cnt(btf__base_btf(btf))] = new_id`
+ *
+ * Setting @id_map element to 0 (except `id_map[0]` for base BTF) drops the corresponding type.
+ * Dropped types must not be referenced by any retained types. After permutation,
+ * type references in BTF data and optional extension are updated automatically.
+ *
+ * Note: Dropping types may orphan some strings, requiring subsequent **btf__dedup()**
+ * to clean up unreferenced strings.
+ *
+ * On error, returns negative error code and sets errno:
+ * - `-EINVAL`: Invalid parameters or ID mapping (duplicates, out-of-range)
+ * - `-ENOMEM`: Memory allocation failure
+ */
+LIBBPF_API int btf__permute(struct btf *btf, __u32 *id_map, __u32 id_map_cnt,
+ const struct btf_permute_opts *opts);
+
struct btf_dump;
struct btf_dump_opts {
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 8ed8749907d4..b778e5a5d0a8 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -451,4 +451,5 @@ LIBBPF_1.7.0 {
global:
bpf_map__set_exclusive_program;
bpf_map__exclusive_program;
+ btf__permute;
} LIBBPF_1.6.0;
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [RFC PATCH 2/2] selftests/bpf: Add test cases for btf__permute functionality
2025-11-14 9:02 [RFC PATCH 0/2] Add btf__permute API Donglin Peng
2025-11-14 9:02 ` [RFC PATCH 1/2] libbpf: Add BTF permutation support for type reordering Donglin Peng
@ 2025-11-14 9:02 ` Donglin Peng
2025-11-14 9:39 ` bot+bpf-ci
1 sibling, 1 reply; 7+ messages in thread
From: Donglin Peng @ 2025-11-14 9:02 UTC (permalink / raw)
To: ast
Cc: eddyz87, andrii.nakryiko, zhangxiaoqin, linux-kernel, bpf,
Donglin Peng, Alan Maguire, Song Liu
From: Donglin Peng <pengdonglin@xiaomi.com>
This patch introduces test cases for the btf__permute function to ensure
it works correctly with both base BTF and split BTF scenarios.
The test suite includes:
- test_permute_base: Validates permutation on base BTF
- test_permute_split: Tests permutation on split BTF
- test_permute_drop_base: Validates type dropping on base BTF
- test_permute_drop_split: Tests type dropping on split BTF
- test_permute_drop_dedup: Tests type dropping and deduping
Cc: Eduard Zingerman <eddyz87@gmail.com>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Andrii Nakryiko <andrii.nakryiko@gmail.com>
Cc: Alan Maguire <alan.maguire@oracle.com>
Cc: Song Liu <song@kernel.org>
Cc: Xiaoqin Zhang <zhangxiaoqin@xiaomi.com>
Signed-off-by: Donglin Peng <pengdonglin@xiaomi.com>
---
.../selftests/bpf/prog_tests/btf_permute.c | 626 ++++++++++++++++++
1 file changed, 626 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_permute.c
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_permute.c b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
new file mode 100644
index 000000000000..c1a47b8461ee
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
@@ -0,0 +1,626 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Xiaomi */
+
+#include <test_progs.h>
+#include <bpf/btf.h>
+#include "btf_helpers.h"
+
+/* Ensure btf__permute work as expected with base BTF */
+static void test_permute_base(void)
+{
+ struct btf *btf;
+ __u32 permute_ids[7];
+ int err;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "empty_main_btf"))
+ return;
+
+ btf__add_int(btf, "int", 4, BTF_INT_SIGNED); /* [1] int */
+ btf__add_ptr(btf, 1); /* [2] ptr to int */
+ btf__add_struct(btf, "s1", 4); /* [3] struct s1 { */
+ btf__add_field(btf, "m", 1, 0, 0); /* int m; */
+ /* } */
+ btf__add_struct(btf, "s2", 4); /* [4] struct s2 { */
+ btf__add_field(btf, "m", 1, 0, 0); /* int m; */
+ /* } */
+ btf__add_func_proto(btf, 1); /* [5] int (*)(int *p); */
+ btf__add_func_param(btf, "p", 2);
+ btf__add_func(btf, "f", BTF_FUNC_STATIC, 5); /* [6] int f(int *p); */
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[4] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[5] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p' type_id=2",
+ "[6] FUNC 'f' type_id=5 linkage=static");
+
+ permute_ids[0] = 0;
+ permute_ids[1] = 4; /* [1] -> [4] */
+ permute_ids[2] = 3; /* [2] -> [3] */
+ permute_ids[3] = 5; /* [3] -> [5] */
+ permute_ids[4] = 1; /* [4] -> [1] */
+ permute_ids[5] = 6; /* [5] -> [6] */
+ permute_ids[6] = 2; /* [6] -> [2] */
+ err = btf__permute(btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
+ if (!ASSERT_OK(err, "btf__permute_base"))
+ goto done;
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[2] FUNC 'f' type_id=6 linkage=static",
+ "[3] PTR '(anon)' type_id=4",
+ "[4] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[5] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[6] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1\n"
+ "\t'p' type_id=3");
+
+ /* For base BTF, id_map[0] must be 0 */
+ permute_ids[0] = 4;
+ permute_ids[1] = 0;
+ permute_ids[2] = 3;
+ permute_ids[3] = 5;
+ permute_ids[4] = 1;
+ permute_ids[5] = 6;
+ permute_ids[6] = 2;
+ err = btf__permute(btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
+ if (!ASSERT_ERR(err, "btf__permute_base"))
+ goto done;
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[2] FUNC 'f' type_id=6 linkage=static",
+ "[3] PTR '(anon)' type_id=4",
+ "[4] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[5] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[6] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1\n"
+ "\t'p' type_id=3");
+
+ /*
+ * For base BTF, id_map_cnt must equal to the number of types
+ * include VOID type
+ */
+ permute_ids[0] = 4;
+ permute_ids[1] = 0;
+ permute_ids[2] = 3;
+ permute_ids[3] = 5;
+ permute_ids[4] = 1;
+ permute_ids[5] = 6;
+ err = btf__permute(btf, permute_ids, ARRAY_SIZE(permute_ids) - 1, NULL);
+ if (!ASSERT_ERR(err, "btf__permute_base"))
+ goto done;
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[2] FUNC 'f' type_id=6 linkage=static",
+ "[3] PTR '(anon)' type_id=4",
+ "[4] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[5] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[6] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1\n"
+ "\t'p' type_id=3");
+
+ /* Multiple types can not be mapped to the same ID */
+ permute_ids[0] = 0;
+ permute_ids[1] = 4;
+ permute_ids[2] = 4;
+ permute_ids[3] = 5;
+ permute_ids[4] = 1;
+ permute_ids[5] = 6;
+ permute_ids[6] = 2;
+ err = btf__permute(btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
+ if (!ASSERT_ERR(err, "btf__permute_base"))
+ goto done;
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[2] FUNC 'f' type_id=6 linkage=static",
+ "[3] PTR '(anon)' type_id=4",
+ "[4] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[5] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[6] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1\n"
+ "\t'p' type_id=3");
+
+ /* Type ID must be valid */
+ permute_ids[0] = 0;
+ permute_ids[1] = 4;
+ permute_ids[2] = 3;
+ permute_ids[3] = 5;
+ permute_ids[4] = 1;
+ permute_ids[5] = 7;
+ permute_ids[6] = 2;
+ err = btf__permute(btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
+ if (!ASSERT_ERR(err, "btf__permute_base"))
+ goto done;
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[2] FUNC 'f' type_id=6 linkage=static",
+ "[3] PTR '(anon)' type_id=4",
+ "[4] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[5] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[6] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1\n"
+ "\t'p' type_id=3");
+
+done:
+ btf__free(btf);
+}
+
+/* Ensure btf__permute work as expected with split BTF */
+static void test_permute_split(void)
+{
+ struct btf *split_btf = NULL, *base_btf = NULL;
+ __u32 permute_ids[4];
+ int err;
+
+ base_btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(base_btf, "empty_main_btf"))
+ return;
+
+ btf__add_int(base_btf, "int", 4, BTF_INT_SIGNED); /* [1] int */
+ btf__add_ptr(base_btf, 1); /* [2] ptr to int */
+ VALIDATE_RAW_BTF(
+ base_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1");
+ split_btf = btf__new_empty_split(base_btf);
+ if (!ASSERT_OK_PTR(split_btf, "empty_split_btf"))
+ goto cleanup;
+ btf__add_struct(split_btf, "s1", 4); /* [3] struct s1 { */
+ btf__add_field(split_btf, "m", 1, 0, 0); /* int m; */
+ /* } */
+ btf__add_struct(split_btf, "s2", 4); /* [4] struct s2 { */
+ btf__add_field(split_btf, "m", 1, 0, 0); /* int m; */
+ /* } */
+ btf__add_func_proto(split_btf, 1); /* [5] int (*)(int p); */
+ btf__add_func_param(split_btf, "p", 2);
+ btf__add_func(split_btf, "f", BTF_FUNC_STATIC, 5); /* [6] int f(int *p); */
+
+ VALIDATE_RAW_BTF(
+ split_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[4] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[5] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p' type_id=2",
+ "[6] FUNC 'f' type_id=5 linkage=static");
+
+ permute_ids[0] = 6; /* [3] -> [6] */
+ permute_ids[1] = 3; /* [4] -> [3] */
+ permute_ids[2] = 5; /* [5] -> [5] */
+ permute_ids[3] = 4; /* [6] -> [4] */
+ err = btf__permute(split_btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
+ if (!ASSERT_OK(err, "btf__permute_split"))
+ goto cleanup;
+
+ VALIDATE_RAW_BTF(
+ split_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[4] FUNC 'f' type_id=5 linkage=static",
+ "[5] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p' type_id=2",
+ "[6] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0");
+
+ /*
+ * For split BTF, id_map_cnt must equal to the number of types
+ * added on top of base BTF
+ */
+ permute_ids[0] = 4;
+ permute_ids[1] = 3;
+ permute_ids[2] = 5;
+ permute_ids[3] = 6;
+ err = btf__permute(split_btf, permute_ids, 3, NULL);
+ if (!ASSERT_ERR(err, "btf__permute_split"))
+ goto cleanup;
+
+ VALIDATE_RAW_BTF(
+ split_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[4] FUNC 'f' type_id=5 linkage=static",
+ "[5] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p' type_id=2",
+ "[6] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0");
+
+ /* Multiple types can not be mapped to the same ID */
+ permute_ids[0] = 4;
+ permute_ids[1] = 3;
+ permute_ids[2] = 3;
+ permute_ids[3] = 6;
+ err = btf__permute(split_btf, permute_ids, 4, NULL);
+ if (!ASSERT_ERR(err, "btf__permute_split"))
+ goto cleanup;
+
+ VALIDATE_RAW_BTF(
+ split_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[4] FUNC 'f' type_id=5 linkage=static",
+ "[5] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p' type_id=2",
+ "[6] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0");
+
+ /* Can not map to base ID */
+ permute_ids[0] = 4;
+ permute_ids[1] = 2;
+ permute_ids[2] = 5;
+ permute_ids[3] = 6;
+ err = btf__permute(split_btf, permute_ids, 4, NULL);
+ if (!ASSERT_ERR(err, "btf__permute_split"))
+ goto cleanup;
+
+ VALIDATE_RAW_BTF(
+ split_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[4] FUNC 'f' type_id=5 linkage=static",
+ "[5] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p' type_id=2",
+ "[6] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0");
+
+cleanup:
+ btf__free(split_btf);
+ btf__free(base_btf);
+}
+
+/* Verify btf__permute function drops types correctly with base_btf */
+static void test_permute_drop_base(void)
+{
+ struct btf *btf;
+ __u32 permute_ids[7];
+ int err;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "empty_main_btf"))
+ return;
+
+ btf__add_int(btf, "int", 4, BTF_INT_SIGNED); /* [1] int */
+ btf__add_ptr(btf, 1); /* [2] ptr to int */
+ btf__add_struct(btf, "s1", 4); /* [3] struct s1 { */
+ btf__add_field(btf, "m", 1, 0, 0); /* int m; */
+ /* } */
+ btf__add_struct(btf, "s2", 4); /* [4] struct s2 { */
+ btf__add_field(btf, "m", 1, 0, 0); /* int m; */
+ /* } */
+ btf__add_func_proto(btf, 1); /* [5] int (*)(int *p); */
+ btf__add_func_param(btf, "p", 2);
+ btf__add_func(btf, "f", BTF_FUNC_STATIC, 5); /* [6] int f(int *p); */
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[4] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[5] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p' type_id=2",
+ "[6] FUNC 'f' type_id=5 linkage=static");
+
+ /* Drop ID 4 */
+ permute_ids[0] = 0;
+ permute_ids[1] = 5; /* [1] -> [5] */
+ permute_ids[2] = 1; /* [2] -> [1] */
+ permute_ids[3] = 2; /* [3] -> [2] */
+ permute_ids[4] = 0; /* Drop [4] */
+ permute_ids[5] = 3; /* [5] -> [3] */
+ permute_ids[6] = 4; /* [6] -> [4] */
+ err = btf__permute(btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
+ if (!ASSERT_OK(err, "btf__permute_drop_base"))
+ goto done;
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] PTR '(anon)' type_id=5",
+ "[2] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=5 bits_offset=0",
+ "[3] FUNC_PROTO '(anon)' ret_type_id=5 vlen=1\n"
+ "\t'p' type_id=1",
+ "[4] FUNC 'f' type_id=3 linkage=static",
+ "[5] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED");
+
+ /* Continue dropping */
+ permute_ids[0] = 0;
+ permute_ids[1] = 1; /* [1] -> [1] */
+ permute_ids[2] = 2; /* [2] -> [2] */
+ permute_ids[3] = 3; /* [3] -> [3] */
+ permute_ids[4] = 0; /* Drop [4] */
+ permute_ids[5] = 4; /* [5] -> [4] */
+ err = btf__permute(btf, permute_ids, 6, NULL);
+ if (!ASSERT_OK(err, "btf__permute_drop_base_fail"))
+ goto done;
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] PTR '(anon)' type_id=4",
+ "[2] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[3] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1\n"
+ "\t'p' type_id=1",
+ "[4] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED");
+
+ /* Cannot drop the ID referenced by others */
+ permute_ids[0] = 0;
+ permute_ids[1] = 2;
+ permute_ids[2] = 3;
+ permute_ids[3] = 1;
+ permute_ids[4] = 0; /* [4] is referenced by others */
+ err = btf__permute(btf, permute_ids, 5, NULL);
+ if (!ASSERT_ERR(err, "btf__permute_drop_base_fail"))
+ goto done;
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] PTR '(anon)' type_id=4",
+ "[2] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=4 bits_offset=0",
+ "[3] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1\n"
+ "\t'p' type_id=1",
+ "[4] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED");
+
+ /* Drop 2 IDs at once */
+ permute_ids[0] = 0;
+ permute_ids[1] = 2; /* [1] -> [2] */
+ permute_ids[2] = 0; /* Drop [2] */
+ permute_ids[3] = 0; /* Drop [3] */
+ permute_ids[4] = 1; /* [4] -> [1] */
+ err = btf__permute(btf, permute_ids, 5, NULL);
+ if (!ASSERT_OK(err, "btf__permute_drop_base_fail"))
+ goto done;
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1");
+
+ /* Drop all IDs */
+ permute_ids[0] = 0;
+ permute_ids[1] = 0; /* Drop [1] */
+ permute_ids[2] = 0; /* Drop [2] */
+ err = btf__permute(btf, permute_ids, 3, NULL);
+ if (!ASSERT_OK(err, "btf__permute_drop_base_fail"))
+ goto done;
+ if (!ASSERT_EQ(btf__type_cnt(btf), 0, "btf__permute_drop_split all"))
+ goto done;
+
+done:
+ btf__free(btf);
+}
+
+/* Verify btf__permute function drops types correctly with split BTF */
+static void test_permute_drop_split(void)
+{
+ struct btf *split_btf = NULL, *base_btf = NULL;
+ __u32 permute_ids[4];
+ int err;
+
+ base_btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(base_btf, "empty_main_btf"))
+ return;
+
+ btf__add_int(base_btf, "int", 4, BTF_INT_SIGNED); /* [1] int */
+ btf__add_ptr(base_btf, 1); /* [2] ptr to int */
+ VALIDATE_RAW_BTF(
+ base_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1");
+ split_btf = btf__new_empty_split(base_btf);
+ if (!ASSERT_OK_PTR(split_btf, "empty_split_btf"))
+ goto cleanup;
+ btf__add_struct(split_btf, "s1", 4); /* [3] struct s1 { */
+ btf__add_field(split_btf, "m", 1, 0, 0); /* int m; */
+ /* } */
+ btf__add_struct(split_btf, "s2", 4); /* [4] struct s2 { */
+ btf__add_field(split_btf, "m", 1, 0, 0); /* int m; */
+ /* } */
+ btf__add_func_proto(split_btf, 1); /* [5] int (*)(int p); */
+ btf__add_func_param(split_btf, "p", 2);
+ btf__add_func(split_btf, "f", BTF_FUNC_STATIC, 5); /* [6] int f(int *p); */
+
+ VALIDATE_RAW_BTF(
+ split_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[4] STRUCT 's2' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[5] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p' type_id=2",
+ "[6] FUNC 'f' type_id=5 linkage=static");
+
+ /* Drop ID 4 */
+ permute_ids[0] = 5; /* [3] -> [5] */
+ permute_ids[1] = 0; /* Drop [4] */
+ permute_ids[2] = 3; /* [5] -> [3] */
+ permute_ids[3] = 4; /* [6] -> [4] */
+ err = btf__permute(split_btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
+ if (!ASSERT_OK(err, "btf__permute_drop_split"))
+ goto cleanup;
+
+ VALIDATE_RAW_BTF(
+ split_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p' type_id=2",
+ "[4] FUNC 'f' type_id=3 linkage=static",
+ "[5] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0");
+
+ /* Can not drop the type referenced by others */
+ permute_ids[0] = 0; /* [3] is referenced by [4] */
+ permute_ids[1] = 4;
+ permute_ids[2] = 3;
+ err = btf__permute(split_btf, permute_ids, 3, NULL);
+ if (!ASSERT_ERR(err, "btf__permute_drop_split"))
+ goto cleanup;
+
+ VALIDATE_RAW_BTF(
+ split_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p' type_id=2",
+ "[4] FUNC 'f' type_id=3 linkage=static",
+ "[5] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0");
+
+ /* Continue dropping */
+ permute_ids[0] = 0; /* Drop [3] */
+ permute_ids[1] = 0; /* Drop [4] */
+ permute_ids[2] = 3; /* [5] -> [3] */
+ err = btf__permute(split_btf, permute_ids, 3, NULL);
+ if (!ASSERT_OK(err, "btf__permute_drop_split"))
+ goto cleanup;
+
+ VALIDATE_RAW_BTF(
+ split_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0");
+
+ /* Continue dropping */
+ permute_ids[0] = 0; /* Drop [3] */
+ err = btf__permute(split_btf, permute_ids, 1, NULL);
+ if (!ASSERT_OK(err, "btf__permute_drop_split"))
+ goto cleanup;
+
+ VALIDATE_RAW_BTF(
+ split_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1");
+
+cleanup:
+ btf__free(split_btf);
+ btf__free(base_btf);
+}
+
+/* Verify btf__permute then btf__dedup work correctly */
+static void test_permute_drop_dedup(void)
+{
+ struct btf *btf, *new_btf;
+ const struct btf_header *hdr;
+ const void *btf_data;
+ char expect_strs[] = "\0int\0s1\0m\0tag1\0tag2\0tag3";
+ char expect_strs_dedupped[] = "\0int\0s1\0m\0tag1";
+ __u32 permute_ids[6], btf_size;
+ int err;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "empty_main_btf"))
+ return;
+
+ btf__add_int(btf, "int", 4, BTF_INT_SIGNED); /* [1] int */
+ btf__add_struct(btf, "s1", 4); /* [2] struct s1 { */
+ btf__add_field(btf, "m", 1, 0, 0); /* int m; */
+ /* } */
+ btf__add_decl_tag(btf, "tag1", 2, -1); /* [3] tag -> s1: tag1 */
+ btf__add_decl_tag(btf, "tag2", 2, 1); /* [4] tag -> s1/m: tag2 */
+ btf__add_decl_tag(btf, "tag3", 2, 1); /* [5] tag -> s1/m: tag3 */
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=1 bits_offset=0",
+ "[3] DECL_TAG 'tag1' type_id=2 component_idx=-1",
+ "[4] DECL_TAG 'tag2' type_id=2 component_idx=1",
+ "[5] DECL_TAG 'tag3' type_id=2 component_idx=1");
+
+ btf_data = btf__raw_data(btf, &btf_size);
+ hdr = btf_data;
+ if (!ASSERT_EQ(hdr->str_len, ARRAY_SIZE(expect_strs), "expect_strs"))
+ goto done;
+
+ new_btf = btf__new(btf_data, btf_size);
+ if (!ASSERT_OK_PTR(new_btf, "btf__new"))
+ goto done;
+
+ /* Drop 2 IDs result in unreferenced strings */
+ permute_ids[0] = 0;
+ permute_ids[1] = 3; /* [1] -> [3] */
+ permute_ids[2] = 1; /* [2] -> [1] */
+ permute_ids[3] = 2; /* [3] -> [2] */
+ permute_ids[4] = 0; /* Drop result in unreferenced "tag2" */
+ permute_ids[5] = 0; /* Drop result in unreferenced "tag3" */
+ err = btf__permute(new_btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
+ if (!ASSERT_OK(err, "btf__permute"))
+ goto done;
+
+ VALIDATE_RAW_BTF(
+ new_btf,
+ "[1] STRUCT 's1' size=4 vlen=1\n"
+ "\t'm' type_id=3 bits_offset=0",
+ "[2] DECL_TAG 'tag1' type_id=1 component_idx=-1",
+ "[3] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED");
+
+ btf_data = btf__raw_data(new_btf, &btf_size);
+ hdr = btf_data;
+ if (!ASSERT_EQ(hdr->str_len, ARRAY_SIZE(expect_strs), "expect_strs"))
+ goto done;
+
+ err = btf__dedup(new_btf, NULL);
+ if (!ASSERT_OK(err, "btf__dedup"))
+ goto done;
+
+ btf_data = btf__raw_data(new_btf, &btf_size);
+ hdr = btf_data;
+ if (!ASSERT_EQ(hdr->str_len, ARRAY_SIZE(expect_strs_dedupped), "expect_strs_dedupped"))
+ goto done;
+
+done:
+ btf__free(btf);
+ btf__free(new_btf);
+}
+
+void test_btf_permute(void)
+{
+ if (test__start_subtest("permute_base"))
+ test_permute_base();
+ if (test__start_subtest("permute_split"))
+ test_permute_split();
+ if (test__start_subtest("permute_drop_base"))
+ test_permute_drop_base();
+ if (test__start_subtest("permute_drop_split"))
+ test_permute_drop_split();
+ if (test__start_subtest("permute_drop_dedup"))
+ test_permute_drop_dedup();
+}
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [RFC PATCH 2/2] selftests/bpf: Add test cases for btf__permute functionality
2025-11-14 9:02 ` [RFC PATCH 2/2] selftests/bpf: Add test cases for btf__permute functionality Donglin Peng
@ 2025-11-14 9:39 ` bot+bpf-ci
2025-11-14 9:44 ` Donglin Peng
0 siblings, 1 reply; 7+ messages in thread
From: bot+bpf-ci @ 2025-11-14 9:39 UTC (permalink / raw)
To: dolinux.peng, ast
Cc: eddyz87, andrii.nakryiko, zhangxiaoqin, linux-kernel, bpf,
pengdonglin, alan.maguire, song, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4216 bytes --]
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_permute.c b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
> new file mode 100644
> index 000000000..c1a47b846
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
[ ... ]
> @@ -0,0 +1,626 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2025 Xiaomi */
> +
> +#include <test_progs.h>
> +#include <bpf/btf.h>
> +#include "btf_helpers.h"
[ ... ]
> +/* Verify btf__permute then btf__dedup work correctly */
> +static void test_permute_drop_dedup(void)
> +{
> + struct btf *btf, *new_btf;
^^^^^^^^
Can this crash if the test fails before new_btf is initialized? Looking
at the declaration above, new_btf is uninitialized, and if ASSERT_EQ()
fails at line 570 before new_btf is assigned, the code jumps to the done
label which calls btf__free(new_btf). While btf__free() checks for NULL,
it doesn't protect against uninitialized garbage values and may
dereference an invalid pointer.
> + const struct btf_header *hdr;
> + const void *btf_data;
> + char expect_strs[] = "\0int\0s1\0m\0tag1\0tag2\0tag3";
> + char expect_strs_dedupped[] = "\0int\0s1\0m\0tag1";
> + __u32 permute_ids[6], btf_size;
> + int err;
> +
> + btf = btf__new_empty();
> + if (!ASSERT_OK_PTR(btf, "empty_main_btf"))
> + return;
> +
> + btf__add_int(btf, "int", 4, BTF_INT_SIGNED); /* [1] int */
> + btf__add_struct(btf, "s1", 4); /* [2] struct s1 { */
> + btf__add_field(btf, "m", 1, 0, 0); /* int m; */
> + /* } */
> + btf__add_decl_tag(btf, "tag1", 2, -1); /* [3] tag -> s1: tag1 */
> + btf__add_decl_tag(btf, "tag2", 2, 1); /* [4] tag -> s1/m: tag2 */
> + btf__add_decl_tag(btf, "tag3", 2, 1); /* [5] tag -> s1/m: tag3 */
> +
> + VALIDATE_RAW_BTF(
> + btf,
> + "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
> + "[2] STRUCT 's1' size=4 vlen=1\n"
> + "\t'm' type_id=1 bits_offset=0",
> + "[3] DECL_TAG 'tag1' type_id=2 component_idx=-1",
> + "[4] DECL_TAG 'tag2' type_id=2 component_idx=1",
> + "[5] DECL_TAG 'tag3' type_id=2 component_idx=1");
> +
> + btf_data = btf__raw_data(btf, &btf_size);
> + hdr = btf_data;
> + if (!ASSERT_EQ(hdr->str_len, ARRAY_SIZE(expect_strs), "expect_strs"))
^^^^^^^^^^^^^^
Does this need a NULL check for btf_data? The btf__raw_data() function
can return NULL on allocation failure (returns NULL at btf.c:1596), and
dereferencing hdr->str_len without checking would cause a segfault and
crash the test program.
> + goto done;
> +
> + new_btf = btf__new(btf_data, btf_size);
> + if (!ASSERT_OK_PTR(new_btf, "btf__new"))
> + goto done;
> +
> + /* Drop 2 IDs result in unreferenced strings */
> + permute_ids[0] = 0;
> + permute_ids[1] = 3; /* [1] -> [3] */
> + permute_ids[2] = 1; /* [2] -> [1] */
> + permute_ids[3] = 2; /* [3] -> [2] */
> + permute_ids[4] = 0; /* Drop result in unreferenced "tag2" */
> + permute_ids[5] = 0; /* Drop result in unreferenced "tag3" */
> + err = btf__permute(new_btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
> + if (!ASSERT_OK(err, "btf__permute"))
> + goto done;
> +
> + VALIDATE_RAW_BTF(
> + new_btf,
> + "[1] STRUCT 's1' size=4 vlen=1\n"
> + "\t'm' type_id=3 bits_offset=0",
> + "[2] DECL_TAG 'tag1' type_id=1 component_idx=-1",
> + "[3] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED");
> +
> + btf_data = btf__raw_data(new_btf, &btf_size);
> + hdr = btf_data;
> + if (!ASSERT_EQ(hdr->str_len, ARRAY_SIZE(expect_strs), "expect_strs"))
^^^^^^^^^^^^^^
Same NULL check issue here with btf__raw_data().
> + goto done;
> +
> + err = btf__dedup(new_btf, NULL);
> + if (!ASSERT_OK(err, "btf__dedup"))
> + goto done;
> +
> + btf_data = btf__raw_data(new_btf, &btf_size);
> + hdr = btf_data;
> + if (!ASSERT_EQ(hdr->str_len, ARRAY_SIZE(expect_strs_dedupped), "expect_strs_dedupped"))
^^^^^^^^^^^^^^
And again here.
> + goto done;
> +
> +done:
> + btf__free(btf);
> + btf__free(new_btf);
> +}
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/19359945665
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH 2/2] selftests/bpf: Add test cases for btf__permute functionality
2025-11-14 9:39 ` bot+bpf-ci
@ 2025-11-14 9:44 ` Donglin Peng
0 siblings, 0 replies; 7+ messages in thread
From: Donglin Peng @ 2025-11-14 9:44 UTC (permalink / raw)
To: bot+bpf-ci
Cc: ast, eddyz87, andrii.nakryiko, zhangxiaoqin, linux-kernel, bpf,
pengdonglin, alan.maguire, song, andrii, daniel, martin.lau,
yonghong.song, clm, ihor.solodrai
On Fri, Nov 14, 2025 at 5:39 PM <bot+bpf-ci@kernel.org> wrote:
>
> > diff --git a/tools/testing/selftests/bpf/prog_tests/btf_permute.c b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
> > new file mode 100644
> > index 000000000..c1a47b846
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
>
> [ ... ]
>
> > @@ -0,0 +1,626 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright (c) 2025 Xiaomi */
> > +
> > +#include <test_progs.h>
> > +#include <bpf/btf.h>
> > +#include "btf_helpers.h"
>
> [ ... ]
>
> > +/* Verify btf__permute then btf__dedup work correctly */
> > +static void test_permute_drop_dedup(void)
> > +{
> > + struct btf *btf, *new_btf;
> ^^^^^^^^
> Can this crash if the test fails before new_btf is initialized? Looking
> at the declaration above, new_btf is uninitialized, and if ASSERT_EQ()
> fails at line 570 before new_btf is assigned, the code jumps to the done
> label which calls btf__free(new_btf). While btf__free() checks for NULL,
> it doesn't protect against uninitialized garbage values and may
> dereference an invalid pointer.
Thanks, I will fix it.
>
> > + const struct btf_header *hdr;
> > + const void *btf_data;
> > + char expect_strs[] = "\0int\0s1\0m\0tag1\0tag2\0tag3";
> > + char expect_strs_dedupped[] = "\0int\0s1\0m\0tag1";
> > + __u32 permute_ids[6], btf_size;
> > + int err;
> > +
> > + btf = btf__new_empty();
> > + if (!ASSERT_OK_PTR(btf, "empty_main_btf"))
> > + return;
> > +
> > + btf__add_int(btf, "int", 4, BTF_INT_SIGNED); /* [1] int */
> > + btf__add_struct(btf, "s1", 4); /* [2] struct s1 { */
> > + btf__add_field(btf, "m", 1, 0, 0); /* int m; */
> > + /* } */
> > + btf__add_decl_tag(btf, "tag1", 2, -1); /* [3] tag -> s1: tag1 */
> > + btf__add_decl_tag(btf, "tag2", 2, 1); /* [4] tag -> s1/m: tag2 */
> > + btf__add_decl_tag(btf, "tag3", 2, 1); /* [5] tag -> s1/m: tag3 */
> > +
> > + VALIDATE_RAW_BTF(
> > + btf,
> > + "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
> > + "[2] STRUCT 's1' size=4 vlen=1\n"
> > + "\t'm' type_id=1 bits_offset=0",
> > + "[3] DECL_TAG 'tag1' type_id=2 component_idx=-1",
> > + "[4] DECL_TAG 'tag2' type_id=2 component_idx=1",
> > + "[5] DECL_TAG 'tag3' type_id=2 component_idx=1");
> > +
> > + btf_data = btf__raw_data(btf, &btf_size);
> > + hdr = btf_data;
> > + if (!ASSERT_EQ(hdr->str_len, ARRAY_SIZE(expect_strs), "expect_strs"))
> ^^^^^^^^^^^^^^
> Does this need a NULL check for btf_data? The btf__raw_data() function
> can return NULL on allocation failure (returns NULL at btf.c:1596), and
> dereferencing hdr->str_len without checking would cause a segfault and
> crash the test program.
Thanks, I will fix it.
>
> > + goto done;
> > +
> > + new_btf = btf__new(btf_data, btf_size);
> > + if (!ASSERT_OK_PTR(new_btf, "btf__new"))
> > + goto done;
> > +
> > + /* Drop 2 IDs result in unreferenced strings */
> > + permute_ids[0] = 0;
> > + permute_ids[1] = 3; /* [1] -> [3] */
> > + permute_ids[2] = 1; /* [2] -> [1] */
> > + permute_ids[3] = 2; /* [3] -> [2] */
> > + permute_ids[4] = 0; /* Drop result in unreferenced "tag2" */
> > + permute_ids[5] = 0; /* Drop result in unreferenced "tag3" */
> > + err = btf__permute(new_btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
> > + if (!ASSERT_OK(err, "btf__permute"))
> > + goto done;
> > +
> > + VALIDATE_RAW_BTF(
> > + new_btf,
> > + "[1] STRUCT 's1' size=4 vlen=1\n"
> > + "\t'm' type_id=3 bits_offset=0",
> > + "[2] DECL_TAG 'tag1' type_id=1 component_idx=-1",
> > + "[3] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED");
> > +
> > + btf_data = btf__raw_data(new_btf, &btf_size);
> > + hdr = btf_data;
> > + if (!ASSERT_EQ(hdr->str_len, ARRAY_SIZE(expect_strs), "expect_strs"))
> ^^^^^^^^^^^^^^
> Same NULL check issue here with btf__raw_data().
Thanks, I will fix it.
>
> > + goto done;
> > +
> > + err = btf__dedup(new_btf, NULL);
> > + if (!ASSERT_OK(err, "btf__dedup"))
> > + goto done;
> > +
> > + btf_data = btf__raw_data(new_btf, &btf_size);
> > + hdr = btf_data;
> > + if (!ASSERT_EQ(hdr->str_len, ARRAY_SIZE(expect_strs_dedupped), "expect_strs_dedupped"))
> ^^^^^^^^^^^^^^
> And again here.
Thanks, I will fix it.
>
> > + goto done;
> > +
> > +done:
> > + btf__free(btf);
> > + btf__free(new_btf);
> > +}
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/19359945665
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH 1/2] libbpf: Add BTF permutation support for type reordering
2025-11-14 9:02 ` [RFC PATCH 1/2] libbpf: Add BTF permutation support for type reordering Donglin Peng
@ 2025-11-14 9:47 ` bot+bpf-ci
2025-11-14 10:38 ` Donglin Peng
0 siblings, 1 reply; 7+ messages in thread
From: bot+bpf-ci @ 2025-11-14 9:47 UTC (permalink / raw)
To: dolinux.peng, ast
Cc: eddyz87, andrii.nakryiko, zhangxiaoqin, linux-kernel, bpf,
pengdonglin, alan.maguire, song, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4952 bytes --]
> diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
> index 9f141395c..de3aa62fd 100644
> --- a/tools/lib/bpf/btf.c
> +++ b/tools/lib/bpf/btf.c
> @@ -5829,3 +5829,189 @@ int btf__relocate(struct btf *btf, const struct btf *base_btf)
> btf->owns_base = false;
> return libbpf_err(err);
> }
> +
> +struct btf_permute {
> + struct btf *btf;
> + __u32 *id_map;
> + __u32 offs;
> +};
> +
> +/* Callback function to remap individual type ID references */
> +static int btf_permute_remap_type_id(__u32 *type_id, void *ctx)
> +{
[ ... ]
> +int btf__permute(struct btf *btf, __u32 *id_map, __u32 id_map_cnt,
> + const struct btf_permute_opts *opts)
> +{
> + struct btf_permute p;
> + struct btf_ext *btf_ext;
> + void *next_type, *end_type;
> + void *nt, *new_types = NULL;
> + int err = 0, n, i, new_type_len;
> + __u32 *order_map = NULL;
> + __u32 offs, id, new_nr_types = 0;
> +
> + if (btf__base_btf(btf)) {
> + /*
> + * For split BTF, the number of types added on the
> + * top of base BTF
> + */
> + n = btf->nr_types;
> + offs = btf->start_id;
> + } else if (id_map[0] != 0) {
> + /* id_map[0] must be 0 for base BTF */
> + err = -EINVAL;
> + goto done;
> + } else {
> + /* include VOID type 0 for base BTF */
> + n = btf__type_cnt(btf);
> + offs = 0;
> + }
> +
> + if (!OPTS_VALID(opts, btf_permute_opts) || (id_map_cnt != n))
> + return libbpf_err(-EINVAL);
> +
> + /* used to record the storage sequence of types */
> + order_map = calloc(n, sizeof(*id_map));
> + if (!order_map) {
> + err = -ENOMEM;
> + goto done;
> + }
> +
> + new_types = calloc(btf->hdr->type_len, 1);
> + if (!new_types) {
> + err = -ENOMEM;
> + goto done;
> + }
> +
> + if (btf_ensure_modifiable(btf)) {
> + err = -ENOMEM;
> + goto done;
> + }
> +
> + for (i = 0; i < id_map_cnt; i++) {
> + id = id_map[i];
> + /*
> + * 0: Drop the specified type (exclude base BTF type 0).
> + * For base BTF, type 0 is always preserved.
> + */
> + if (id == 0)
> + continue;
> + /* Invalid id */
> + if (id < btf->start_id || id >= btf__type_cnt(btf)) {
> + err = -EINVAL;
> + goto done;
> + }
> + id -= offs;
> + /* Multiple types cannot be mapped to the same ID */
> + if (order_map[id]) {
> + err = -EINVAL;
> + goto done;
> + }
> + order_map[id] = i + offs;
> + new_nr_types = max(id + 1, new_nr_types);
> + }
> +
> + /* Check for missing IDs */
> + for (i = offs ? 0 : 1; i < new_nr_types; i++) {
> + if (order_map[i] == 0) {
> + err = -EINVAL;
> + goto done;
> + }
> + }
> +
> + p.btf = btf;
> + p.id_map = id_map;
> + p.offs = offs;
> + nt = new_types;
> + for (i = offs ? 0 : 1; i < new_nr_types; i++) {
> + struct btf_field_iter it;
> + const struct btf_type *t;
> + __u32 *type_id;
> + int type_size;
> +
> + id = order_map[i];
> + /* must be a valid type ID */
> + t = btf__type_by_id(btf, id);
> + if (!t) {
> + err = -EINVAL;
> + goto done;
> + }
> + type_size = btf_type_size(t);
> + memcpy(nt, t, type_size);
> +
> + /* Fix up referenced IDs for BTF */
> + err = btf_field_iter_init(&it, nt, BTF_FIELD_ITER_IDS);
> + if (err)
> + goto done;
> + while ((type_id = btf_field_iter_next(&it))) {
> + err = btf_permute_remap_type_id(type_id, &p);
> + if (err)
> + goto done;
> + }
> +
> + nt += type_size;
> + }
> +
> + /* Fix up referenced IDs for btf_ext */
> + btf_ext = OPTS_GET(opts, btf_ext, NULL);
> + if (btf_ext) {
> + err = btf_ext_visit_type_ids(btf_ext, btf_permute_remap_type_id, &p);
> + if (err)
> + goto done;
> + }
> +
> + new_type_len = nt - new_types;
> + next_type = new_types;
> + end_type = next_type + new_type_len;
> + i = 0;
> + while (next_type + sizeof(struct btf_type) <= end_type) {
> + btf->type_offs[i++] = next_type - new_types;
> + next_type += btf_type_size(next_type);
> + }
> +
> + /* Resize */
> + if (new_type_len < btf->hdr->type_len) {
> + void *tmp_types;
> +
> + tmp_types = realloc(new_types, new_type_len);
> + if (new_type_len && !tmp_types) {
> + err = -ENOMEM;
> + goto done;
> + }
> + new_types = tmp_types;
> + btf->nr_types = new_nr_types - (offs ? 0 : 1);
> + btf->type_offs_cap = btf->nr_types;
> + btf->types_data_cap = new_type_len;
> + btf->hdr->type_len = new_type_len;
> + btf->hdr->str_off = new_type_len;
> + btf->raw_size = btf->hdr->hdr_len + btf->hdr->type_len + btf->hdr->str_len;
> + }
> + free(btf->types_data);
> + btf->types_data = new_types;
> + return 0;
Does this leak order_map on the success path? It looks like order_map is
allocated above and freed only in the error path at the done label, but
the success path returns here without freeing it.
> +
> +done:
> + free(order_map);
> + free(new_types);
> + return libbpf_err(err);
> +}
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/19359945665
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH 1/2] libbpf: Add BTF permutation support for type reordering
2025-11-14 9:47 ` bot+bpf-ci
@ 2025-11-14 10:38 ` Donglin Peng
0 siblings, 0 replies; 7+ messages in thread
From: Donglin Peng @ 2025-11-14 10:38 UTC (permalink / raw)
To: bot+bpf-ci
Cc: ast, eddyz87, andrii.nakryiko, zhangxiaoqin, linux-kernel, bpf,
pengdonglin, alan.maguire, song, andrii, daniel, martin.lau,
yonghong.song, clm, ihor.solodrai
On Fri, Nov 14, 2025 at 5:47 PM <bot+bpf-ci@kernel.org> wrote:
>
> > diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
> > index 9f141395c..de3aa62fd 100644
> > --- a/tools/lib/bpf/btf.c
> > +++ b/tools/lib/bpf/btf.c
> > @@ -5829,3 +5829,189 @@ int btf__relocate(struct btf *btf, const struct btf *base_btf)
> > btf->owns_base = false;
> > return libbpf_err(err);
> > }
> > +
> > +struct btf_permute {
> > + struct btf *btf;
> > + __u32 *id_map;
> > + __u32 offs;
> > +};
> > +
> > +/* Callback function to remap individual type ID references */
> > +static int btf_permute_remap_type_id(__u32 *type_id, void *ctx)
> > +{
>
> [ ... ]
>
> > +int btf__permute(struct btf *btf, __u32 *id_map, __u32 id_map_cnt,
> > + const struct btf_permute_opts *opts)
> > +{
> > + struct btf_permute p;
> > + struct btf_ext *btf_ext;
> > + void *next_type, *end_type;
> > + void *nt, *new_types = NULL;
> > + int err = 0, n, i, new_type_len;
> > + __u32 *order_map = NULL;
> > + __u32 offs, id, new_nr_types = 0;
> > +
> > + if (btf__base_btf(btf)) {
> > + /*
> > + * For split BTF, the number of types added on the
> > + * top of base BTF
> > + */
> > + n = btf->nr_types;
> > + offs = btf->start_id;
> > + } else if (id_map[0] != 0) {
> > + /* id_map[0] must be 0 for base BTF */
> > + err = -EINVAL;
> > + goto done;
> > + } else {
> > + /* include VOID type 0 for base BTF */
> > + n = btf__type_cnt(btf);
> > + offs = 0;
> > + }
> > +
> > + if (!OPTS_VALID(opts, btf_permute_opts) || (id_map_cnt != n))
> > + return libbpf_err(-EINVAL);
> > +
> > + /* used to record the storage sequence of types */
> > + order_map = calloc(n, sizeof(*id_map));
> > + if (!order_map) {
> > + err = -ENOMEM;
> > + goto done;
> > + }
> > +
> > + new_types = calloc(btf->hdr->type_len, 1);
> > + if (!new_types) {
> > + err = -ENOMEM;
> > + goto done;
> > + }
> > +
> > + if (btf_ensure_modifiable(btf)) {
> > + err = -ENOMEM;
> > + goto done;
> > + }
> > +
> > + for (i = 0; i < id_map_cnt; i++) {
> > + id = id_map[i];
> > + /*
> > + * 0: Drop the specified type (exclude base BTF type 0).
> > + * For base BTF, type 0 is always preserved.
> > + */
> > + if (id == 0)
> > + continue;
> > + /* Invalid id */
> > + if (id < btf->start_id || id >= btf__type_cnt(btf)) {
> > + err = -EINVAL;
> > + goto done;
> > + }
> > + id -= offs;
> > + /* Multiple types cannot be mapped to the same ID */
> > + if (order_map[id]) {
> > + err = -EINVAL;
> > + goto done;
> > + }
> > + order_map[id] = i + offs;
> > + new_nr_types = max(id + 1, new_nr_types);
> > + }
> > +
> > + /* Check for missing IDs */
> > + for (i = offs ? 0 : 1; i < new_nr_types; i++) {
> > + if (order_map[i] == 0) {
> > + err = -EINVAL;
> > + goto done;
> > + }
> > + }
> > +
> > + p.btf = btf;
> > + p.id_map = id_map;
> > + p.offs = offs;
> > + nt = new_types;
> > + for (i = offs ? 0 : 1; i < new_nr_types; i++) {
> > + struct btf_field_iter it;
> > + const struct btf_type *t;
> > + __u32 *type_id;
> > + int type_size;
> > +
> > + id = order_map[i];
> > + /* must be a valid type ID */
> > + t = btf__type_by_id(btf, id);
> > + if (!t) {
> > + err = -EINVAL;
> > + goto done;
> > + }
> > + type_size = btf_type_size(t);
> > + memcpy(nt, t, type_size);
> > +
> > + /* Fix up referenced IDs for BTF */
> > + err = btf_field_iter_init(&it, nt, BTF_FIELD_ITER_IDS);
> > + if (err)
> > + goto done;
> > + while ((type_id = btf_field_iter_next(&it))) {
> > + err = btf_permute_remap_type_id(type_id, &p);
> > + if (err)
> > + goto done;
> > + }
> > +
> > + nt += type_size;
> > + }
> > +
> > + /* Fix up referenced IDs for btf_ext */
> > + btf_ext = OPTS_GET(opts, btf_ext, NULL);
> > + if (btf_ext) {
> > + err = btf_ext_visit_type_ids(btf_ext, btf_permute_remap_type_id, &p);
> > + if (err)
> > + goto done;
> > + }
> > +
> > + new_type_len = nt - new_types;
> > + next_type = new_types;
> > + end_type = next_type + new_type_len;
> > + i = 0;
> > + while (next_type + sizeof(struct btf_type) <= end_type) {
> > + btf->type_offs[i++] = next_type - new_types;
> > + next_type += btf_type_size(next_type);
> > + }
> > +
> > + /* Resize */
> > + if (new_type_len < btf->hdr->type_len) {
> > + void *tmp_types;
> > +
> > + tmp_types = realloc(new_types, new_type_len);
> > + if (new_type_len && !tmp_types) {
> > + err = -ENOMEM;
> > + goto done;
> > + }
> > + new_types = tmp_types;
> > + btf->nr_types = new_nr_types - (offs ? 0 : 1);
> > + btf->type_offs_cap = btf->nr_types;
> > + btf->types_data_cap = new_type_len;
> > + btf->hdr->type_len = new_type_len;
> > + btf->hdr->str_off = new_type_len;
> > + btf->raw_size = btf->hdr->hdr_len + btf->hdr->type_len + btf->hdr->str_len;
> > + }
> > + free(btf->types_data);
> > + btf->types_data = new_types;
> > + return 0;
>
> Does this leak order_map on the success path? It looks like order_map is
> allocated above and freed only in the error path at the done label, but
> the success path returns here without freeing it.
Thanks, I will fix it.
>
> > +
> > +done:
> > + free(order_map);
> > + free(new_types);
> > + return libbpf_err(err);
> > +}
>
> [ ... ]
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/19359945665
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-11-14 10:38 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-14 9:02 [RFC PATCH 0/2] Add btf__permute API Donglin Peng
2025-11-14 9:02 ` [RFC PATCH 1/2] libbpf: Add BTF permutation support for type reordering Donglin Peng
2025-11-14 9:47 ` bot+bpf-ci
2025-11-14 10:38 ` Donglin Peng
2025-11-14 9:02 ` [RFC PATCH 2/2] selftests/bpf: Add test cases for btf__permute functionality Donglin Peng
2025-11-14 9:39 ` bot+bpf-ci
2025-11-14 9:44 ` Donglin Peng
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox