Merge branch 'improve-the-performance-of-btf-type-lookups-with-binary-search'

Donglin Peng says:

====================
Improve the performance of BTF type lookups with binary search

From: Donglin Peng <pengdonglin@xiaomi.com>

The series addresses the performance limitations of linear search in large
BTFs by:
1. Adding BTF permutation support
2. Using resolve_btfids to sort BTF during the build phase
3. Checking BTF sorting
4. Using binary search when looking up types

Patch #1 introduces an interface for btf__permute in libbpf to relay out BTF.
Patch #2 adds test cases to validate the functionality of btf__permute in base
and split BTF scenarios.
Patch #3 introduces a new phase in the resolve_btfids tool to sort BTF by name
in ascending order.
Patches #4-#7 implement the sorting check and binary search.
Patches #8-#10 optimize type lookup performance of some functions by skipping
anonymous types or invoking btf_find_by_name_kind.
Patch #11 refactors the code by calling str_is_empty.

Here is a simple performance test result [1] for lookups to find 87,584 named
types in vmlinux BTF:

./vmtest.sh -- ./test_progs -t btf_permute/perf -v

Results:
| Condition          | Lookup Time | Improvement  |
|--------------------|-------------|--------------|
| Unsorted (Linear)  |  36,534 ms  | Baseline     |
| Sorted (Binary)    |      15 ms  | 2437x faster |

The binary search implementation reduces lookup time from 36.5 seconds to 15
milliseconds, achieving a **2437x** speedup for large-scale type queries.

Changelog:
v12:
- Set the start_id to 1 instead of btf->start_id in the btf__find_by_name (AI)

v11:
- Link: https://lore.kernel.org/bpf/20260108031645.1350069-1-dolinux.peng@gmail.com/
- PATCH #1: Modify implementation of btf__permute: id_map[0] must be 0 for base BTF (Andrii)
- PATCH #3: Refactor the code (Andrii)
- PATCH #4~8:
  - Revert to using the binary search in v7 to simplify the code (Andrii)
  - Refactor the code of btf_check_sorted (Andrii, Eduard)
  - Rename sorted_start_id to named_start_id
  - Rename btf_sorted_start_id to btf_named_start_id, and add comments (Andrii, Eduard)

v10:
- Link: https://lore.kernel.org/all/20251218113051.455293-1-dolinux.peng@gmail.com/
- Improve btf__permute() documentation (Eduard)
- Fall back to linear search when locating anonymous types (Eduard)
- Remove redundant NULL name check in libbpf's linear search path (Eduard)
- Simplify btf_check_sorted() implementation (Eduard)
- Treat kernel modules as unsorted by default
- Introduce btf_is_sorted and btf_sorted_start_id for clarity (Eduard)
- Fix optimizations in btf_find_decl_tag_value() and btf_prepare_func_args()
  to support split BTF
- Remove linear search branch in determine_ptr_size()
- Rebase onto Ihor's v4 patch series [4]

v9:
- Link: https://lore.kernel.org/bpf/20251208062353.1702672-1-dolinux.peng@gmail.com/
- Optimize the performance of the function determine_ptr_size by invoking
  btf__find_by_name_kind
- Optimize the performance of btf_find_decl_tag_value/btf_prepare_func_args/
  bpf_core_add_cands by skipping anonymous types
- Rebase the patch series onto Ihor's v3 patch series [3]

v8
- Link: https://lore.kernel.org/bpf/20251126085025.784288-1-dolinux.peng@gmail.com/
- Remove the type dropping feature of btf__permute (Andrii)
- Refactor the code of btf__permute (Andrii, Eduard)
- Make the self-test code cleaner (Eduard)
- Reconstruct the BTF sorting patch based on Ihor's patch series [2]
- Simplify the sorting logic and place anonymous types before named types
  (Andrii, Eduard)
- Optimize type lookup performance of two kernel functions
- Refactoring the binary search and type lookup logic achieves a 4.2%
  performance gain, reducing the average lookup time (via the perf test
  code in [1] for 60,995 named types in vmlinux BTF) from 10,217 us (v7) to
  9,783 us (v8).

v7:
- Link: https://lore.kernel.org/all/20251119031531.1817099-1-dolinux.peng@gmail.com/
- btf__permute API refinement: Adjusted id_map and id_map_cnt parameter
  usage so that for base BTF, id_map[0] now contains the new id of original
  type id 1 (instead of VOID type id 0), improving logical consistency
- Selftest updates: Modified test cases to align with the API usage changes
- Refactor the code of resolve_btfids

v6:
- Link: https://lore.kernel.org/all/20251117132623.3807094-1-dolinux.peng@gmail.com/
- ID Map-based reimplementation of btf__permute (Andrii)
- Build-time BTF sorting using resolve_btfids (Alexei, Eduard)
- Binary search method refactoring (Andrii)
- Enhanced selftest coverage

v5:
- Link: https://lore.kernel.org/all/20251106131956.1222864-1-dolinux.peng@gmail.com/
- Refactor binary search implementation for improved efficiency
  (Thanks to Andrii and Eduard)
- Extend btf__permute interface with 'ids_sz' parameter to support
  type dropping feature (suggested by Andrii). Plan subsequent reimplementation of
  id_map version for comparative analysis with current sequence interface
- Add comprehensive test coverage for type dropping functionality
- Enhance function comment clarity and accuracy

v4:
- Link: https://lore.kernel.org/all/20251104134033.344807-1-dolinux.peng@gmail.com/
- Abstracted btf_dedup_remap_types logic into a helper function (suggested by Eduard).
- Removed btf_sort.c and implemented sorting separately for libbpf and kernel (suggested by Andrii).
- Added test cases for both base BTF and split BTF scenarios (suggested by Eduard).
- Added validation for name-only sorting of types (suggested by Andrii)
- Refactored btf__permute implementation to reduce complexity (suggested by Andrii)
- Add doc comments for btf__permute (suggested by Andrii)

v3:
- Link: https://lore.kernel.org/all/20251027135423.3098490-1-dolinux.peng@gmail.com/
- Remove sorting logic from libbpf and provide a generic btf__permute() interface (suggested
  by Andrii)
- Omitted the search direction patch to avoid conflicts with base BTF (suggested by Eduard).
- Include btf_sort.c directly in btf.c to reduce function call overhead

v2:
- Link: https://lore.kernel.org/all/20251020093941.548058-1-dolinux.peng@gmail.com/
- Moved sorting to the build phase to reduce overhead (suggested by Alexei).
- Integrated sorting into btf_dedup_compact_and_sort_types (suggested by Eduard).
- Added sorting checks during BTF parsing.
- Consolidated common logic into btf_sort.c for sharing (suggested by Alan).

v1:
- Link: https://lore.kernel.org/all/20251013131537.1927035-1-dolinux.peng@gmail.com/

[1] https://github.com/pengdonglin137/btf_sort_test
[2] https://lore.kernel.org/bpf/20251126012656.3546071-1-ihor.solodrai@linux.dev/
[3] https://lore.kernel.org/bpf/20251205223046.4155870-1-ihor.solodrai@linux.dev/
[4] https://lore.kernel.org/bpf/20251218003314.260269-1-ihor.solodrai@linux.dev/
====================

Link: https://patch.msgid.link/20260109130003.3313716-1-dolinux.peng@gmail.com
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
This commit is contained in:
Andrii Nakryiko 2026-01-13 16:10:40 -08:00
commit b9da17391e
10 changed files with 746 additions and 92 deletions

View File

@ -219,6 +219,7 @@ bool btf_is_module(const struct btf *btf);
bool btf_is_vmlinux(const struct btf *btf);
struct module *btf_try_get_module(const struct btf *btf);
u32 btf_nr_types(const struct btf *btf);
u32 btf_named_start_id(const struct btf *btf, bool own);
struct btf *btf_base_btf(const struct btf *btf);
bool btf_type_is_i32(const struct btf_type *t);
bool btf_type_is_i64(const struct btf_type *t);

View File

@ -259,6 +259,7 @@ struct btf {
void *nohdr_data;
struct btf_header hdr;
u32 nr_types; /* includes VOID for base BTF */
u32 named_start_id;
u32 types_size;
u32 data_size;
refcount_t refcnt;
@ -494,6 +495,11 @@ static bool btf_type_is_modifier(const struct btf_type *t)
return false;
}
static int btf_start_id(const struct btf *btf)
{
return btf->start_id + (btf->base_btf ? 0 : 1);
}
bool btf_type_is_void(const struct btf_type *t)
{
return t == &btf_void;
@ -544,21 +550,125 @@ u32 btf_nr_types(const struct btf *btf)
return total;
}
s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind)
/*
* Note that vmlinux and kernel module BTFs are always sorted
* during the building phase.
*/
static void btf_check_sorted(struct btf *btf)
{
u32 i, n, named_start_id = 0;
n = btf_nr_types(btf);
if (btf_is_vmlinux(btf)) {
for (i = btf_start_id(btf); i < n; i++) {
const struct btf_type *t = btf_type_by_id(btf, i);
const char *n = btf_name_by_offset(btf, t->name_off);
if (n[0] != '\0') {
btf->named_start_id = i;
return;
}
}
return;
}
for (i = btf_start_id(btf) + 1; i < n; i++) {
const struct btf_type *ta = btf_type_by_id(btf, i - 1);
const struct btf_type *tb = btf_type_by_id(btf, i);
const char *na = btf_name_by_offset(btf, ta->name_off);
const char *nb = btf_name_by_offset(btf, tb->name_off);
if (strcmp(na, nb) > 0)
return;
if (named_start_id == 0 && na[0] != '\0')
named_start_id = i - 1;
if (named_start_id == 0 && nb[0] != '\0')
named_start_id = i;
}
if (named_start_id)
btf->named_start_id = named_start_id;
}
/*
* btf_named_start_id - Get the named starting ID for the BTF
* @btf: Pointer to the target BTF object
* @own: Flag indicating whether to query only the current BTF (true = current BTF only,
* false = recursively traverse the base BTF chain)
*
* Return value rules:
* 1. For a sorted btf, return its named_start_id
* 2. Else for a split BTF, return its start_id
* 3. Else for a base BTF, return 1
*/
u32 btf_named_start_id(const struct btf *btf, bool own)
{
const struct btf *base_btf = btf;
while (!own && base_btf->base_btf)
base_btf = base_btf->base_btf;
return base_btf->named_start_id ?: (base_btf->start_id ?: 1);
}
static s32 btf_find_by_name_kind_bsearch(const struct btf *btf, const char *name)
{
const struct btf_type *t;
const char *tname;
u32 i, total;
s32 l, r, m;
l = btf_named_start_id(btf, true);
r = btf_nr_types(btf) - 1;
while (l <= r) {
m = l + (r - l) / 2;
t = btf_type_by_id(btf, m);
tname = btf_name_by_offset(btf, t->name_off);
if (strcmp(tname, name) >= 0) {
if (l == r)
return r;
r = m;
} else {
l = m + 1;
}
}
return btf_nr_types(btf);
}
s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind)
{
const struct btf *base_btf = btf_base_btf(btf);
const struct btf_type *t;
const char *tname;
s32 id, total;
if (base_btf) {
id = btf_find_by_name_kind(base_btf, name, kind);
if (id > 0)
return id;
}
total = btf_nr_types(btf);
for (i = 1; i < total; i++) {
t = btf_type_by_id(btf, i);
if (BTF_INFO_KIND(t->info) != kind)
continue;
tname = btf_name_by_offset(btf, t->name_off);
if (!strcmp(tname, name))
return i;
if (btf->named_start_id > 0 && name[0]) {
id = btf_find_by_name_kind_bsearch(btf, name);
for (; id < total; id++) {
t = btf_type_by_id(btf, id);
tname = btf_name_by_offset(btf, t->name_off);
if (strcmp(tname, name) != 0)
return -ENOENT;
if (BTF_INFO_KIND(t->info) == kind)
return id;
}
} else {
for (id = btf_start_id(btf); id < total; id++) {
t = btf_type_by_id(btf, id);
if (BTF_INFO_KIND(t->info) != kind)
continue;
tname = btf_name_by_offset(btf, t->name_off);
if (strcmp(tname, name) == 0)
return id;
}
}
return -ENOENT;
@ -3424,7 +3534,8 @@ const char *btf_find_decl_tag_value(const struct btf *btf, const struct btf_type
const struct btf_type *t;
int len, id;
id = btf_find_next_decl_tag(btf, pt, comp_idx, tag_key, 0);
id = btf_find_next_decl_tag(btf, pt, comp_idx, tag_key,
btf_named_start_id(btf, false) - 1);
if (id < 0)
return ERR_PTR(id);
@ -5791,6 +5902,7 @@ static struct btf *btf_parse(const union bpf_attr *attr, bpfptr_t uattr, u32 uat
goto errout;
}
env->btf = btf;
btf->named_start_id = 0;
data = kvmalloc(attr->btf_size, GFP_KERNEL | __GFP_NOWARN);
if (!data) {
@ -6210,6 +6322,7 @@ static struct btf *btf_parse_base(struct btf_verifier_env *env, const char *name
btf->data = data;
btf->data_size = data_size;
btf->kernel_btf = true;
btf->named_start_id = 0;
snprintf(btf->name, sizeof(btf->name), "%s", name);
err = btf_parse_hdr(env);
@ -6230,6 +6343,7 @@ static struct btf *btf_parse_base(struct btf_verifier_env *env, const char *name
if (err)
goto errout;
btf_check_sorted(btf);
refcount_set(&btf->refcnt, 1);
return btf;
@ -6327,6 +6441,7 @@ static struct btf *btf_parse_module(const char *module_name, const void *data,
btf->start_id = base_btf->nr_types;
btf->start_str_off = base_btf->hdr.str_len;
btf->kernel_btf = true;
btf->named_start_id = 0;
snprintf(btf->name, sizeof(btf->name), "%s", module_name);
btf->data = kvmemdup(data, data_size, GFP_KERNEL | __GFP_NOWARN);
@ -6363,6 +6478,7 @@ static struct btf *btf_parse_module(const char *module_name, const void *data,
}
btf_verifier_env_free(env);
btf_check_sorted(btf);
refcount_set(&btf->refcnt, 1);
return btf;
@ -7729,12 +7845,13 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
tname);
return -EINVAL;
}
/* Convert BTF function arguments into verifier types.
* Only PTR_TO_CTX and SCALAR are supported atm.
*/
for (i = 0; i < nargs; i++) {
u32 tags = 0;
int id = 0;
int id = btf_named_start_id(btf, false) - 1;
/* 'arg:<tag>' decl_tag takes precedence over derivation of
* register type from BTF type itself
@ -9223,7 +9340,7 @@ bpf_core_find_cands(struct bpf_core_ctx *ctx, u32 local_type_id)
}
/* Attempt to find target candidates in vmlinux BTF first */
cands = bpf_core_add_cands(cands, main_btf, 1);
cands = bpf_core_add_cands(cands, main_btf, btf_named_start_id(main_btf, true));
if (IS_ERR(cands))
return ERR_CAST(cands);
@ -9255,7 +9372,7 @@ bpf_core_find_cands(struct bpf_core_ctx *ctx, u32 local_type_id)
*/
btf_get(mod_btf);
spin_unlock_bh(&btf_idr_lock);
cands = bpf_core_add_cands(cands, mod_btf, btf_nr_types(main_btf));
cands = bpf_core_add_cands(cands, mod_btf, btf_named_start_id(mod_btf, true));
btf_put(mod_btf);
if (IS_ERR(cands))
return ERR_CAST(cands);

View File

@ -600,10 +600,17 @@ struct bpffs_btf_enums {
static int find_bpffs_btf_enums(struct bpffs_btf_enums *info)
{
struct {
const struct btf_type **type;
const char *name;
} btf_enums[] = {
{&info->cmd_t, "bpf_cmd"},
{&info->map_t, "bpf_map_type"},
{&info->prog_t, "bpf_prog_type"},
{&info->attach_t, "bpf_attach_type"},
};
const struct btf *btf;
const struct btf_type *t;
const char *name;
int i, n;
int i, id;
memset(info, 0, sizeof(*info));
@ -615,31 +622,16 @@ static int find_bpffs_btf_enums(struct bpffs_btf_enums *info)
info->btf = btf;
for (i = 1, n = btf_nr_types(btf); i < n; i++) {
t = btf_type_by_id(btf, i);
if (!btf_type_is_enum(t))
continue;
for (i = 0; i < ARRAY_SIZE(btf_enums); i++) {
id = btf_find_by_name_kind(btf, btf_enums[i].name,
BTF_KIND_ENUM);
if (id < 0)
return -ESRCH;
name = btf_name_by_offset(btf, t->name_off);
if (!name)
continue;
if (strcmp(name, "bpf_cmd") == 0)
info->cmd_t = t;
else if (strcmp(name, "bpf_map_type") == 0)
info->map_t = t;
else if (strcmp(name, "bpf_prog_type") == 0)
info->prog_t = t;
else if (strcmp(name, "bpf_attach_type") == 0)
info->attach_t = t;
else
continue;
if (info->cmd_t && info->map_t && info->prog_t && info->attach_t)
return 0;
*btf_enums[i].type = btf_type_by_id(btf, id);
}
return -ESRCH;
return 0;
}
static bool find_btf_enum_const(const struct btf *btf, const struct btf_type *enum_t,

View File

@ -20687,12 +20687,7 @@ static int find_btf_percpu_datasec(struct btf *btf)
* types to look at only module's own BTF types.
*/
n = btf_nr_types(btf);
if (btf_is_module(btf))
i = btf_nr_types(btf_vmlinux);
else
i = 1;
for(; i < n; i++) {
for (i = btf_named_start_id(btf, true); i < n; i++) {
t = btf_type_by_id(btf, i);
if (BTF_INFO_KIND(t->info) != BTF_KIND_DATASEC)
continue;

View File

@ -850,6 +850,67 @@ static int dump_raw_btf(struct btf *btf, const char *out_path)
return 0;
}
/*
* Sort types by name in ascending order resulting in all
* anonymous types being placed before named types.
*/
static int cmp_type_names(const void *a, const void *b, void *priv)
{
struct btf *btf = (struct btf *)priv;
const struct btf_type *ta = btf__type_by_id(btf, *(__u32 *)a);
const struct btf_type *tb = btf__type_by_id(btf, *(__u32 *)b);
const char *na, *nb;
na = btf__str_by_offset(btf, ta->name_off);
nb = btf__str_by_offset(btf, tb->name_off);
return strcmp(na, nb);
}
static int sort_btf_by_name(struct btf *btf)
{
__u32 *permute_ids = NULL, *id_map = NULL;
int nr_types, i, err = 0;
__u32 start_id = 0, start_offs = 1, id;
if (btf__base_btf(btf)) {
start_id = btf__type_cnt(btf__base_btf(btf));
start_offs = 0;
}
nr_types = btf__type_cnt(btf) - start_id;
permute_ids = calloc(nr_types, sizeof(*permute_ids));
if (!permute_ids) {
err = -ENOMEM;
goto out;
}
id_map = calloc(nr_types, sizeof(*id_map));
if (!id_map) {
err = -ENOMEM;
goto out;
}
for (i = 0, id = start_id; i < nr_types; i++, id++)
permute_ids[i] = id;
qsort_r(permute_ids + start_offs, nr_types - start_offs,
sizeof(*permute_ids), cmp_type_names, btf);
for (i = 0; i < nr_types; i++) {
id = permute_ids[i] - start_id;
id_map[id] = i + start_id;
}
err = btf__permute(btf, id_map, nr_types, NULL);
if (err)
pr_err("FAILED: btf permute: %s\n", strerror(-err));
out:
free(permute_ids);
free(id_map);
return err;
}
static inline int make_out_path(char *buf, u32 buf_sz, const char *in_path, const char *suffix)
{
int len = snprintf(buf, buf_sz, "%s%s", in_path, suffix);
@ -1025,6 +1086,9 @@ int main(int argc, const char **argv)
if (load_btf(&obj))
goto out;
if (sort_btf_by_name(obj.btf))
goto out;
if (elf_collect(&obj))
goto out;

View File

@ -92,6 +92,8 @@ struct btf {
* - for split BTF counts number of types added on top of base BTF.
*/
__u32 nr_types;
/* the start IDs of named types in sorted BTF */
int named_start_id;
/* if not NULL, points to the base BTF on top of which the current
* split BTF is based
*/
@ -897,44 +899,103 @@ int btf__resolve_type(const struct btf *btf, __u32 type_id)
return type_id;
}
__s32 btf__find_by_name(const struct btf *btf, const char *type_name)
static void btf_check_sorted(struct btf *btf)
{
__u32 i, nr_types = btf__type_cnt(btf);
__u32 i, n, named_start_id = 0;
if (!strcmp(type_name, "void"))
n = btf__type_cnt(btf);
for (i = btf->start_id + 1; i < n; i++) {
struct btf_type *ta = btf_type_by_id(btf, i - 1);
struct btf_type *tb = btf_type_by_id(btf, i);
const char *na = btf__str_by_offset(btf, ta->name_off);
const char *nb = btf__str_by_offset(btf, tb->name_off);
if (strcmp(na, nb) > 0)
return;
if (named_start_id == 0 && na[0] != '\0')
named_start_id = i - 1;
if (named_start_id == 0 && nb[0] != '\0')
named_start_id = i;
}
if (named_start_id)
btf->named_start_id = named_start_id;
}
static __s32 btf_find_type_by_name_bsearch(const struct btf *btf, const char *name,
__s32 start_id)
{
const struct btf_type *t;
const char *tname;
__s32 l, r, m;
l = start_id;
r = btf__type_cnt(btf) - 1;
while (l <= r) {
m = l + (r - l) / 2;
t = btf_type_by_id(btf, m);
tname = btf__str_by_offset(btf, t->name_off);
if (strcmp(tname, name) >= 0) {
if (l == r)
return r;
r = m;
} else {
l = m + 1;
}
}
return btf__type_cnt(btf);
}
static __s32 btf_find_by_name_kind(const struct btf *btf, int start_id,
const char *type_name, __s32 kind)
{
__u32 nr_types = btf__type_cnt(btf);
const struct btf_type *t;
const char *tname;
__s32 id;
if (start_id < btf->start_id) {
id = btf_find_by_name_kind(btf->base_btf, start_id,
type_name, kind);
if (id >= 0)
return id;
start_id = btf->start_id;
}
if (kind == BTF_KIND_UNKN || strcmp(type_name, "void") == 0)
return 0;
for (i = 1; i < nr_types; i++) {
const struct btf_type *t = btf__type_by_id(btf, i);
const char *name = btf__name_by_offset(btf, t->name_off);
if (name && !strcmp(type_name, name))
return i;
if (btf->named_start_id > 0 && type_name[0]) {
start_id = max(start_id, btf->named_start_id);
id = btf_find_type_by_name_bsearch(btf, type_name, start_id);
for (; id < nr_types; id++) {
t = btf__type_by_id(btf, id);
tname = btf__str_by_offset(btf, t->name_off);
if (strcmp(tname, type_name) != 0)
return libbpf_err(-ENOENT);
if (kind < 0 || btf_kind(t) == kind)
return id;
}
} else {
for (id = start_id; id < nr_types; id++) {
t = btf_type_by_id(btf, id);
if (kind > 0 && btf_kind(t) != kind)
continue;
tname = btf__str_by_offset(btf, t->name_off);
if (strcmp(tname, type_name) == 0)
return id;
}
}
return libbpf_err(-ENOENT);
}
static __s32 btf_find_by_name_kind(const struct btf *btf, int start_id,
const char *type_name, __u32 kind)
/* the kind value of -1 indicates that kind matching should be skipped */
__s32 btf__find_by_name(const struct btf *btf, const char *type_name)
{
__u32 i, nr_types = btf__type_cnt(btf);
if (kind == BTF_KIND_UNKN || !strcmp(type_name, "void"))
return 0;
for (i = start_id; i < nr_types; i++) {
const struct btf_type *t = btf__type_by_id(btf, i);
const char *name;
if (btf_kind(t) != kind)
continue;
name = btf__name_by_offset(btf, t->name_off);
if (name && !strcmp(type_name, name))
return i;
}
return libbpf_err(-ENOENT);
return btf_find_by_name_kind(btf, 1, type_name, -1);
}
__s32 btf__find_by_name_kind_own(const struct btf *btf, const char *type_name,
@ -1006,6 +1067,7 @@ static struct btf *btf_new_empty(struct btf *base_btf)
btf->fd = -1;
btf->ptr_sz = sizeof(void *);
btf->swapped_endian = false;
btf->named_start_id = 0;
if (base_btf) {
btf->base_btf = base_btf;
@ -1057,6 +1119,7 @@ static struct btf *btf_new(const void *data, __u32 size, struct btf *base_btf, b
btf->start_id = 1;
btf->start_str_off = 0;
btf->fd = -1;
btf->named_start_id = 0;
if (base_btf) {
btf->base_btf = base_btf;
@ -1091,6 +1154,7 @@ static struct btf *btf_new(const void *data, __u32 size, struct btf *base_btf, b
err = err ?: btf_sanity_check(btf);
if (err)
goto done;
btf_check_sorted(btf);
done:
if (err) {
@ -1715,6 +1779,7 @@ static void btf_invalidate_raw_data(struct btf *btf)
free(btf->raw_data_swapped);
btf->raw_data_swapped = NULL;
}
btf->named_start_id = 0;
}
/* Ensure BTF is ready to be modified (by splitting into a three memory
@ -2069,7 +2134,7 @@ int btf__add_int(struct btf *btf, const char *name, size_t byte_sz, int encoding
int sz, name_off;
/* non-empty name */
if (!name || !name[0])
if (str_is_empty(name))
return libbpf_err(-EINVAL);
/* byte_sz must be power of 2 */
if (!byte_sz || (byte_sz & (byte_sz - 1)) || byte_sz > 16)
@ -2117,7 +2182,7 @@ int btf__add_float(struct btf *btf, const char *name, size_t byte_sz)
int sz, name_off;
/* non-empty name */
if (!name || !name[0])
if (str_is_empty(name))
return libbpf_err(-EINVAL);
/* byte_sz must be one of the explicitly allowed values */
@ -2172,7 +2237,7 @@ static int btf_add_ref_kind(struct btf *btf, int kind, const char *name, int ref
if (!t)
return libbpf_err(-ENOMEM);
if (name && name[0]) {
if (!str_is_empty(name)) {
name_off = btf__add_str(btf, name);
if (name_off < 0)
return name_off;
@ -2249,7 +2314,7 @@ static int btf_add_composite(struct btf *btf, int kind, const char *name, __u32
if (!t)
return libbpf_err(-ENOMEM);
if (name && name[0]) {
if (!str_is_empty(name)) {
name_off = btf__add_str(btf, name);
if (name_off < 0)
return name_off;
@ -2350,7 +2415,7 @@ int btf__add_field(struct btf *btf, const char *name, int type_id,
if (!m)
return libbpf_err(-ENOMEM);
if (name && name[0]) {
if (!str_is_empty(name)) {
name_off = btf__add_str(btf, name);
if (name_off < 0)
return name_off;
@ -2388,7 +2453,7 @@ static int btf_add_enum_common(struct btf *btf, const char *name, __u32 byte_sz,
if (!t)
return libbpf_err(-ENOMEM);
if (name && name[0]) {
if (!str_is_empty(name)) {
name_off = btf__add_str(btf, name);
if (name_off < 0)
return name_off;
@ -2446,7 +2511,7 @@ int btf__add_enum_value(struct btf *btf, const char *name, __s64 value)
return libbpf_err(-EINVAL);
/* non-empty name */
if (!name || !name[0])
if (str_is_empty(name))
return libbpf_err(-EINVAL);
if (value < INT_MIN || value > UINT_MAX)
return libbpf_err(-E2BIG);
@ -2523,7 +2588,7 @@ int btf__add_enum64_value(struct btf *btf, const char *name, __u64 value)
return libbpf_err(-EINVAL);
/* non-empty name */
if (!name || !name[0])
if (str_is_empty(name))
return libbpf_err(-EINVAL);
/* decompose and invalidate raw data */
@ -2563,7 +2628,7 @@ int btf__add_enum64_value(struct btf *btf, const char *name, __u64 value)
*/
int btf__add_fwd(struct btf *btf, const char *name, enum btf_fwd_kind fwd_kind)
{
if (!name || !name[0])
if (str_is_empty(name))
return libbpf_err(-EINVAL);
switch (fwd_kind) {
@ -2599,7 +2664,7 @@ int btf__add_fwd(struct btf *btf, const char *name, enum btf_fwd_kind fwd_kind)
*/
int btf__add_typedef(struct btf *btf, const char *name, int ref_type_id)
{
if (!name || !name[0])
if (str_is_empty(name))
return libbpf_err(-EINVAL);
return btf_add_ref_kind(btf, BTF_KIND_TYPEDEF, name, ref_type_id, 0);
@ -2651,7 +2716,7 @@ int btf__add_restrict(struct btf *btf, int ref_type_id)
*/
int btf__add_type_tag(struct btf *btf, const char *value, int ref_type_id)
{
if (!value || !value[0])
if (str_is_empty(value))
return libbpf_err(-EINVAL);
return btf_add_ref_kind(btf, BTF_KIND_TYPE_TAG, value, ref_type_id, 0);
@ -2668,7 +2733,7 @@ int btf__add_type_tag(struct btf *btf, const char *value, int ref_type_id)
*/
int btf__add_type_attr(struct btf *btf, const char *value, int ref_type_id)
{
if (!value || !value[0])
if (str_is_empty(value))
return libbpf_err(-EINVAL);
return btf_add_ref_kind(btf, BTF_KIND_TYPE_TAG, value, ref_type_id, 1);
@ -2687,7 +2752,7 @@ int btf__add_func(struct btf *btf, const char *name,
{
int id;
if (!name || !name[0])
if (str_is_empty(name))
return libbpf_err(-EINVAL);
if (linkage != BTF_FUNC_STATIC && linkage != BTF_FUNC_GLOBAL &&
linkage != BTF_FUNC_EXTERN)
@ -2773,7 +2838,7 @@ int btf__add_func_param(struct btf *btf, const char *name, int type_id)
if (!p)
return libbpf_err(-ENOMEM);
if (name && name[0]) {
if (!str_is_empty(name)) {
name_off = btf__add_str(btf, name);
if (name_off < 0)
return name_off;
@ -2808,7 +2873,7 @@ int btf__add_var(struct btf *btf, const char *name, int linkage, int type_id)
int sz, name_off;
/* non-empty name */
if (!name || !name[0])
if (str_is_empty(name))
return libbpf_err(-EINVAL);
if (linkage != BTF_VAR_STATIC && linkage != BTF_VAR_GLOBAL_ALLOCATED &&
linkage != BTF_VAR_GLOBAL_EXTERN)
@ -2857,7 +2922,7 @@ int btf__add_datasec(struct btf *btf, const char *name, __u32 byte_sz)
int sz, name_off;
/* non-empty name */
if (!name || !name[0])
if (str_is_empty(name))
return libbpf_err(-EINVAL);
if (btf_ensure_modifiable(btf))
@ -2934,7 +2999,7 @@ static int btf_add_decl_tag(struct btf *btf, const char *value, int ref_type_id,
struct btf_type *t;
int sz, value_off;
if (!value || !value[0] || component_idx < -1)
if (str_is_empty(value) || component_idx < -1)
return libbpf_err(-EINVAL);
if (validate_type_id(ref_type_id))
@ -5887,3 +5952,136 @@ 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 start_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_id = *type_id;
/* refer to the base BTF or VOID type */
if (new_id < p->btf->start_id)
return 0;
if (new_id >= btf__type_cnt(p->btf))
return -EINVAL;
*type_id = p->id_map[new_id - p->btf->start_id + p->start_offs];
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 *nt, *new_types = NULL;
__u32 *order_map = NULL;
int err = 0, i;
__u32 n, id, start_offs = 0;
if (!OPTS_VALID(opts, btf_permute_opts))
return libbpf_err(-EINVAL);
if (btf__base_btf(btf)) {
n = btf->nr_types;
} else {
if (id_map[0] != 0)
return libbpf_err(-EINVAL);
n = btf__type_cnt(btf);
start_offs = 1;
}
if (id_map_cnt != n)
return libbpf_err(-EINVAL);
/* record the sequence of types */
order_map = calloc(id_map_cnt, 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 = start_offs; i < id_map_cnt; i++) {
id = id_map[i];
if (id < btf->start_id || id >= btf__type_cnt(btf)) {
err = -EINVAL;
goto done;
}
id -= btf->start_id - start_offs;
/* cannot be mapped to the same ID */
if (order_map[id]) {
err = -EINVAL;
goto done;
}
order_map[id] = i + btf->start_id - start_offs;
}
p.btf = btf;
p.id_map = id_map;
p.start_offs = start_offs;
nt = new_types;
for (i = start_offs; i < id_map_cnt; i++) {
struct btf_field_iter it;
const struct btf_type *t;
__u32 *type_id;
int type_size;
id = order_map[i];
t = btf__type_by_id(btf, id);
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;
}
for (nt = new_types, i = 0; i < id_map_cnt - start_offs; i++) {
btf->type_offs[i] = nt - new_types;
nt += btf_type_size(nt);
}
free(order_map);
free(btf->types_data);
btf->types_data = new_types;
return 0;
done:
free(order_map);
free(new_types);
return libbpf_err(err);
}

View File

@ -281,6 +281,48 @@ 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()** rearranges BTF types in-place according to a specified ID mapping
* @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, including BTF extension data for reference updates
* @return 0 on success, negative error code on failure
*
* **btf__permute()** reorders BTF types based on the provided @id_map array,
* updating all internal type references to maintain consistency. The function
* operates in-place, modifying the BTF object directly.
*
* For **base BTF**:
* - @id_map must include all types from ID 0 to `btf__type_cnt(btf) - 1`
* - @id_map_cnt must be `btf__type_cnt(btf)`
* - Mapping is defined as `id_map[original_id] = new_id`
* - `id_map[0]` must be 0 (void type cannot be moved)
*
* For **split BTF**:
* - @id_map must include only split types (types added on top of the base BTF)
* - @id_map_cnt must be `btf__type_cnt(btf) - btf__type_cnt(btf__base_btf(btf))`
* - Mapping is defined as `id_map[original_id - start_id] = new_id`
* - `start_id` equals `btf__type_cnt(btf__base_btf(btf))`
*
* After permutation, all type references within the BTF data and optional
* BTF extension (if provided via @opts) are updated automatically.
*
* On error, returns a negative error code and sets errno:
* - `-EINVAL`: Invalid parameters or invalid ID mapping
* - `-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 {

View File

@ -2904,7 +2904,7 @@ static int bpf_object__init_user_btf_map(struct bpf_object *obj,
var_extra = btf_var(var);
map_name = btf__name_by_offset(obj->btf, var->name_off);
if (map_name == NULL || map_name[0] == '\0') {
if (str_is_empty(map_name)) {
pr_warn("map #%d: empty name.\n", var_idx);
return -EINVAL;
}
@ -4281,7 +4281,7 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
if (!sym_is_extern(sym))
continue;
ext_name = elf_sym_str(obj, sym->st_name);
if (!ext_name || !ext_name[0])
if (str_is_empty(ext_name))
continue;
ext = obj->externs;

View File

@ -453,4 +453,5 @@ LIBBPF_1.7.0 {
bpf_map__exclusive_program;
bpf_prog_assoc_struct_ops;
bpf_program__assoc_struct_ops;
btf__permute;
} LIBBPF_1.6.0;

View File

@ -0,0 +1,244 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Xiaomi */
#include <test_progs.h>
#include <bpf/btf.h>
#include "btf_helpers.h"
static void permute_base_check(struct btf *btf)
{
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");
}
/* Ensure btf__permute works as expected in the base-BTF scenario */
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; /* [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;
permute_base_check(btf);
/* ids[0] must be 0 for base BTF */
permute_ids[0] = 4; /* [0] -> [0] */
permute_ids[1] = 0; /* [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_ERR(err, "btf__permute_base"))
goto done;
/* BTF is not modified */
permute_base_check(btf);
/* id_map_cnt is invalid */
permute_ids[0] = 0; /* [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) - 1, NULL);
if (!ASSERT_ERR(err, "btf__permute_base"))
goto done;
/* BTF is not modified */
permute_base_check(btf);
/* 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;
/* BTF is not modified */
permute_base_check(btf);
/* 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;
/* BTF is not modified */
permute_base_check(btf);
done:
btf__free(btf);
}
static void permute_split_check(struct btf *btf)
{
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 '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");
}
/* Ensure btf__permute works as expected in the split-BTF scenario */
static void test_permute_split(void)
{
struct btf *split_btf = NULL, *base_btf = NULL;
__u32 permute_ids[4];
int err, start_id;
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");
start_id = btf__type_cnt(base_btf);
permute_ids[3 - start_id] = 6; /* [3] -> [6] */
permute_ids[4 - start_id] = 3; /* [4] -> [3] */
permute_ids[5 - start_id] = 5; /* [5] -> [5] */
permute_ids[6 - start_id] = 4; /* [6] -> [4] */
err = btf__permute(split_btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
if (!ASSERT_OK(err, "btf__permute_split"))
goto cleanup;
permute_split_check(split_btf);
/*
* For split BTF, id_map_cnt must equal to the number of types
* added on top of base BTF
*/
permute_ids[3 - start_id] = 4;
permute_ids[4 - start_id] = 3;
permute_ids[5 - start_id] = 5;
permute_ids[6 - start_id] = 6;
err = btf__permute(split_btf, permute_ids, ARRAY_SIZE(permute_ids) - 1, NULL);
if (!ASSERT_ERR(err, "btf__permute_split"))
goto cleanup;
/* BTF is not modified */
permute_split_check(split_btf);
/* Multiple types can not be mapped to the same ID */
permute_ids[3 - start_id] = 4;
permute_ids[4 - start_id] = 3;
permute_ids[5 - start_id] = 3;
permute_ids[6 - start_id] = 6;
err = btf__permute(split_btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
if (!ASSERT_ERR(err, "btf__permute_split"))
goto cleanup;
/* BTF is not modified */
permute_split_check(split_btf);
/* Can not map to base ID */
permute_ids[3 - start_id] = 4;
permute_ids[4 - start_id] = 2;
permute_ids[5 - start_id] = 5;
permute_ids[6 - start_id] = 6;
err = btf__permute(split_btf, permute_ids, ARRAY_SIZE(permute_ids), NULL);
if (!ASSERT_ERR(err, "btf__permute_split"))
goto cleanup;
/* BTF is not modified */
permute_split_check(split_btf);
cleanup:
btf__free(split_btf);
btf__free(base_btf);
}
void test_btf_permute(void)
{
if (test__start_subtest("permute_base"))
test_permute_base();
if (test__start_subtest("permute_split"))
test_permute_split();
}