* [PATCH v2 bpf-next 00/18] Support inline functions in BTF
@ 2026-09-01 16:57 Alan Maguire
2026-09-01 16:57 ` [PATCH v2 bpf-next 01/18] btf: Extend UAPI to support BTF location (inline site) info Alan Maguire
` (17 more replies)
0 siblings, 18 replies; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
This series adds support to facilitate tracing of inline function
sites using BPF Type Format (BTF) information. An excellent overview
of the problem and proposed solution presented at LSF/MM/BPF is
available at [1].
The aim is to produce a compact representation providing sufficient
information to a tracer wishing to instrument an inline site via a
kprobe. The challenge to solve is compact representation - my
local bpf-next builds show nearly 600,000 inline sites for approximately
100,000 functions. Any BTF representation should utilize deduplication
where possible to minimize overheads.
The approach used here is to encode a series of inline sites in
a BTF DATASEC-like LOCSEC named for the associated section (like
".text", where each entry consists of a
<function type id, location prototype id, offset from base address>
triple. The function type id is the BTF_KIND_FUNC that was inlined,
the location prototype is a BTF_KIND_LOC_PROTO which tells us
for each function parameter how it is represented at the site.
It is a collection of either type id 0 (parameter not available)
or BTF_KIND_LOC_PARAM ids, the latter encoding a register number,
a dereference, a constant etc. Finally the offset is relative to
the base address, so in the case of the kernel this allows for
kASLR, and modules addresses are relative to module base address.
Full location parameter information is available for ~78% of
inlined functions; in other words all function parameters are
expressed via BTF_KIND_LOC_PARAM and can be retrieved. Those
remaining have more complex multi-expression encoding or are
not available at all.
For a vmlinux with 6Mb of BTF in /sys/kernel/btf/vmlinux, I
see 10.8Mb of inline information. When delivered as a compressed
module, the module size is 3.5Mb (via CONFIG_DEBUG_INFO_BTF_INLINE=m).
In this module-delivered mode, /sys/kernel/btf/vmlinux.inline will
be zero-sized until it is opened, at which point the module is
requested so the user experience is identical to when inline info
is derived from vmlinux itself, but we save the allocation associated
with live inline representation.
This series is rather large, and ideally we would split it into
basic inline support in kernel/libbpf/bpftool and associated tests
(patches 1-11) and delivery of inline info (patches 12-18).
This would allow us to land the pahole support once the libbpf
interfaces become available, though it will work in its absence.
The pahole code is still being tested but is available at [2].
Patch 1 consists of the UAPI changes and associated basic support
for KIND_LOC[SEC|PARAM|PROTO].
Patch 2 wires in libbpf support to handle these, including in dedup,
field iteration and so on. LOCSEC does not dedup since each entry
has a unique offset, but LOC_PARAM and LOC_PROTO do.
Patch 3 widens BTF permutation to support a transfer mode which
allows us to move a subset of types to a newly-created split BTF.
This will allow resolve_btfids to take a pahole-generated BTF
object (consisting of usual BTF plus inline info) and separate
out the inline-relevant components into a new .BTF.inline section.
This is an approach in line with other resolve_btfids changes which
prepare more generic BTF for the needs of the kernel.
Patches 4-8 provide tests for all of this.
Patches 9-11 add bpftool support to handle multi-split BTF for
inline info and to display location info.
Patch 12 adds resolve_btfids support to split out inline info
with the aim that any functions that are exclusively needed for
inline sites wind up in the inline BTF; this is a good test to
see if a function is likely fully inlined. Similarly any LOCSEC
references < start BTF id of the inline BTF suggest partially
inlined functions. See the patch for the nuances. resolve_btfids
takes as input BTF consisting of the usual vmlinux BTF objects
and inline-related ones; its role is to create a split BTF
where we move the inline-related pieces out so that
/sys/kernel/btf/vmlinux (or module equivalent) is the same
as before and does not pay the cost of inline representations.
This approach is different to how it was done in the RFC because
the model of applying kernel customizations in resolve_btfids
did not exist to the same degree then. In the RFC approach,
pahole delivered already-split BTF and we relocated it, but
since the base BTF it is built on now is transformed significantly
(added/changed types, sorting, dedup) it is not feasible to
track id changes across all of these operations. As such
we view splitting the inline info out as another kernel-specific
customization.
Patches 13-16 add support to the kbuild infrastructure and
/sys/kernel/btf representations for inline data. We can choose
to deliver vmlinux inline info via a module (since it is ~10Mb
that makes sense) but there are some issues in getting that
module to load when /sys/kernel/btf/vmlinux.inline is accessed
that are described in patch 15). Patch 16 handles relocation
of distilled base BTF for out-of-tree modules, ensuring that
relocation works across module and module inline info. The
painful part here is that we need to relocate ids in the
inline info, which requires multi-split BTF support. Happily
that is not a big change.
Finally tests in patches 17, 18 cover sysfs representation
and we introduce an inline site to bpf_testmod and ensure
its representation makes sense.
Changes since RFC [3]:
- Support for distilled base BTF
- Support for BTF_INLINE=m on-demand loading
- Reworked inline support to handle new resolve_btfids model
- .BTF.inline sections host FUNCs/FUNC_PROTOs/strings that are
needed for inline info only, avoiding polluting standard
vmlinux/module BTFs
[1] https://lwn.net/Articles/1083985/
[2] https://github.com/alan-maguire/dwarves/tree/pahole-next-btf-inline-v2-testing
[3] https://lore.kernel.org/bpf/20251008173512.731801-1-alan.maguire@oracle.com/
Alan Maguire (18):
btf: Extend UAPI to support BTF location (inline site) info
libbpf: Add support for BTF kinds LOC[_PARAM|_PROTO|SEC]
libbpf: Support moving permuted BTF types into split BTF
selftests/bpf: Test helper support for BTF_KIND_LOC[_PARAM|_PROTO|SEC]
selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to field iter tests
selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to dedup split tests
selftests/bpf: BTF distill tests to ensure LOC[_PARAM|_PROTO] add to
split BTF
selftests/bpf: Validate that btf__permute transfer works
bpftool: Handle multi-split BTF by supporting multiple base BTFs
bpftool: Document support for multi-split BTF
bpftool: Add ability to dump LOC_PARAM, LOC_PROTO and LOCSEC
resolve_btfids: Extract inline BTF
kbuild: Add support for BTF inline information
btf: Make vmlinux, module inline info available in /sys/kernel/btf
btf: Support CONFIG_DEBUG_INFO_BTF_INLINE=m
btf: Relocate inline BTF for modules with distilled base BTF
selftests/bpf: Test BTF sysfs inline representations
selftests/bpf: Add a test verifying inline information
Makefile | 1 +
include/asm-generic/vmlinux.lds.h | 11 +
include/linux/btf.h | 23 +-
include/linux/module.h | 4 +
include/uapi/linux/btf.h | 65 +-
kernel/bpf/Makefile | 1 +
kernel/bpf/btf.c | 686 ++++++++++++++++--
kernel/bpf/btf_vmlinux_inline.c | 37 +
kernel/module/main.c | 4 +
lib/Kconfig.debug | 18 +
scripts/Makefile.btf | 7 +
scripts/gen-btf.sh | 33 +-
.../bpf/bpftool/Documentation/bpftool-btf.rst | 7 +-
tools/bpf/bpftool/btf.c | 85 +++
tools/bpf/bpftool/main.c | 3 +-
tools/bpf/resolve_btfids/main.c | 336 ++++++++-
tools/include/uapi/linux/btf.h | 65 +-
tools/lib/bpf/btf.c | 659 ++++++++++++++++-
tools/lib/bpf/btf.h | 66 +-
tools/lib/bpf/btf_dump.c | 9 +
tools/lib/bpf/btf_iter.c | 22 +
tools/lib/bpf/btf_relocate.c | 7 +-
tools/lib/bpf/libbpf.map | 6 +
tools/lib/bpf/libbpf_internal.h | 5 +-
tools/testing/selftests/bpf/btf_helpers.c | 36 +-
.../bpf/prog_tests/btf_dedup_split.c | 109 +++
.../selftests/bpf/prog_tests/btf_distill.c | 76 ++
.../selftests/bpf/prog_tests/btf_field_iter.c | 31 +-
.../selftests/bpf/prog_tests/btf_inline.c | 110 +++
.../selftests/bpf/prog_tests/btf_permute.c | 128 ++++
.../selftests/bpf/prog_tests/btf_sysfs.c | 74 ++
tools/testing/selftests/bpf/test_btf.h | 16 +
.../selftests/bpf/test_kmods/bpf_testmod.c | 2 +-
tools/testing/selftests/bpf/trace_helpers.c | 20 +
tools/testing/selftests/bpf/trace_helpers.h | 1 +
35 files changed, 2636 insertions(+), 127 deletions(-)
create mode 100644 kernel/bpf/btf_vmlinux_inline.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_inline.c
--
2.43.5
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 01/18] btf: Extend UAPI to support BTF location (inline site) info
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:17 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 02/18] libbpf: Add support for BTF kinds LOC[_PARAM|_PROTO|SEC] Alan Maguire
` (16 subsequent siblings)
17 siblings, 2 replies; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Add BTF_KIND_LOC_PARAM, BTF_KIND_LOC_PROTO and BTF_KIND_LOCSEC
to help represent location information for functions.
BTF_KIND_LOC_PARAM is used to represent how we retrieve data at a
location; either via register(s), or register+offset, a dereference
of a register+offset or a constant value.
BTF_KIND_LOC_PROTO represents location information about a location
with multiple BTF_KIND_LOC_PARAMs.
And finally BTF_KIND_LOCSEC is a set of location sites, each
of which has
- a BTF_KIND_FUNC function associated with the inline site
- a location prototype specifying where to find the function
parameters
- an address offset relative to the kernel base address
This can be used to support representing
- a fully-inlined function at potentially multiple inline sites
with potentially different parameter availability
- a partially-inlined function where some _LOC_PROTOs represent
inlined sites as above and others have normal _FUNC representations
Also BTF_KIND_LOCSEC struct btf_loc will have two type id
references; one for the associated func, the other for the loc_proto.
Accordingly increase the number of m_offs references in btf_field_desc
to 2.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
include/linux/btf.h | 17 ++-
include/uapi/linux/btf.h | 65 ++++++++-
kernel/bpf/btf.c | 238 ++++++++++++++++++++++++++++++++-
tools/include/uapi/linux/btf.h | 65 ++++++++-
4 files changed, 379 insertions(+), 6 deletions(-)
diff --git a/include/linux/btf.h b/include/linux/btf.h
index ddd0f4f32d24..a4412bc16688 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -329,6 +329,21 @@ static inline u64 btf_enum64_value(const struct btf_enum64 *e)
return ((u64)e->val_hi32 << 32) | e->val_lo32;
}
+static inline struct btf_loc_param *btf_loc_param(const struct btf_type *t)
+{
+ return (struct btf_loc_param *)(t + 1);
+}
+
+static inline __u32 *btf_loc_params(const struct btf_type *t)
+{
+ return (__u32 *)(t + 1);
+}
+
+static inline struct btf_loc *btf_type_loc_secinfo(const struct btf_type *t)
+{
+ return (struct btf_loc *)(t + 1);
+}
+
static inline bool btf_is_composite(const struct btf_type *t)
{
u16 kind = btf_kind(t);
@@ -559,7 +574,7 @@ struct btf_field_desc {
/* member struct size, or zero, if no members */
int m_sz;
/* repeated per-member offsets */
- int m_off_cnt, m_offs[1];
+ int m_off_cnt, m_offs[2];
};
struct btf_field_iter {
diff --git a/include/uapi/linux/btf.h b/include/uapi/linux/btf.h
index 618167cab4e6..6062c9958034 100644
--- a/include/uapi/linux/btf.h
+++ b/include/uapi/linux/btf.h
@@ -92,7 +92,9 @@ enum {
BTF_KIND_DECL_TAG = 17, /* Decl Tag */
BTF_KIND_TYPE_TAG = 18, /* Type Tag */
BTF_KIND_ENUM64 = 19, /* Enumeration up to 64-bit values */
-
+ BTF_KIND_LOC_PARAM = 20, /* Location parameter information */
+ BTF_KIND_LOC_PROTO = 21, /* Location prototype for site */
+ BTF_KIND_LOCSEC = 22, /* Location section */
NR_BTF_KINDS,
BTF_KIND_MAX = NR_BTF_KINDS - 1,
};
@@ -212,4 +214,65 @@ struct btf_enum64 {
__u32 val_hi32;
};
+/*
+ * BTF_KIND_LOC_PARAM is followed by a single "struct btf_loc_param"
+ * that contains flags specifying the contents of the vlen-specified
+ * number of 4-byte values that follow.
+ */
+struct btf_loc_param {
+ __u32 flags;
+};
+
+/*
+ * The combination of size, vlen and flags gives us the means to interpret
+ * the following vlen-specified set of 4-byte values:
+ *
+ * - a BTF_LOC_PARAM_CONST is a constant value; combination
+ * of size, vlen and _SIGNED flag determines it. If the value requires
+ * 64 bits it is stored in {lo,hi} order.
+ * - a BTF_LOC_PARAM_ADDR is an address that will be normalized with
+ * respect to kernel base address.
+ * - a BTF_LOC_PARAM_REG with vlen 1 is a simple register number;
+ * with vlen 2 it is a multi-register parameter.
+ * - a _REG | DEREF with vlen 1 dereferences the value in the register
+ * number specified.
+ * - a REG | DEREF | OFFSET with vlen specifies the register value in
+ * the first 4-byte value and the offset in the remainder.
+ * - binary logical operators operate on a combination of register
+ * number and constant value, aside from _NOT which operates on
+ * a register
+ */
+enum btf_loc_param_flags {
+ BTF_LOC_PARAM_SIGNED = 0x1,
+ BTF_LOC_PARAM_CONST = 0x2,
+ BTF_LOC_PARAM_ADDR = 0x4,
+ BTF_LOC_PARAM_REG = 0x8,
+ BTF_LOC_PARAM_DEREF = 0x10,
+ BTF_LOC_PARAM_OFFSET = 0x20,
+};
+
+/*
+ * BTF_KIND_LOC_PROTO specifies location prototypes; i.e. how locations relate
+ * to parameters; a struct btf_type of BTF_KIND_LOC_PROTO is followed by a
+ * a vlen-specified number of __u32 BTF type ids which specify the associated
+ * BTF_KIND_LOC_PARAM for each function parameter associated with the
+ * location. The type should either be 0 (no location info) or point at
+ * a BTF_KIND_LOC_PARAM.
+ */
+
+/*
+ * BTF_KIND_LOCSEC consists of vlen-specified number of "struct btf_loc"
+ * containing location site-specific information;
+ *
+ * - function (func)
+ * - location prototype type id (loc_proto)
+ * - address offset (offset) relative to kernel base address
+ */
+
+struct btf_loc {
+ __u32 func;
+ __u32 loc_proto;
+ __u32 offset;
+};
+
#endif /* _UAPI__LINUX_BTF_H__ */
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 9c2cab08bb79..d74c8668aa3f 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -345,6 +345,9 @@ static const char * const btf_kind_str[NR_BTF_KINDS] = {
[BTF_KIND_DECL_TAG] = "DECL_TAG",
[BTF_KIND_TYPE_TAG] = "TYPE_TAG",
[BTF_KIND_ENUM64] = "ENUM64",
+ [BTF_KIND_LOC_PARAM] = "LOC_PARAM",
+ [BTF_KIND_LOC_PROTO] = "LOC_PROTO",
+ [BTF_KIND_LOCSEC] = "LOCSEC",
};
const char *btf_type_str(const struct btf_type *t)
@@ -517,11 +520,27 @@ static bool btf_type_is_decl_tag(const struct btf_type *t)
return BTF_INFO_KIND(t->info) == BTF_KIND_DECL_TAG;
}
+static bool btf_type_is_loc_param(const struct btf_type *t)
+{
+ return BTF_INFO_KIND(t->info) == BTF_KIND_LOC_PARAM;
+}
+
+static bool btf_type_is_loc_proto(const struct btf_type *t)
+{
+ return BTF_INFO_KIND(t->info) == BTF_KIND_LOC_PROTO;
+}
+
+static bool btf_type_is_locsec(const struct btf_type *t)
+{
+ return BTF_INFO_KIND(t->info) == BTF_KIND_LOCSEC;
+}
+
static bool btf_type_nosize(const struct btf_type *t)
{
return btf_type_is_void(t) || btf_type_is_fwd(t) ||
btf_type_is_func(t) || btf_type_is_func_proto(t) ||
- btf_type_is_decl_tag(t);
+ btf_type_is_decl_tag(t) || btf_type_is_loc_param(t) ||
+ btf_type_is_loc_proto(t) || btf_type_is_locsec(t);
}
static bool btf_type_nosize_or_null(const struct btf_type *t)
@@ -796,7 +815,9 @@ static bool btf_type_needs_resolve(const struct btf_type *t)
btf_type_is_var(t) ||
btf_type_is_func(t) ||
btf_type_is_decl_tag(t) ||
- btf_type_is_datasec(t);
+ btf_type_is_datasec(t) ||
+ btf_type_is_loc_proto(t) ||
+ btf_type_is_locsec(t);
}
/* t->size can be used */
@@ -4714,6 +4735,213 @@ static const struct btf_kind_operations enum64_ops = {
.show = btf_enum64_show,
};
+static s32 btf_loc_param_check_meta(struct btf_verifier_env *env,
+ const struct btf_type *t,
+ u32 meta_left)
+{
+ const struct btf_loc_param *p = btf_loc_param(t);
+ u32 meta_needed;
+ u32 size;
+
+ meta_needed = sizeof(*p) + sizeof(__u32) * btf_vlen(t);
+ if (meta_left < meta_needed) {
+ btf_verifier_log_basic(env, t,
+ "meta_left:%u meta_needed:%u",
+ meta_left, meta_needed);
+ return -EINVAL;
+ }
+
+ if (t->name_off) {
+ btf_verifier_log_type(env, t, "Invalid name");
+ return -EINVAL;
+ }
+ size = t->size;
+ if (size > 16 || !is_power_of_2(size)) {
+ btf_verifier_log_type(env, t, "Unexpected size");
+ return -EINVAL;
+ }
+
+ return meta_needed;
+}
+
+static void btf_loc_param_log(struct btf_verifier_env *env,
+ const struct btf_type *t)
+{
+ const struct btf_loc_param *p = btf_loc_param(t);
+ u32 *d = (u32 *)(p + 1);
+ u32 i, vlen = btf_vlen(t);
+
+ btf_verifier_log(env, "size=%u flags=0x%x param_data(", t->size, p->flags);
+ for (i = 0; i < vlen; i++, d++)
+ btf_verifier_log(env, "%u%s", *d,
+ i + 1 == vlen ? ")" : ", ");
+}
+
+static const struct btf_kind_operations loc_param_ops = {
+ .check_meta = btf_loc_param_check_meta,
+ .resolve = btf_df_resolve,
+ .check_member = btf_df_check_member,
+ .check_kflag_member = btf_df_check_kflag_member,
+ .log_details = btf_loc_param_log,
+ .show = btf_df_show,
+};
+
+static s32 btf_loc_proto_check_meta(struct btf_verifier_env *env,
+ const struct btf_type *t,
+ u32 meta_left)
+{
+ u32 meta_needed;
+
+ meta_needed = sizeof(__u32) * btf_type_vlen(t);
+
+ if (meta_left < meta_needed) {
+ btf_verifier_log_basic(env, t,
+ "meta_left:%u meta_needed:%u",
+ meta_left, meta_needed);
+ return -EINVAL;
+ }
+
+ if (t->name_off) {
+ btf_verifier_log_type(env, t, "Invalid name");
+ return -EINVAL;
+ }
+ return meta_needed;
+}
+
+static void btf_loc_proto_log(struct btf_verifier_env *env,
+ const struct btf_type *t)
+{
+ const __u32 *params = btf_loc_params(t);
+ u32 nr_params = btf_type_vlen(t), i;
+
+ btf_verifier_log(env, "loc_proto locs=(");
+ for (i = 0; i < nr_params; i++, params++) {
+ btf_verifier_log(env, "type=%u%s", *params,
+ i + 1 == nr_params ? ")" : ", ");
+ }
+}
+
+static int btf_loc_proto_resolve(struct btf_verifier_env *env,
+ const struct resolve_vertex *v)
+{
+ const __u32 *params = btf_loc_params(v->t);
+ u32 i, nr_params = btf_type_vlen(v->t);
+ struct btf *btf = env->btf;
+
+ for (i = 0; i < nr_params; i++) {
+ const struct btf_type *param_type;
+ u32 param_type_id = params[i];
+
+ if (!param_type_id)
+ continue;
+
+ param_type = btf_type_by_id(btf, param_type_id);
+ if (!param_type || !btf_type_is_loc_param(param_type)) {
+ btf_verifier_log_type(env, v->t,
+ "Invalid loc_param#%u", i + 1);
+ return -EINVAL;
+ }
+ }
+
+ env_stack_pop_resolved(env, 0, 0);
+ return 0;
+}
+
+static const struct btf_kind_operations loc_proto_ops = {
+ .check_meta = btf_loc_proto_check_meta,
+ .resolve = btf_loc_proto_resolve,
+ .check_member = btf_df_check_member,
+ .check_kflag_member = btf_df_check_kflag_member,
+ .log_details = btf_loc_proto_log,
+ .show = btf_df_show,
+};
+
+static s32 btf_locsec_check_meta(struct btf_verifier_env *env,
+ const struct btf_type *t,
+ u32 meta_left)
+{
+ u32 meta_needed;
+
+ meta_needed = sizeof(struct btf_loc) * btf_type_vlen(t);
+
+ if (meta_left < meta_needed) {
+ btf_verifier_log_basic(env, t,
+ "meta_left:%u meta_needed:%u",
+ meta_left, meta_needed);
+ return -EINVAL;
+ }
+ return meta_needed;
+}
+
+static void btf_locsec_log(struct btf_verifier_env *env,
+ const struct btf_type *t)
+{
+ const struct btf_loc *loc = btf_type_loc_secinfo(t);
+ u32 nr_locs = btf_type_vlen(t), i;
+ const struct btf *btf = env->btf;
+
+ btf_verifier_log(env, "locsec %s locs=(",
+ __btf_name_by_offset(btf, t->name_off));
+ for (i = 0; i < nr_locs; i++, loc++) {
+ btf_verifier_log(env, "\n\tfunc %u loc_proto %u offset 0x%x%s",
+ loc->func, loc->loc_proto, loc->offset,
+ i + 1 == nr_locs ? ")" : ", ");
+ }
+}
+
+static int btf_locsec_resolve(struct btf_verifier_env *env,
+ const struct resolve_vertex *v)
+{
+ const struct btf_loc *loc;
+ struct btf *btf = env->btf;
+ u32 i;
+
+ env->resolve_mode = RESOLVE_TBD;
+ for (i = v->next_member, loc = btf_type_loc_secinfo(v->t) + i;
+ i < btf_type_vlen(v->t); i++, loc++) {
+ const struct btf_type *func_type, *loc_proto_type;
+ u32 func_type_id = loc->func;
+ u32 loc_proto_type_id = loc->loc_proto;
+
+ func_type = btf_type_by_id(btf, func_type_id);
+ if (!func_type || !btf_type_is_func(func_type)) {
+ btf_verifier_log_type(env, v->t,
+ "Invalid func#%u", i + 1);
+ return -EINVAL;
+ }
+
+ if (!env_type_is_resolved(env, func_type_id)) {
+ env_stack_set_next_member(env, i);
+ return env_stack_push(env, func_type, func_type_id);
+ }
+
+ loc_proto_type = btf_type_by_id(btf, loc_proto_type_id);
+ if (!loc_proto_type || !btf_type_is_loc_proto(loc_proto_type)) {
+ btf_verifier_log_type(env, v->t,
+ "Invalid loc_proto#%u", i + 1);
+ return -EINVAL;
+ }
+
+ if (!env_type_is_resolved(env, loc_proto_type_id)) {
+ env_stack_set_next_member(env, i + 1);
+ return env_stack_push(env, loc_proto_type,
+ loc_proto_type_id);
+ }
+ }
+
+ env_stack_pop_resolved(env, 0, 0);
+ return 0;
+}
+
+static const struct btf_kind_operations locsec_ops = {
+ .check_meta = btf_locsec_check_meta,
+ .resolve = btf_locsec_resolve,
+ .check_member = btf_df_check_member,
+ .check_kflag_member = btf_df_check_kflag_member,
+ .log_details = btf_locsec_log,
+ .show = btf_df_show,
+};
+
static s32 btf_func_proto_check_meta(struct btf_verifier_env *env,
const struct btf_type *t,
u32 meta_left)
@@ -5383,6 +5611,9 @@ static const struct btf_kind_operations * const kind_ops[NR_BTF_KINDS] = {
[BTF_KIND_DECL_TAG] = &decl_tag_ops,
[BTF_KIND_TYPE_TAG] = &modifier_ops,
[BTF_KIND_ENUM64] = &enum64_ops,
+ [BTF_KIND_LOC_PARAM] = &loc_param_ops,
+ [BTF_KIND_LOC_PROTO] = &loc_proto_ops,
+ [BTF_KIND_LOCSEC] = &locsec_ops,
};
static s32 btf_check_meta(struct btf_verifier_env *env,
@@ -5457,7 +5688,8 @@ static bool btf_resolve_valid(struct btf_verifier_env *env,
if (!env_type_is_resolved(env, type_id))
return false;
- if (btf_type_is_struct(t) || btf_type_is_datasec(t))
+ if (btf_type_is_struct(t) || btf_type_is_datasec(t) ||
+ btf_type_is_loc_proto(t) || btf_type_is_locsec(t))
return !btf_resolved_type_id(btf, type_id) &&
!btf_resolved_type_size(btf, type_id);
diff --git a/tools/include/uapi/linux/btf.h b/tools/include/uapi/linux/btf.h
index 618167cab4e6..6062c9958034 100644
--- a/tools/include/uapi/linux/btf.h
+++ b/tools/include/uapi/linux/btf.h
@@ -92,7 +92,9 @@ enum {
BTF_KIND_DECL_TAG = 17, /* Decl Tag */
BTF_KIND_TYPE_TAG = 18, /* Type Tag */
BTF_KIND_ENUM64 = 19, /* Enumeration up to 64-bit values */
-
+ BTF_KIND_LOC_PARAM = 20, /* Location parameter information */
+ BTF_KIND_LOC_PROTO = 21, /* Location prototype for site */
+ BTF_KIND_LOCSEC = 22, /* Location section */
NR_BTF_KINDS,
BTF_KIND_MAX = NR_BTF_KINDS - 1,
};
@@ -212,4 +214,65 @@ struct btf_enum64 {
__u32 val_hi32;
};
+/*
+ * BTF_KIND_LOC_PARAM is followed by a single "struct btf_loc_param"
+ * that contains flags specifying the contents of the vlen-specified
+ * number of 4-byte values that follow.
+ */
+struct btf_loc_param {
+ __u32 flags;
+};
+
+/*
+ * The combination of size, vlen and flags gives us the means to interpret
+ * the following vlen-specified set of 4-byte values:
+ *
+ * - a BTF_LOC_PARAM_CONST is a constant value; combination
+ * of size, vlen and _SIGNED flag determines it. If the value requires
+ * 64 bits it is stored in {lo,hi} order.
+ * - a BTF_LOC_PARAM_ADDR is an address that will be normalized with
+ * respect to kernel base address.
+ * - a BTF_LOC_PARAM_REG with vlen 1 is a simple register number;
+ * with vlen 2 it is a multi-register parameter.
+ * - a _REG | DEREF with vlen 1 dereferences the value in the register
+ * number specified.
+ * - a REG | DEREF | OFFSET with vlen specifies the register value in
+ * the first 4-byte value and the offset in the remainder.
+ * - binary logical operators operate on a combination of register
+ * number and constant value, aside from _NOT which operates on
+ * a register
+ */
+enum btf_loc_param_flags {
+ BTF_LOC_PARAM_SIGNED = 0x1,
+ BTF_LOC_PARAM_CONST = 0x2,
+ BTF_LOC_PARAM_ADDR = 0x4,
+ BTF_LOC_PARAM_REG = 0x8,
+ BTF_LOC_PARAM_DEREF = 0x10,
+ BTF_LOC_PARAM_OFFSET = 0x20,
+};
+
+/*
+ * BTF_KIND_LOC_PROTO specifies location prototypes; i.e. how locations relate
+ * to parameters; a struct btf_type of BTF_KIND_LOC_PROTO is followed by a
+ * a vlen-specified number of __u32 BTF type ids which specify the associated
+ * BTF_KIND_LOC_PARAM for each function parameter associated with the
+ * location. The type should either be 0 (no location info) or point at
+ * a BTF_KIND_LOC_PARAM.
+ */
+
+/*
+ * BTF_KIND_LOCSEC consists of vlen-specified number of "struct btf_loc"
+ * containing location site-specific information;
+ *
+ * - function (func)
+ * - location prototype type id (loc_proto)
+ * - address offset (offset) relative to kernel base address
+ */
+
+struct btf_loc {
+ __u32 func;
+ __u32 loc_proto;
+ __u32 offset;
+};
+
#endif /* _UAPI__LINUX_BTF_H__ */
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 02/18] libbpf: Add support for BTF kinds LOC[_PARAM|_PROTO|SEC]
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
2026-09-01 16:57 ` [PATCH v2 bpf-next 01/18] btf: Extend UAPI to support BTF location (inline site) info Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:11 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 03/18] libbpf: Support moving permuted BTF types into split BTF Alan Maguire
` (15 subsequent siblings)
17 siblings, 1 reply; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Add support for new kinds to libbpf. BTF_KIND_LOC_PARAM and
BTF_KIND_LOC_PROTO are dedup-able so add support for their
deduplication, whereas since BTF_KIND_LOCSEC contains a unique
offset it is not. LOC_PARAM is considered a primary type
since it contains no external references; LOC_PROTO is a
reference type consisting of LOC_PARAM references so they
are handled in the primary and reference dedup phases
respectively.
For BTF field iteration, BTF_KIND_LOCSEC needs 2 m_offs[] values
for the associated KIND_FUNC and KIND_LOC_PROTO type ids in
each LOCSEC entry.
Add APIs to add location param, location prototypes and location
sections and btf_is_* tests, data accessors for each.
For BTF distillation we add location info to split BTF.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
tools/lib/bpf/btf.c | 372 +++++++++++++++++++++++++++++++-
tools/lib/bpf/btf.h | 50 +++++
tools/lib/bpf/btf_dump.c | 9 +
tools/lib/bpf/btf_iter.c | 22 ++
tools/lib/bpf/libbpf.map | 6 +
tools/lib/bpf/libbpf_internal.h | 2 +-
6 files changed, 457 insertions(+), 4 deletions(-)
diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
index c783359977b4..9449f6f50f18 100644
--- a/tools/lib/bpf/btf.c
+++ b/tools/lib/bpf/btf.c
@@ -57,6 +57,9 @@ static struct btf_layout layouts[NR_BTF_KINDS] = {
[BTF_KIND_DECL_TAG] = { sizeof(struct btf_decl_tag), 0, 0 },
[BTF_KIND_TYPE_TAG] = { 0, 0, 0 },
[BTF_KIND_ENUM64] = { 0, sizeof(struct btf_enum64), 0 },
+[BTF_KIND_LOC_PARAM] = { sizeof(struct btf_loc_param), sizeof(__u32), 0 },
+[BTF_KIND_LOC_PROTO] = { 0, sizeof(__u32), 0 },
+[BTF_KIND_LOCSEC] = { 0, sizeof(struct btf_loc), 0 },
};
struct btf {
@@ -486,6 +489,12 @@ static int btf_type_size(const struct btf *btf, const struct btf_type *t)
return base_size + vlen * sizeof(struct btf_var_secinfo);
case BTF_KIND_DECL_TAG:
return base_size + sizeof(struct btf_decl_tag);
+ case BTF_KIND_LOC_PARAM:
+ return base_size + sizeof(struct btf_loc_param) + vlen * sizeof(__u32);
+ case BTF_KIND_LOC_PROTO:
+ return base_size + vlen * sizeof(__u32);
+ case BTF_KIND_LOCSEC:
+ return base_size + vlen * sizeof(struct btf_loc);
default:
return btf_type_size_unknown(btf, t);
}
@@ -501,12 +510,15 @@ static void btf_bswap_type_base(struct btf_type *t)
static int btf_bswap_type_rest(struct btf_type *t)
{
struct btf_var_secinfo *v;
+ struct btf_loc_param *lp;
struct btf_enum64 *e64;
struct btf_member *m;
struct btf_array *a;
struct btf_param *p;
struct btf_enum *e;
+ struct btf_loc *l;
__u32 vlen = btf_vlen(t);
+ __u32 *d;
int i;
switch (btf_kind(t)) {
@@ -569,6 +581,23 @@ static int btf_bswap_type_rest(struct btf_type *t)
case BTF_KIND_DECL_TAG:
btf_decl_tag(t)->component_idx = bswap_32(btf_decl_tag(t)->component_idx);
return 0;
+ case BTF_KIND_LOC_PARAM:
+ lp = btf_loc_param(t);
+ lp->flags = bswap_32(lp->flags);
+ for (i = 0, d = (__u32 *)(lp + 1); i < vlen; i++, d++)
+ *d = bswap_32(*d);
+ return 0;
+ case BTF_KIND_LOC_PROTO:
+ for (i = 0, d = btf_loc_proto_params(t); i < vlen; i++, d++)
+ *d = bswap_32(*d);
+ return 0;
+ case BTF_KIND_LOCSEC:
+ for (i = 0, l = btf_locsec_locs(t); i < vlen; i++, l++) {
+ l->func = bswap_32(l->func);
+ l->loc_proto = bswap_32(l->loc_proto);
+ l->offset = bswap_32(l->offset);
+ }
+ return 0;
default:
pr_debug("Unsupported BTF_KIND:%u\n", btf_kind(t));
return -EINVAL;
@@ -745,6 +774,33 @@ static int btf_validate_type(const struct btf *btf, const struct btf_type *t, __
}
break;
}
+ case BTF_KIND_LOC_PARAM:
+ break;
+ case BTF_KIND_LOC_PROTO: {
+ __u32 *p = btf_loc_proto_params(t);
+
+ n = btf_vlen(t);
+ for (i = 0; i < n; i++, p++) {
+ err = btf_validate_id(btf, *p, id);
+ if (err)
+ return err;
+ }
+ break;
+ }
+ case BTF_KIND_LOCSEC: {
+ const struct btf_loc *l = btf_locsec_locs(t);
+
+ n = btf_vlen(t);
+ for (i = 0; i < n; i++, l++) {
+ if (!err)
+ err = btf_validate_id(btf, l->func, id);
+ if (!err)
+ err = btf_validate_id(btf, l->loc_proto, id);
+ if (err)
+ return err;
+ }
+ break;
+ }
default:
/* Kind may be represented in kind layout information. */
if (btf_type_size_unknown(btf, t) < 0) {
@@ -3344,6 +3400,208 @@ int btf__add_decl_attr(struct btf *btf, const char *value, int ref_type_id,
return btf_add_decl_tag(btf, value, ref_type_id, component_idx, 1);
}
+/*
+ * Append new BTF_KIND_LOC_PARAM with specified size and flags. Values are
+ * added via btf__add_loc_param_value().
+ *
+ * Returns:
+ * - >0, type ID of newly added BTF type;
+ * - <0, on error.
+ */
+int btf__add_loc_param(struct btf *btf, __u32 size, __u32 flags)
+{
+ struct btf_loc_param *p;
+ struct btf_type *t;
+ int sz, err;
+
+ err = btf_ensure_modifiable(btf);
+ if (err)
+ return libbpf_err(err);
+
+ sz = sizeof(struct btf_type) + sizeof(*p);
+ t = btf_add_type_mem(btf, sz);
+ if (!t)
+ return libbpf_err(-ENOMEM);
+
+ t->name_off = 0;
+ t->info = btf_type_info(BTF_KIND_LOC_PARAM, 0, 0);
+ t->size = size;
+
+ p = btf_loc_param(t);
+ p->flags = flags;
+
+ return btf_commit_type(btf, sz);
+}
+
+int btf__add_loc_param_value(struct btf *btf, __u32 value)
+{
+ struct btf_type *t;
+ int sz, err;
+ __u32 *v;
+
+ /* last type should be BTF_KIND_LOC_PARAM */
+ if (btf->nr_types == 0)
+ return libbpf_err(-EINVAL);
+ t = btf_last_type(btf);
+ if (!btf_is_loc_param(t))
+ return libbpf_err(-EINVAL);
+
+ /* decompose and invalidate raw data */
+ err = btf_ensure_modifiable(btf);
+ if (err)
+ return libbpf_err(err);
+
+ sz = sizeof(value);
+ v = btf_add_type_mem(btf, sz);
+ if (!v)
+ return libbpf_err(-ENOMEM);
+ *v = value;
+
+ /* update parent type's vlen */
+ t = btf_last_type(btf);
+ err = btf_type_inc_vlen(t);
+ if (err)
+ return libbpf_err(err);
+
+ btf_hdr_update_type_len(btf, btf->hdr.type_len + sz);
+ return 0;
+}
+
+/*
+ * Append new BTF_KIND_LOC_PROTO
+ *
+ * The prototype is then populated with 0 or more BTF_KIND_LOC_PARAMs via
+ * btf__add_loc_proto_param(); similar to how btf__add_func_param() adds
+ * parameters to a FUNC_PROTO.
+ *
+ * Returns:
+ * - >0, type ID of newly added BTF type;
+ * - <0, on error.
+ */
+int btf__add_loc_proto(struct btf *btf)
+{
+ struct btf_type *t;
+ int err;
+
+ err = btf_ensure_modifiable(btf);
+ if (err)
+ return libbpf_err(err);
+
+ t = btf_add_type_mem(btf, sizeof(struct btf_type));
+ if (!t)
+ return libbpf_err(-ENOMEM);
+
+ t->name_off = 0;
+ t->info = btf_type_info(BTF_KIND_LOC_PROTO, 0, 0);
+ t->size = 0;
+
+ return btf_commit_type(btf, sizeof(struct btf_type));
+}
+
+int btf__add_loc_proto_param(struct btf *btf, __u32 id)
+{
+ struct btf_type *t;
+ int sz, err;
+ __u32 *p;
+
+ if (validate_type_id(id))
+ return libbpf_err(-EINVAL);
+
+ /* last type should be BTF_KIND_LOC_PROTO */
+ if (btf->nr_types == 0)
+ return libbpf_err(-EINVAL);
+ t = btf_last_type(btf);
+ if (!btf_is_loc_proto(t))
+ return libbpf_err(-EINVAL);
+
+ /* decompose and invalidate raw data */
+ err = btf_ensure_modifiable(btf);
+ if (err)
+ return libbpf_err(err);
+
+ sz = sizeof(__u32);
+ p = btf_add_type_mem(btf, sz);
+ if (!p)
+ return libbpf_err(-ENOMEM);
+ *p = id;
+
+ /* update parent type's vlen */
+ t = btf_last_type(btf);
+ err = btf_type_inc_vlen(t);
+ if (err)
+ return libbpf_err(err);
+
+ btf_hdr_update_type_len(btf, btf->hdr.type_len + sz);
+ return 0;
+}
+
+int btf__add_locsec(struct btf *btf, const char *name)
+{
+ struct btf_type *t;
+ int name_off = 0;
+ int err;
+
+ err = btf_ensure_modifiable(btf);
+ if (err)
+ return libbpf_err(err);
+
+ t = btf_add_type_mem(btf, sizeof(struct btf_type));
+ if (!t)
+ return libbpf_err(-ENOMEM);
+
+ if (!str_is_empty(name)) {
+ name_off = btf__add_str(btf, name);
+ if (name_off < 0)
+ return name_off;
+ }
+ t->name_off = name_off;
+ t->info = btf_type_info(BTF_KIND_LOCSEC, 0, 0);
+ t->size = 0;
+
+ return btf_commit_type(btf, sizeof(struct btf_type));
+}
+
+int btf__add_locsec_loc(struct btf *btf, __u32 func, __u32 loc_proto,
+ __u32 offset)
+{
+ struct btf_type *t;
+ struct btf_loc *l;
+ int sz, err;
+
+ if (validate_type_id(func) || validate_type_id(loc_proto))
+ return libbpf_err(-EINVAL);
+
+ /* last type should be BTF_KIND_LOCSEC */
+ if (btf->nr_types == 0)
+ return libbpf_err(-EINVAL);
+ t = btf_last_type(btf);
+ if (!btf_is_locsec(t))
+ return libbpf_err(-EINVAL);
+
+ /* decompose and invalidate raw data */
+ err = btf_ensure_modifiable(btf);
+ if (err)
+ return libbpf_err(err);
+
+ sz = sizeof(*l);
+ l = btf_add_type_mem(btf, sz);
+ if (!l)
+ return libbpf_err(-ENOMEM);
+
+ l->func = func;
+ l->loc_proto = loc_proto;
+ l->offset = offset;
+
+ /* update parent type's vlen */
+ t = btf_last_type(btf);
+ err = btf_type_inc_vlen(t);
+ if (err)
+ return libbpf_err(err);
+
+ btf_hdr_update_type_len(btf, btf->hdr.type_len + sz);
+ return 0;
+}
+
struct btf_ext_sec_info_param {
__u32 off;
__u32 len;
@@ -4114,8 +4372,8 @@ static struct btf_dedup *btf_dedup_new(struct btf *btf, const struct btf_dedup_o
for (i = 1; i < type_cnt; i++) {
struct btf_type *t = btf_type_by_id(d->btf, i);
- /* VAR and DATASEC are never deduped and are self-canonical */
- if (btf_is_var(t) || btf_is_datasec(t))
+ /* VAR, DATASEC and LOCSEC are never deduped and are self-canonical */
+ if (btf_is_var(t) || btf_is_datasec(t) || btf_is_locsec(t))
d->map[i] = i;
else
d->map[i] = BTF_UNPROCESSED_ID;
@@ -4395,6 +4653,45 @@ static bool btf_compat_enum(struct btf_type *t1, struct btf_type *t2)
btf_is_any_enum(t1) && btf_is_any_enum(t2);
}
+static long btf_hash_loc_param(struct btf_type *t)
+{
+ long h = btf_hash_common(t);
+ __u32 *v = (__u32 *)btf_loc_param(t);
+ int i, vlen = btf_vlen(t);
+
+ for (i = 0; i <= vlen; i++, v++)
+ h = hash_combine(h, *v);
+ return h;
+}
+
+static long btf_hash_loc_proto(struct btf_type *t)
+{
+ __u32 *p = btf_loc_proto_params(t);
+ long h = btf_hash_common(t);
+ int i, vlen = btf_vlen(t);
+
+ for (i = 0; i < vlen; i++, p++)
+ h = hash_combine(h, *p);
+ return h;
+}
+
+static bool btf_equal_loc_param(struct btf_type *t1, struct btf_type *t2)
+{
+ struct btf_loc_param *p1 = btf_loc_param(t1);
+ struct btf_loc_param *p2 = btf_loc_param(t2);
+ __u32 *v1 = (__u32 *)(p1 + 1);
+ __u32 *v2 = (__u32 *)(p2 + 1);
+ int i, vlen = btf_vlen(t1);
+
+ if (!btf_equal_common(t1, t2))
+ return false;
+ for (i = 0; i < vlen; i++, v1++, v2++) {
+ if (*v1 != *v2)
+ return false;
+ }
+ return true;
+}
+
/*
* Calculate type signature hash of STRUCT/UNION, ignoring referenced type IDs,
* as referenced type IDs equivalence is established separately during type
@@ -4622,6 +4919,12 @@ static int btf_dedup_prep(struct btf_dedup *d)
case BTF_KIND_FUNC_PROTO:
h = btf_hash_fnproto(t);
break;
+ case BTF_KIND_LOC_PARAM:
+ h = btf_hash_loc_param(t);
+ break;
+ case BTF_KIND_LOC_PROTO:
+ h = btf_hash_loc_proto(t);
+ break;
default:
pr_debug("unknown kind %d for type [%d]\n", btf_kind(t), type_id);
return -EINVAL;
@@ -4664,6 +4967,8 @@ static int btf_dedup_prim_type(struct btf_dedup *d, __u32 type_id)
case BTF_KIND_DATASEC:
case BTF_KIND_DECL_TAG:
case BTF_KIND_TYPE_TAG:
+ case BTF_KIND_LOC_PROTO:
+ case BTF_KIND_LOCSEC:
return 0;
case BTF_KIND_INT:
@@ -4713,6 +5018,18 @@ static int btf_dedup_prim_type(struct btf_dedup *d, __u32 type_id)
}
break;
+ case BTF_KIND_LOC_PARAM:
+ h = btf_hash_loc_param(t);
+ for_each_dedup_cand(d, hash_entry, h) {
+ cand_id = hash_entry->value;
+ cand = btf_type_by_id(d->btf, cand_id);
+ if (btf_equal_loc_param(t, cand)) {
+ new_id = cand_id;
+ break;
+ }
+ }
+ break;
+
default:
return -EINVAL;
}
@@ -5145,6 +5462,13 @@ static int btf_dedup_is_equiv(struct btf_dedup *d, __u32 cand_id,
return 1;
}
+ case BTF_KIND_LOC_PARAM:
+ return btf_equal_loc_param(cand_type, canon_type);
+
+ case BTF_KIND_LOC_PROTO:
+ case BTF_KIND_LOCSEC:
+ return 0;
+
default:
return -EINVAL;
}
@@ -5489,6 +5813,41 @@ static int btf_dedup_ref_type(struct btf_dedup *d, __u32 type_id)
break;
}
+ case BTF_KIND_LOC_PROTO: {
+ __u32 *p1, *p2;
+ __u32 i, vlen;
+
+ p1 = btf_loc_proto_params(t);
+ vlen = btf_vlen(t);
+
+ for (i = 0; i < vlen; i++, p1++) {
+ ref_type_id = btf_dedup_ref_type(d, *p1);
+ if (ref_type_id < 0)
+ return ref_type_id;
+ *p1 = ref_type_id;
+ }
+
+ h = btf_hash_loc_proto(t);
+ for_each_dedup_cand(d, hash_entry, h) {
+ cand_id = hash_entry->value;
+ cand = btf_type_by_id(d->btf, cand_id);
+ if (!btf_equal_common(t, cand))
+ continue;
+ vlen = btf_vlen(cand);
+ p1 = btf_loc_proto_params(t);
+ p2 = btf_loc_proto_params(cand);
+ if (vlen == 0) {
+ new_id = cand_id;
+ break;
+ }
+ if (memcmp(p1, p2, vlen * sizeof(__u32)) == 0) {
+ new_id = cand_id;
+ break;
+ }
+ }
+ break;
+ }
+
default:
return -EINVAL;
}
@@ -5970,8 +6329,11 @@ static int btf_add_distilled_type_ids(struct btf_distill *dist, __u32 i)
case BTF_KIND_CONST:
case BTF_KIND_RESTRICT:
case BTF_KIND_VOLATILE:
+ case BTF_KIND_FUNC:
case BTF_KIND_FUNC_PROTO:
case BTF_KIND_TYPE_TAG:
+ case BTF_KIND_LOC_PARAM:
+ case BTF_KIND_LOC_PROTO:
dist->id_map[*id] = *id;
break;
default:
@@ -5997,7 +6359,7 @@ static int btf_add_distilled_type_ids(struct btf_distill *dist, __u32 i)
static int btf_add_distilled_types(struct btf_distill *dist)
{
- bool adding_to_base = dist->pipe.dst->start_id == 1;
+ bool adding_to_base = dist->pipe.dst->base_btf == NULL;
int id = btf__type_cnt(dist->pipe.dst);
struct btf_type *t;
int i, err = 0;
@@ -6065,8 +6427,12 @@ static int btf_add_distilled_types(struct btf_distill *dist)
case BTF_KIND_CONST:
case BTF_KIND_RESTRICT:
case BTF_KIND_VOLATILE:
+ case BTF_KIND_FUNC:
case BTF_KIND_FUNC_PROTO:
case BTF_KIND_TYPE_TAG:
+ case BTF_KIND_LOC_PARAM:
+ case BTF_KIND_LOC_PROTO:
+ case BTF_KIND_LOCSEC:
/* All other types are added to split BTF. */
if (adding_to_base)
continue;
diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h
index 587172c0de08..57f12630c5d1 100644
--- a/tools/lib/bpf/btf.h
+++ b/tools/lib/bpf/btf.h
@@ -274,6 +274,20 @@ LIBBPF_API int btf__add_decl_tag(struct btf *btf, const char *value, int ref_typ
LIBBPF_API int btf__add_decl_attr(struct btf *btf, const char *value, int ref_type_id,
int component_idx);
+/* location construction APIs */
+LIBBPF_API int btf__add_loc_param(struct btf *btf, __u32 size, __u32 flags);
+
+LIBBPF_API int btf__add_loc_param_value(struct btf *btf, __u32 value);
+
+LIBBPF_API int btf__add_loc_proto(struct btf *btf);
+
+LIBBPF_API int btf__add_loc_proto_param(struct btf *btf, __u32 id);
+
+LIBBPF_API int btf__add_locsec(struct btf *btf, const char *name);
+
+LIBBPF_API int btf__add_locsec_loc(struct btf *btf, __u32 func, __u32 loc_proto,
+ __u32 offset);
+
struct btf_dedup_opts {
size_t sz;
/* optional .BTF.ext info to dedup along the main BTF info */
@@ -431,6 +445,12 @@ btf_dump__dump_type_data(struct btf_dump *d, __u32 id,
#define BTF_KIND_DECL_TAG 17 /* Decl Tag */
#define BTF_KIND_TYPE_TAG 18 /* Type Tag */
#define BTF_KIND_ENUM64 19 /* Enum for up-to 64bit values */
+#define BTF_KIND_LOC_PARAM 20 /* Parameter at location */
+#define BTF_KIND_LOC_PROTO 21 /* Parameter set at location */
+#define BTF_KIND_LOCSEC 22 /* Section containing location info */
+
+struct btf_loc_param;
+struct btf_loc;
static inline __u16 btf_kind(const struct btf_type *t)
{
@@ -569,6 +589,21 @@ static inline bool btf_is_any_enum(const struct btf_type *t)
return btf_is_enum(t) || btf_is_enum64(t);
}
+static inline bool btf_is_loc_param(const struct btf_type *t)
+{
+ return btf_kind(t) == BTF_KIND_LOC_PARAM;
+}
+
+static inline bool btf_is_loc_proto(const struct btf_type *t)
+{
+ return btf_kind(t) == BTF_KIND_LOC_PROTO;
+}
+
+static inline bool btf_is_locsec(const struct btf_type *t)
+{
+ return btf_kind(t) == BTF_KIND_LOCSEC;
+}
+
static inline bool btf_kind_core_compat(const struct btf_type *t1,
const struct btf_type *t2)
{
@@ -683,6 +718,21 @@ static inline struct btf_decl_tag *btf_decl_tag(const struct btf_type *t)
return (struct btf_decl_tag *)(t + 1);
}
+static inline struct btf_loc_param *btf_loc_param(const struct btf_type *t)
+{
+ return (struct btf_loc_param *)(t + 1);
+}
+
+static inline __u32 *btf_loc_proto_params(const struct btf_type *t)
+{
+ return (__u32 *)(t + 1);
+}
+
+static inline struct btf_loc *btf_locsec_locs(const struct btf_type *t)
+{
+ return (struct btf_loc *)(t + 1);
+}
+
#ifdef __cplusplus
} /* extern "C" */
#endif
diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
index 123c448f20c7..fa995c02a170 100644
--- a/tools/lib/bpf/btf_dump.c
+++ b/tools/lib/bpf/btf_dump.c
@@ -328,6 +328,9 @@ static int btf_dump_mark_referenced(struct btf_dump *d)
case BTF_KIND_ENUM64:
case BTF_KIND_FWD:
case BTF_KIND_FLOAT:
+ case BTF_KIND_LOC_PARAM:
+ case BTF_KIND_LOC_PROTO:
+ case BTF_KIND_LOCSEC:
break;
case BTF_KIND_VOLATILE:
@@ -609,6 +612,9 @@ static int btf_dump_order_type(struct btf_dump *d, __u32 id, bool through_ptr)
case BTF_KIND_VAR:
case BTF_KIND_DATASEC:
case BTF_KIND_DECL_TAG:
+ case BTF_KIND_LOC_PARAM:
+ case BTF_KIND_LOC_PROTO:
+ case BTF_KIND_LOCSEC:
d->type_states[id].order_state = ORDERED;
return 0;
@@ -2525,6 +2531,9 @@ static int btf_dump_dump_type_data(struct btf_dump *d,
case BTF_KIND_FUNC:
case BTF_KIND_FUNC_PROTO:
case BTF_KIND_DECL_TAG:
+ case BTF_KIND_LOC_PARAM:
+ case BTF_KIND_LOC_PROTO:
+ case BTF_KIND_LOCSEC:
err = btf_dump_unsupported_data(d, t, id);
break;
case BTF_KIND_INT:
diff --git a/tools/lib/bpf/btf_iter.c b/tools/lib/bpf/btf_iter.c
index 9a6c822c2294..54428e422ec3 100644
--- a/tools/lib/bpf/btf_iter.c
+++ b/tools/lib/bpf/btf_iter.c
@@ -29,6 +29,7 @@ int btf_field_iter_init(struct btf_field_iter *it, struct btf_type *t,
case BTF_KIND_FLOAT:
case BTF_KIND_ENUM:
case BTF_KIND_ENUM64:
+ case BTF_KIND_LOC_PARAM:
it->desc = (struct btf_field_desc) {};
break;
case BTF_KIND_FWD:
@@ -71,6 +72,20 @@ int btf_field_iter_init(struct btf_field_iter *it, struct btf_type *t,
1, {offsetof(struct btf_var_secinfo, type)}
};
break;
+ case BTF_KIND_LOC_PROTO:
+ it->desc = (struct btf_field_desc) {
+ 0, {},
+ sizeof(__u32),
+ 1, {0}};
+ break;
+ case BTF_KIND_LOCSEC:
+ it->desc = (struct btf_field_desc) {
+ 0, {},
+ sizeof(struct btf_loc),
+ 2, {offsetof(struct btf_loc, func),
+ offsetof(struct btf_loc, loc_proto)}};
+ break;
+
default:
return -EINVAL;
}
@@ -94,6 +109,8 @@ int btf_field_iter_init(struct btf_field_iter *it, struct btf_type *t,
case BTF_KIND_DECL_TAG:
case BTF_KIND_TYPE_TAG:
case BTF_KIND_DATASEC:
+ case BTF_KIND_LOC_PARAM:
+ case BTF_KIND_LOC_PROTO:
it->desc = (struct btf_field_desc) {
1, {offsetof(struct btf_type, name_off)}
};
@@ -127,6 +144,11 @@ int btf_field_iter_init(struct btf_field_iter *it, struct btf_type *t,
1, {offsetof(struct btf_param, name_off)}
};
break;
+ case BTF_KIND_LOCSEC:
+ it->desc = (struct btf_field_desc) {
+ 1, {offsetof(struct btf_type, name_off)}
+ };
+ break;
default:
return -EINVAL;
}
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 08ab2ea881fb..7f51783df129 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -460,6 +460,12 @@ LIBBPF_1.8.0 {
global:
bpf_program__attach_tracing_multi;
bpf_program__clone;
+ btf__add_loc_param;
+ btf__add_loc_param_value;
+ btf__add_loc_proto;
+ btf__add_loc_proto_param;
+ btf__add_locsec;
+ btf__add_locsec_loc;
btf__find_by_name_kind_own;
btf__new_empty_opts;
} LIBBPF_1.7.0;
diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
index cb4d96233844..ebac8db1ccfd 100644
--- a/tools/lib/bpf/libbpf_internal.h
+++ b/tools/lib/bpf/libbpf_internal.h
@@ -580,7 +580,7 @@ struct btf_field_desc {
/* member struct size, or zero, if no members */
int m_sz;
/* repeated per-member offsets */
- int m_off_cnt, m_offs[1];
+ int m_off_cnt, m_offs[22];
};
struct btf_field_iter {
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 03/18] libbpf: Support moving permuted BTF types into split BTF
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
2026-09-01 16:57 ` [PATCH v2 bpf-next 01/18] btf: Extend UAPI to support BTF location (inline site) info Alan Maguire
2026-09-01 16:57 ` [PATCH v2 bpf-next 02/18] libbpf: Add support for BTF kinds LOC[_PARAM|_PROTO|SEC] Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:15 ` sashiko-bot
2026-09-01 18:14 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 04/18] selftests/bpf: Test helper support for BTF_KIND_LOC[_PARAM|_PROTO|SEC] Alan Maguire
` (14 subsequent siblings)
17 siblings, 2 replies; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Extend btf__permute() with an optional transfer mode. Type-map entries
marked with BTF_PERMUTE_ID_TRANSFER are removed from the input BTF and
copied into a newly-created split BTF returned through
btf_permute_opts.transfer_btf.
While moving types, remap type IDs for both retained and transferred
types, preserving split-BTF references to the parent BTF where needed.
String handling has to be done carefully as we do not want to have
strings that only exist in the split BTF to impose a cost on the base.
Copy referenced strings locally first, compact the parent string table
after the moved types are gone, rebase split-string offsets, and compact
the split string table. This leaves strings exclusively needed by moved
types in the split BTF while retaining shared strings in the parent.
This will be important later for function names of inline-only functions;
these should not take up space in base BTF but should have strings in
inline BTF only.
This all provides the support needed to place inline-only BTF
in a separate BTF section, which resolve_btfids will use later.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Assisted-by: Codex (GPT-5)
---
tools/lib/bpf/btf.c | 285 +++++++++++++++++++++++++++++++++++++++-----
tools/lib/bpf/btf.h | 16 ++-
2 files changed, 271 insertions(+), 30 deletions(-)
diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
index 9449f6f50f18..30f8c426d145 100644
--- a/tools/lib/bpf/btf.c
+++ b/tools/lib/bpf/btf.c
@@ -2147,16 +2147,9 @@ int btf__find_str(struct btf *btf, const char *s)
* - > 0 offset into string section, on success;
* - < 0, on error.
*/
-int btf__add_str(struct btf *btf, const char *s)
+static int btf_add_local_str(struct btf *btf, const char *s)
{
- int off;
- int err;
-
- if (btf->base_btf) {
- off = btf__find_str(btf->base_btf, s);
- if (off != -ENOENT)
- return off;
- }
+ int off, err;
err = btf_ensure_modifiable(btf);
if (err)
@@ -2167,10 +2160,24 @@ int btf__add_str(struct btf *btf, const char *s)
return libbpf_err(off);
btf->hdr.str_len = strset__data_size(btf->strs_set);
+ btf->strs_deduped = false;
return btf->start_str_off + off;
}
+int btf__add_str(struct btf *btf, const char *s)
+{
+ int off;
+
+ if (btf->base_btf) {
+ off = btf__find_str(btf->base_btf, s);
+ if (off != -ENOENT)
+ return off;
+ }
+
+ return btf_add_local_str(btf, s);
+}
+
static void *btf_add_type_mem(struct btf *btf, size_t add_sz)
{
return libbpf_add_mem(&btf->types_data, &btf->types_data_cap, 1,
@@ -2218,6 +2225,7 @@ struct btf_pipe {
const struct btf *src;
struct btf *dst;
struct hashmap *str_off_map; /* map string offsets from src to dst */
+ bool force_local_strs;
};
static int btf_rewrite_str(struct btf_pipe *p, __u32 *str_off)
@@ -2234,7 +2242,10 @@ static int btf_rewrite_str(struct btf_pipe *p, __u32 *str_off)
return 0;
}
- off = btf__add_str(p->dst, btf__str_by_offset(p->src, *str_off));
+ if (p->force_local_strs)
+ off = btf_add_local_str(p->dst, btf__str_by_offset(p->src, *str_off));
+ else
+ off = btf__add_str(p->dst, btf__str_by_offset(p->src, *str_off));
if (off < 0)
return off;
@@ -4513,6 +4524,19 @@ static int btf_dedup_strings(struct btf_dedup *d)
return err;
}
+/* Compact strings after a type-only transformation such as btf__permute(). */
+static int btf_compact_strings(struct btf *btf, struct btf_ext *btf_ext)
+{
+ struct btf_dedup d = {
+ .btf = btf,
+ .btf_ext = btf_ext,
+ };
+
+ /* Types may have been removed since the last string compaction. */
+ btf->strs_deduped = false;
+ return btf_dedup_strings(&d);
+}
+
/*
* Calculate type signature hash of TYPEDEF, ignoring referenced type IDs,
* as referenced type IDs equivalence is established separately during type
@@ -6617,8 +6641,15 @@ struct btf_permute {
struct btf *btf;
__u32 *id_map;
__u32 start_offs;
+ __u32 transfer_start_id;
+ bool reject_transfer;
};
+static bool btf_permute_id_is_transfer(__u32 id)
+{
+ return id & BTF_PERMUTE_ID_TRANSFER;
+}
+
/* Callback function to remap individual type ID references */
static int btf_permute_remap_type_id(__u32 *type_id, void *ctx)
{
@@ -6633,18 +6664,54 @@ static int btf_permute_remap_type_id(__u32 *type_id, void *ctx)
return -EINVAL;
*type_id = p->id_map[new_id - p->btf->start_id + p->start_offs];
+ if (p->reject_transfer && *type_id >= p->transfer_start_id)
+ return -EINVAL;
+ return 0;
+}
+
+/*
+ * String offsets in a split BTF are absolute. If its parent string table is
+ * compacted after the split was populated, move its local string offsets to
+ * start at the parent's new end.
+ */
+static int btf_permute_rebase_split_strs(struct btf *split_btf, __u32 old_start_str_off)
+{
+ __s64 delta = split_btf->base_btf->start_str_off + split_btf->base_btf->hdr.str_len -
+ old_start_str_off;
+ int i;
+
+ for (i = 0; i < split_btf->nr_types; i++) {
+ struct btf_field_iter it;
+ struct btf_type *t = btf_type_by_id(split_btf,
+ split_btf->start_id + i);
+ __u32 *str_off;
+ int err;
+
+ err = btf_field_iter_init(&it, t, BTF_FIELD_ITER_STRS);
+ if (err)
+ return err;
+ while ((str_off = btf_field_iter_next(&it))) {
+ if (*str_off >= old_start_str_off)
+ *str_off += delta;
+ }
+ }
+
+ btf_set_base_btf(split_btf, split_btf->base_btf);
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;
+ __u32 n, start_offs = 0, nr_transfer = 0, nr_base_types;
+ __u32 next_base_id, next_transfer_id, old_str_off = 0;
+ struct btf **transfer_btfp, *transfer_btf = NULL;
+ __u32 *new_id_map = NULL, *order_map = NULL;
void *nt, *new_types = NULL;
- __u32 *order_map = NULL;
+ struct btf_ext *btf_ext;
+ bool committed = false;
+ struct btf_permute p;
int err = 0, i;
- __u32 n, id, start_offs = 0;
if (!OPTS_VALID(opts, btf_permute_opts))
return libbpf_err(-EINVAL);
@@ -6661,7 +6728,21 @@ int btf__permute(struct btf *btf, __u32 *id_map, __u32 id_map_cnt,
if (id_map_cnt != n)
return libbpf_err(-EINVAL);
- /* record the sequence of types */
+ transfer_btfp = OPTS_GET(opts, transfer_btf, NULL);
+ if (transfer_btfp)
+ *transfer_btfp = NULL;
+
+ /*
+ * Make a working copy of the id map; it will be modified (such as
+ * removing transfer flags) unlike the caller-passed original.
+ */
+ new_id_map = calloc(id_map_cnt, sizeof(*new_id_map));
+ if (!new_id_map) {
+ err = -ENOMEM;
+ goto done;
+ }
+ memcpy(new_id_map, id_map, id_map_cnt * sizeof(*new_id_map));
+
order_map = calloc(id_map_cnt, sizeof(*id_map));
if (!order_map) {
err = -ENOMEM;
@@ -6678,33 +6759,82 @@ int btf__permute(struct btf *btf, __u32 *id_map, __u32 id_map_cnt,
if (err)
goto done;
+ /*
+ * Build the inverse map: for each requested destination ID, record the
+ * original type ID that will occupy it. order_map is indexed by the
+ * destination ID relative to the first ID represented by id_map. The
+ * caller id_map maps old IDs to requested destinations, but BTF types
+ * must be emitted in destination-ID order; the inverse map supplies the
+ * old type to copy for each output slot without repeatedly searching
+ * id_map.
+ */
for (i = start_offs; i < id_map_cnt; i++) {
- id = id_map[i];
- if (id < btf->start_id || id >= btf__type_cnt(btf)) {
+ __u32 requested_id = new_id_map[i];
+ __u32 order_idx;
+
+ if (btf_permute_id_is_transfer(requested_id)) {
+ if (!transfer_btfp) {
+ err = -EINVAL;
+ goto done;
+ }
+ requested_id &= ~BTF_PERMUTE_ID_TRANSFER;
+ nr_transfer++;
+ }
+ if (requested_id < btf->start_id || requested_id >= btf__type_cnt(btf)) {
err = -EINVAL;
goto done;
}
- id -= btf->start_id - start_offs;
+ order_idx = requested_id - btf->start_id + start_offs;
/* cannot be mapped to the same ID */
- if (order_map[id]) {
+ if (order_map[order_idx]) {
err = -EINVAL;
goto done;
}
- order_map[id] = i + btf->start_id - start_offs;
+ order_map[order_idx] = i + btf->start_id - start_offs;
+ }
+
+ /*
+ * Turn requested destination IDs into final IDs. Retained types
+ * occupy the compacted parent BTF first; transferred types immediately
+ * follow them, which is where they will start in returned split BTF.
+ */
+ nr_base_types = n - nr_transfer - start_offs;
+ next_base_id = btf->start_id;
+ next_transfer_id = btf->start_id + nr_base_types;
+ for (i = start_offs; i < id_map_cnt; i++) {
+ __u32 old_id = order_map[i];
+ __u32 old_id_idx = old_id - btf->start_id + start_offs;
+
+ if (btf_permute_id_is_transfer(new_id_map[old_id_idx]))
+ new_id_map[old_id_idx] = next_transfer_id++;
+ else
+ new_id_map[old_id_idx] = next_base_id++;
}
+ /*
+ * Copy retained types and reject references into the split (transfer)
+ * BTF from the original BTF.
+ */
p.btf = btf;
- p.id_map = id_map;
+ p.id_map = new_id_map;
p.start_offs = start_offs;
+ p.transfer_start_id = btf->start_id + nr_base_types;
+ p.reject_transfer = true;
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;
+ __u32 old_id;
+ __u32 old_id_idx;
int type_size;
- id = order_map[i];
- t = btf__type_by_id(btf, id);
+ old_id = order_map[i];
+ old_id_idx = old_id - btf->start_id + start_offs;
+
+ if (new_id_map[old_id_idx] >= btf->start_id + nr_base_types)
+ continue;
+ t = btf__type_by_id(btf, old_id);
type_size = btf_type_size(btf, t);
memcpy(nt, t, type_size);
@@ -6714,14 +6844,71 @@ int btf__permute(struct btf *btf, __u32 *id_map, __u32 id_map_cnt,
goto done;
while ((type_id = btf_field_iter_next(&it))) {
err = btf_permute_remap_type_id(type_id, &p);
- if (err)
+ if (err < 0)
goto done;
}
nt += type_size;
}
- /* fix up referenced IDs for btf_ext */
+ /*
+ * Copy transferred types into a split BTF. They can reference both
+ * retained parent types and other transferred types, so do not reject
+ * final IDs in the split-ID range while rewriting their references.
+ */
+ if (nr_transfer) {
+ struct btf_permute transfer_p;
+
+ transfer_btf = btf__new_empty_split(btf);
+ if (!transfer_btf) {
+ err = -errno;
+ goto done;
+ }
+ old_str_off = transfer_btf->start_str_off;
+
+ transfer_p = p;
+ /* Split transfer types may refer to other split types */
+ transfer_p.reject_transfer = false;
+ for (i = start_offs; i < id_map_cnt; i++) {
+ struct btf_field_iter it;
+ struct btf_pipe pipe = {
+ .src = btf,
+ .dst = transfer_btf,
+ .force_local_strs = true,
+ };
+ const struct btf_type *t;
+ struct btf_type *new_t;
+ __u32 *type_id;
+ __u32 old_id;
+ __u32 old_id_idx;
+ int new_id;
+
+ old_id = order_map[i];
+ old_id_idx = old_id - btf->start_id + start_offs;
+
+ if (!btf_permute_id_is_transfer(id_map[old_id_idx]))
+ continue;
+ t = btf__type_by_id(btf, old_id);
+ new_id = btf_add_type(&pipe, t);
+ if (new_id < 0) {
+ err = new_id;
+ goto done;
+ }
+ new_t = btf_type_by_id(transfer_btf, new_id);
+ err = btf_field_iter_init(&it, new_t, BTF_FIELD_ITER_IDS);
+ if (!err) {
+ while ((type_id = btf_field_iter_next(&it))) {
+ err = btf_permute_remap_type_id(type_id, &transfer_p);
+ if (err)
+ break;
+ }
+ }
+ if (err < 0)
+ goto done;
+ }
+ }
+
+ /* Rewrite optional .BTF.ext references to retained type IDs. */
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);
@@ -6729,18 +6916,58 @@ int btf__permute(struct btf *btf, __u32 *id_map, __u32 id_map_cnt,
goto done;
}
- for (nt = new_types, i = 0; i < id_map_cnt - start_offs; i++) {
+ /* Install the compacted parent types and rebuild their index. */
+ for (nt = new_types, i = 0; i < nr_base_types; i++) {
btf->type_offs[i] = nt - new_types;
nt += btf_type_size(btf, nt);
}
-
- free(order_map);
free(btf->types_data);
btf->types_data = new_types;
+ committed = true;
+ btf_hdr_update_type_len(btf, nt - new_types);
+ btf->nr_types = nr_base_types;
+
+ /*
+ * An important goal is to also transfer strings to the transfer
+ * BTF where they do not have duplicates in BTF. This is important
+ * for cases like inline BTF where inline function names could
+ * comprise a significant portion of the string table. The approach
+ * is to first compact the set of strings in the base now transfer
+ * is complete; this will remove unreferenced strings. Then
+ * local references in the transfer BTF have to be moved downwards
+ * based upon that compaction. Finally also compact transfer BTF
+ * references so that we replace duplicate references in split with
+ * base references where present.
+ *
+ * Once all this is done, we end up with transfer-only strings in
+ * transfer split BTF and shared strings in BTF. The result is
+ * a transfer BTF with a lot of transfer-only strings will not
+ * pollute the string table of the non-transfer BTF.
+ */
+ if (transfer_btf) {
+ err = btf_compact_strings(btf, btf_ext);
+ if (err)
+ goto done;
+ err = btf_permute_rebase_split_strs(transfer_btf, old_str_off);
+ if (err)
+ goto done;
+ err = btf_compact_strings(transfer_btf, NULL);
+ if (err)
+ goto done;
+ *transfer_btfp = transfer_btf;
+ }
+
+ /* On success return the final old-to-new type-ID map to the caller. */
+ memcpy(id_map, new_id_map, id_map_cnt * sizeof(*id_map));
+ free(new_id_map);
+ free(order_map);
return 0;
done:
+ free(new_id_map);
free(order_map);
- free(new_types);
+ if (!committed)
+ free(new_types);
+ btf__free(transfer_btf);
return libbpf_err(err);
}
diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h
index 57f12630c5d1..8ce569755773 100644
--- a/tools/lib/bpf/btf.h
+++ b/tools/lib/bpf/btf.h
@@ -321,9 +321,17 @@ struct btf_permute_opts {
size_t sz;
/* optional .BTF.ext info along the main BTF info */
struct btf_ext *btf_ext;
+ /*
+ * If set, types whose map entry has BTF_PERMUTE_ID_TRANSFER set are
+ * removed from @btf and returned in a new split BTF based on @btf.
+ */
+ struct btf **transfer_btf;
size_t :0;
};
-#define btf_permute_opts__last_field btf_ext
+#define btf_permute_opts__last_field transfer_btf
+
+/* Mark an id_map entry as a type to be moved to .transfer_btf. */
+#define BTF_PERMUTE_ID_TRANSFER (1U << 31)
/**
* @brief **btf__permute()** rearranges BTF types in-place according to a specified ID mapping
@@ -349,6 +357,12 @@ struct btf_permute_opts {
* - Mapping is defined as `id_map[original_id - start_id] = new_id`
* - `start_id` equals `btf__type_cnt(btf__base_btf(btf))`
*
+ * An @id_map entry can be ORed with BTF_PERMUTE_ID_TRANSFER to move that
+ * type to a newly-created split BTF returned through @opts->transfer_btf.
+ * The low bits still specify the type's position in the requested ordering.
+ * The id map is updated to contain the final IDs in the base and returned
+ * split BTF.
+ *
* After permutation, all type references within the BTF data and optional
* BTF extension (if provided via @opts) are updated automatically.
*
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 04/18] selftests/bpf: Test helper support for BTF_KIND_LOC[_PARAM|_PROTO|SEC]
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (2 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 03/18] libbpf: Support moving permuted BTF types into split BTF Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:06 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 05/18] selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to field iter tests Alan Maguire
` (13 subsequent siblings)
17 siblings, 1 reply; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Add support to dump, encode and validate new location-related kinds.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
tools/testing/selftests/bpf/btf_helpers.c | 36 ++++++++++++++++++++++-
tools/testing/selftests/bpf/test_btf.h | 16 ++++++++++
2 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/bpf/btf_helpers.c b/tools/testing/selftests/bpf/btf_helpers.c
index 1c1c2c26690a..6065ee4ba00a 100644
--- a/tools/testing/selftests/bpf/btf_helpers.c
+++ b/tools/testing/selftests/bpf/btf_helpers.c
@@ -27,11 +27,14 @@ static const char * const btf_kind_str_mapping[] = {
[BTF_KIND_DECL_TAG] = "DECL_TAG",
[BTF_KIND_TYPE_TAG] = "TYPE_TAG",
[BTF_KIND_ENUM64] = "ENUM64",
+ [BTF_KIND_LOC_PARAM] = "LOC_PARAM",
+ [BTF_KIND_LOC_PROTO] = "LOC_PROTO",
+ [BTF_KIND_LOCSEC] = "LOCSEC",
};
static const char *btf_kind_str(__u16 kind)
{
- if (kind > BTF_KIND_ENUM64)
+ if (kind > BTF_KIND_LOCSEC)
return "UNKNOWN";
return btf_kind_str_mapping[kind];
}
@@ -203,6 +206,37 @@ int fprintf_btf_type_raw(FILE *out, const struct btf *btf, __u32 id)
fprintf(out, " type_id=%u component_idx=%d",
t->type, btf_decl_tag(t)->component_idx);
break;
+ case BTF_KIND_LOC_PARAM: {
+ struct btf_loc_param *p = btf_loc_param(t);
+ __u32 *v = (__u32 *)(p + 1);
+
+ fprintf(out, " size=%d flags=0x%x vlen=%u", t->size, p->flags, vlen);
+ for (i = 0; i < vlen; i++, v++) {
+ if (p->flags & BTF_LOC_PARAM_SIGNED)
+ fprintf(out, "\n\tvalue=%d", (__s32)*v);
+ else
+ fprintf(out, "\n\tvalue=%u", *v);
+ }
+ break;
+ }
+ case BTF_KIND_LOC_PROTO: {
+ const __u32 *p = btf_loc_proto_params(t);
+
+ fprintf(out, " vlen=%u", vlen);
+ for (i = 0; i < vlen; i++, p++)
+ fprintf(out, "\n\ttype_id=%u", *p);
+ break;
+ }
+ case BTF_KIND_LOCSEC: {
+ const struct btf_loc *l = btf_locsec_locs(t);
+
+ fprintf(out, " vlen=%u", vlen);
+ for (i = 0; i < vlen; i++, l++) {
+ fprintf(out, "\n\tfunc_type_id=%u loc_proto_type_id=%u offset=%d",
+ l->func, l->loc_proto, l->offset);
+ }
+ break;
+ }
default:
break;
}
diff --git a/tools/testing/selftests/bpf/test_btf.h b/tools/testing/selftests/bpf/test_btf.h
index e7bc78108374..b99c04b4d26b 100644
--- a/tools/testing/selftests/bpf/test_btf.h
+++ b/tools/testing/selftests/bpf/test_btf.h
@@ -84,4 +84,20 @@
#define BTF_TYPE_TAG_ENC(value, type) \
BTF_TYPE_ENC(value, BTF_INFO_ENC(BTF_KIND_TYPE_TAG, 0, 0), type)
+#define BTF_LOC_PARAM_ENC(nvals, sz, flags) \
+ BTF_TYPE_ENC(0, BTF_INFO_ENC(BTF_KIND_LOC_PARAM, 0, nvals), sz), (flags)
+
+#define BTF_LOC_PARAM_VAL_ENCODE(val) (val)
+
+#define BTF_LOC_PROTO_ENC(nargs) \
+ BTF_TYPE_ENC(0, BTF_INFO_ENC(BTF_KIND_LOC_PROTO, 0, nargs), 0)
+
+#define BTF_LOC_PROTO_PARAM_ENCODE(param) (param)
+
+#define BTF_LOCSEC_ENC(name, nlocs) \
+ BTF_TYPE_ENC(name, BTF_INFO_ENC(BTF_KIND_LOCSEC, 0, nlocs), 0)
+
+#define BTF_LOCSEC_LOC_ENCODE(func, loc_proto, offset) \
+ (func), (loc_proto), (offset)
+
#endif /* _TEST_BTF_H */
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 05/18] selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to field iter tests
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (3 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 04/18] selftests/bpf: Test helper support for BTF_KIND_LOC[_PARAM|_PROTO|SEC] Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 16:57 ` [PATCH v2 bpf-next 06/18] selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to dedup split tests Alan Maguire
` (12 subsequent siblings)
17 siblings, 0 replies; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
BTF_KIND_LOC[_PARAM|_PROTO|SEC] need to work with field iteration, so
extend the selftest to cover these and ensure iteration over all types
and names succeeds.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
.../selftests/bpf/prog_tests/btf_field_iter.c | 31 +++++++++++++++++--
1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_field_iter.c b/tools/testing/selftests/bpf/prog_tests/btf_field_iter.c
index 32159d3eb281..dcb5429d141d 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_field_iter.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_field_iter.c
@@ -31,8 +31,11 @@ struct field_data {
{ .ids = { 11 }, .strs = { "decltag" } },
{ .ids = { 6 }, .strs = { "typetag" } },
{ .ids = {}, .strs = { "e64", "eval1", "eval2", "eval3" } },
- { .ids = { 15, 16 }, .strs = { "datasec1" } }
-
+ { .ids = { 15, 16 }, .strs = { "datasec1" } },
+ { .ids = {}, .strs = { "" } },
+ { .ids = {}, .strs = { "" } },
+ { .ids = { 22, 23 }, .strs = { "" } },
+ { .ids = { 14, 24 }, .strs = { ".loc" } }
};
/* Fabricate BTF with various types and check BTF field iteration finds types,
@@ -88,6 +91,19 @@ void test_btf_field_iter(void)
btf__add_datasec_var_info(btf, 15, 0, 4);
btf__add_datasec_var_info(btf, 16, 4, 8);
+ btf__add_loc_param(btf, 4, BTF_LOC_PARAM_CONST | BTF_LOC_PARAM_SIGNED);
+ /* [22] loc value -1 */
+ btf__add_loc_param_value(btf, -1);
+ btf__add_loc_param(btf, 8, BTF_LOC_PARAM_REG); /* [23] loc reg 1 */
+ btf__add_loc_param_value(btf, 1);
+
+ btf__add_loc_proto(btf); /* [24] loc proto */
+ btf__add_loc_proto_param(btf, 22); /* param value -1, */
+ btf__add_loc_proto_param(btf, 23); /* param reg 1 */
+
+ btf__add_locsec(btf, ".loc"); /* [25] locsec ".loc" */
+ btf__add_locsec_loc(btf, 14, 24, 128); /* "func" */
+
VALIDATE_RAW_BTF(
btf,
"[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
@@ -123,7 +139,16 @@ void test_btf_field_iter(void)
"\t'eval3' val=3000",
"[21] DATASEC 'datasec1' size=12 vlen=2\n"
"\ttype_id=15 offset=0 size=4\n"
- "\ttype_id=16 offset=4 size=8");
+ "\ttype_id=16 offset=4 size=8",
+ "[22] LOC_PARAM '(anon)' size=4 flags=0x3 vlen=1\n"
+ "\tvalue=-1",
+ "[23] LOC_PARAM '(anon)' size=8 flags=0x8 vlen=1\n"
+ "\tvalue=1",
+ "[24] LOC_PROTO '(anon)' vlen=2\n"
+ "\ttype_id=22\n"
+ "\ttype_id=23",
+ "[25] LOCSEC '.loc' vlen=1\n"
+ "\tfunc_type_id=14 loc_proto_type_id=24 offset=128");
for (id = 1; id < btf__type_cnt(btf); id++) {
struct btf_type *t = btf_type_by_id(btf, id);
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 06/18] selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to dedup split tests
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (4 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 05/18] selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to field iter tests Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 07/18] selftests/bpf: BTF distill tests to ensure LOC[_PARAM|_PROTO] add to split BTF Alan Maguire
` (11 subsequent siblings)
17 siblings, 1 reply; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Ensure that location params/protos are deduplicated and location
sections are not, and that references to deduplicated locations within
location prototypes and sections are updated after deduplication.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
.../bpf/prog_tests/btf_dedup_split.c | 109 ++++++++++++++++++
1 file changed, 109 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c b/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c
index 9d6161151593..34ca20f88afd 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c
@@ -554,6 +554,113 @@ static void test_split_module(void)
btf__free(vmlinux_btf);
}
+static void test_split_loc(void)
+{
+ struct btf *btf1, *btf2;
+ int err;
+
+ btf1 = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf1, "empty_main_btf"))
+ return;
+
+ btf__set_pointer_size(btf1, 8); /* enforce 64-bit arch */
+
+
+ btf__add_int(btf1, "long", 8, BTF_INT_SIGNED); /* [1] long */
+ btf__add_ptr(btf1, 1); /* [2] ptr to long */
+ btf__add_func_proto(btf1, 1); /* [3] long (*)(long, long *); */
+ btf__add_func_param(btf1, "p1", 1);
+ btf__add_func_param(btf1, "p2", 2);
+ btf__add_func(btf1, "foo", BTF_FUNC_STATIC, 3); /* [4] long foo(long, long *); */
+ btf__add_loc_param(btf1, 8, BTF_LOC_PARAM_CONST);
+ btf__add_loc_param_value(btf1, 3735928559);
+ btf__add_loc_param_value(btf1, 4277009102); /* [5] loc value */
+ btf__add_loc_param(btf1, 8, BTF_LOC_PARAM_REG); /* [6] loc reg 1 */
+ btf__add_loc_param_value(btf1, 1);
+ btf__add_loc_proto(btf1); /* [7] loc proto */
+ btf__add_loc_proto_param(btf1, 5); /* param value */
+ btf__add_loc_proto_param(btf1, 6); /* param reg 1 */
+
+ VALIDATE_RAW_BTF(
+ btf1,
+ "[1] INT 'long' size=8 bits_offset=0 nr_bits=64 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] FUNC_PROTO '(anon)' ret_type_id=1 vlen=2\n"
+ "\t'p1' type_id=1\n"
+ "\t'p2' type_id=2",
+ "[4] FUNC 'foo' type_id=3 linkage=static",
+ "[5] LOC_PARAM '(anon)' size=8 flags=0x2 vlen=2\n"
+ "\tvalue=3735928559\n"
+ "\tvalue=4277009102",
+ "[6] LOC_PARAM '(anon)' size=8 flags=0x8 vlen=1\n"
+ "\tvalue=1",
+ "[7] LOC_PROTO '(anon)' vlen=2\n"
+ "\ttype_id=5\n"
+ "\ttype_id=6");
+
+ btf2 = btf__new_empty_split(btf1);
+ if (!ASSERT_OK_PTR(btf2, "empty_split_btf"))
+ goto cleanup;
+ btf__add_loc_param(btf2, 8, BTF_LOC_PARAM_REG);
+ btf__add_loc_param_value(btf2, 1); /* [8] loc reg 1 */
+ btf__add_loc_proto(btf2); /* [9] loc proto */
+ btf__add_loc_proto_param(btf2, 5); /* param value */
+ btf__add_loc_proto_param(btf2, 8); /* param reg 1 */
+ btf__add_locsec(btf2, ".locs"); /* [9] locsec ".locs" */
+ btf__add_locsec_loc(btf2, 4, 9, 128);
+
+ VALIDATE_RAW_BTF(
+ btf2,
+ "[1] INT 'long' size=8 bits_offset=0 nr_bits=64 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] FUNC_PROTO '(anon)' ret_type_id=1 vlen=2\n"
+ "\t'p1' type_id=1\n"
+ "\t'p2' type_id=2",
+ "[4] FUNC 'foo' type_id=3 linkage=static",
+ "[5] LOC_PARAM '(anon)' size=8 flags=0x2 vlen=2\n"
+ "\tvalue=3735928559\n"
+ "\tvalue=4277009102",
+ "[6] LOC_PARAM '(anon)' size=8 flags=0x8 vlen=1\n"
+ "\tvalue=1",
+ "[7] LOC_PROTO '(anon)' vlen=2\n"
+ "\ttype_id=5\n"
+ "\ttype_id=6",
+ "[8] LOC_PARAM '(anon)' size=8 flags=0x8 vlen=1\n"
+ "\tvalue=1",
+ "[9] LOC_PROTO '(anon)' vlen=2\n"
+ "\ttype_id=5\n"
+ "\ttype_id=8",
+ "[10] LOCSEC '.locs' vlen=1\n"
+ "\tfunc_type_id=4 loc_proto_type_id=9 offset=128");
+
+ err = btf__dedup(btf2, NULL);
+ if (!ASSERT_OK(err, "btf_dedup"))
+ goto cleanup;
+
+ VALIDATE_RAW_BTF(
+ btf2,
+ "[1] INT 'long' size=8 bits_offset=0 nr_bits=64 encoding=SIGNED",
+ "[2] PTR '(anon)' type_id=1",
+ "[3] FUNC_PROTO '(anon)' ret_type_id=1 vlen=2\n"
+ "\t'p1' type_id=1\n"
+ "\t'p2' type_id=2",
+ "[4] FUNC 'foo' type_id=3 linkage=static",
+ "[5] LOC_PARAM '(anon)' size=8 flags=0x2 vlen=2\n"
+ "\tvalue=3735928559\n"
+ "\tvalue=4277009102",
+ "[6] LOC_PARAM '(anon)' size=8 flags=0x8 vlen=1\n"
+ "\tvalue=1",
+ "[7] LOC_PROTO '(anon)' vlen=2\n"
+ "\ttype_id=5\n"
+ "\ttype_id=6",
+ "[8] LOCSEC '.locs' vlen=1\n"
+ "\tfunc_type_id=4 loc_proto_type_id=7 offset=128");
+
+cleanup:
+ btf__free(btf2);
+ btf__free(btf1);
+}
+
void test_btf_dedup_split()
{
if (test__start_subtest("split_simple"))
@@ -566,4 +673,6 @@ void test_btf_dedup_split()
test_split_dup_struct_in_cu();
if (test__start_subtest("split_module"))
test_split_module();
+ if (test__start_subtest("split_loc"))
+ test_split_loc();
}
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 07/18] selftests/bpf: BTF distill tests to ensure LOC[_PARAM|_PROTO] add to split BTF
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (5 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 06/18] selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to dedup split tests Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 08/18] selftests/bpf: Validate that btf__permute transfer works Alan Maguire
` (10 subsequent siblings)
17 siblings, 1 reply; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
When creating distilled BTF, BTF_KIND_FUNC, _LOC_PARAM and _LOC_PROTO
should be added to split BTF. This means potentially some duplication
of location information, but only for out-of-tree modules that use
distilled base/split BTF.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
.../selftests/bpf/prog_tests/btf_distill.c | 76 +++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_distill.c b/tools/testing/selftests/bpf/prog_tests/btf_distill.c
index fb67ae195a73..f433b29ffce9 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_distill.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_distill.c
@@ -671,6 +671,80 @@ static void test_distilled_base_embedded_err(void)
btf__free(btf1);
}
+/* LOC_PARAM, LOC_PROTO should be added to split BTF. */
+static void test_distilled_loc(void)
+{
+ struct btf *btf1 = NULL, *btf2 = NULL, *btf3 = NULL, *btf4 = NULL;
+
+ btf1 = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf1, "empty_main_btf"))
+ return;
+
+ btf__add_int(btf1, "int", 4, BTF_INT_SIGNED); /* [1] int */
+ btf__add_func_proto(btf1, 1); /* [2] int (*)(int); */
+ btf__add_func_param(btf1, "p1", 1);
+ btf__add_func(btf1, "foo", BTF_FUNC_STATIC, 2); /* [3] int foo(int); */
+ btf__add_loc_param(btf1, 4, BTF_LOC_PARAM_SIGNED | BTF_LOC_PARAM_CONST);
+ btf__add_loc_param_value(btf1, -1); /* [4] loc value */
+
+ VALIDATE_RAW_BTF(
+ btf1,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p1' type_id=1",
+ "[3] FUNC 'foo' type_id=2 linkage=static",
+ "[4] LOC_PARAM '(anon)' size=4 flags=0x3 vlen=1\n"
+ "\tvalue=-1");
+
+ btf2 = btf__new_empty_split(btf1);
+ if (!ASSERT_OK_PTR(btf2, "empty_split_btf"))
+ goto cleanup;
+
+ btf__add_loc_proto(btf2); /* [5] loc proto */
+ btf__add_loc_proto_param(btf2, 4); /* param value */
+
+ btf__add_locsec(btf2, ".locs"); /* [6] locsec */
+ btf__add_locsec_loc(btf2, 3, 5, 256); /* "foo" offset 256 */
+ VALIDATE_RAW_BTF(
+ btf2,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p1' type_id=1",
+ "[3] FUNC 'foo' type_id=2 linkage=static",
+ "[4] LOC_PARAM '(anon)' size=4 flags=0x3 vlen=1\n"
+ "\tvalue=-1",
+ "[5] LOC_PROTO '(anon)' vlen=1\n"
+ "\ttype_id=4",
+ "[6] LOCSEC '.locs' vlen=1\n"
+ "\tfunc_type_id=3 loc_proto_type_id=5 offset=256");
+
+ if (!ASSERT_EQ(0, btf__distill_base(btf2, &btf3, &btf4),
+ "distilled_base") ||
+ !ASSERT_OK_PTR(btf3, "distilled_base") ||
+ !ASSERT_OK_PTR(btf4, "distilled_split") ||
+ !ASSERT_EQ(2, btf__type_cnt(btf3), "distilled_base_type_cnt"))
+ goto cleanup;
+
+ VALIDATE_RAW_BTF(
+ btf4,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ /* remainder is split BTF */
+ "[2] LOC_PROTO '(anon)' vlen=1\n"
+ "\ttype_id=6",
+ "[3] LOCSEC '.locs' vlen=1\n"
+ "\tfunc_type_id=5 loc_proto_type_id=2 offset=256",
+ "[4] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
+ "\t'p1' type_id=1",
+ "[5] FUNC 'foo' type_id=4 linkage=static",
+ "[6] LOC_PARAM '(anon)' size=4 flags=0x3 vlen=1\n"
+ "\tvalue=-1");
+cleanup:
+ btf__free(btf4);
+ btf__free(btf3);
+ btf__free(btf2);
+ btf__free(btf1);
+}
+
void test_btf_distill(void)
{
if (test__start_subtest("distilled_base"))
@@ -689,4 +763,6 @@ void test_btf_distill(void)
test_distilled_base_vmlinux();
if (test__start_subtest("distilled_endianness"))
test_distilled_endianness();
+ if (test__start_subtest("distilled_loc"))
+ test_distilled_loc();
}
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 08/18] selftests/bpf: Validate that btf__permute transfer works
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (6 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 07/18] selftests/bpf: BTF distill tests to ensure LOC[_PARAM|_PROTO] add to split BTF Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:16 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 09/18] bpftool: Handle multi-split BTF by supporting multiple base BTFs Alan Maguire
` (9 subsequent siblings)
17 siblings, 2 replies; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Add coverage for btf__permute() transfer mode, including type-ID
remapping and LOCSEC record ordering in the resulting split BTF.
Verify string handling for both cases: a string used only by transferred
types is removed from the base BTF and deduplicated locally in the split
BTF, while a string also referenced by a retained base type remains a
base-string reference.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
.../selftests/bpf/prog_tests/btf_permute.c | 128 ++++++++++++++++++
1 file changed, 128 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_permute.c b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
index 04ade5ad77ac..ef75fd71df96 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_permute.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
@@ -235,10 +235,138 @@ static void test_permute_split(void)
btf__free(base_btf);
}
+/* Ensure selected types can be moved into a split BTF while permuting. */
+static void test_permute_transfer(void)
+{
+ struct btf *btf = NULL, *transfer_btf = NULL;
+ LIBBPF_OPTS(btf_permute_opts, opts);
+ __u32 permute_ids[11];
+ int err, id, foo_name_off = -1, shared_name_off, foo_funcs = 0, shared_funcs = 0;
+
+ 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_func_proto(btf, 1); /* [2] int (*)(void) */
+ btf__add_func(btf, "foo", BTF_FUNC_STATIC, 2); /* [3] int foo(void) */
+ btf__add_loc_param(btf, 4, BTF_LOC_PARAM_SIGNED); /* [4] location */
+ btf__add_loc_param_value(btf, 0);
+ btf__add_loc_proto(btf); /* [5] location proto */
+ btf__add_loc_proto_param(btf, 4);
+ btf__add_locsec(btf, ".locs"); /* [6] location section */
+ btf__add_locsec_loc(btf, 3, 5, 128);
+ btf__add_locsec_loc(btf, 3, 5, 0);
+ btf__add_int(btf, "long", 8, BTF_INT_SIGNED); /* [7] long */
+ btf__add_func(btf, "foo", BTF_FUNC_STATIC, 2); /* [8] another foo */
+ btf__add_func(btf, "shared", BTF_FUNC_STATIC, 2); /* [9] shared string */
+ btf__add_struct(btf, "shared", 4); /* [10] retains string */
+
+ permute_ids[0] = 0;
+ permute_ids[1] = 1;
+ permute_ids[2] = BTF_PERMUTE_ID_TRANSFER | 2;
+ permute_ids[3] = BTF_PERMUTE_ID_TRANSFER | 3;
+ permute_ids[4] = BTF_PERMUTE_ID_TRANSFER | 5;
+ permute_ids[5] = BTF_PERMUTE_ID_TRANSFER | 6;
+ permute_ids[6] = BTF_PERMUTE_ID_TRANSFER | 7;
+ permute_ids[7] = 4;
+ permute_ids[8] = BTF_PERMUTE_ID_TRANSFER | 8;
+ permute_ids[9] = BTF_PERMUTE_ID_TRANSFER | 9;
+ permute_ids[10] = 10;
+ opts.transfer_btf = &transfer_btf;
+ err = btf__permute(btf, permute_ids, ARRAY_SIZE(permute_ids), &opts);
+ if (!ASSERT_OK(err, "btf__permute_transfer") ||
+ !ASSERT_OK_PTR(transfer_btf, "transfer_btf"))
+ goto cleanup;
+
+ ASSERT_EQ(4, btf__type_cnt(btf), "base_type_cnt");
+ ASSERT_EQ(-ENOENT, btf__find_str(btf, "foo"), "func_name_not_in_base");
+ shared_name_off = btf__find_str(btf, "shared");
+ if (!ASSERT_GE(shared_name_off, 0, "shared_name_in_base"))
+ goto cleanup;
+ for (id = btf__type_cnt(btf); id < btf__type_cnt(transfer_btf); id++) {
+ const struct btf_type *t = btf__type_by_id(transfer_btf, id);
+
+ if (!btf_is_func(t))
+ continue;
+ if (!strcmp(btf__name_by_offset(transfer_btf, t->name_off), "foo")) {
+ foo_funcs++;
+ if (foo_name_off < 0)
+ foo_name_off = t->name_off;
+ else if (!ASSERT_EQ(foo_name_off, t->name_off, "local_foo_dedup"))
+ goto cleanup;
+ } else if (!strcmp(btf__name_by_offset(transfer_btf, t->name_off), "shared")) {
+ shared_funcs++;
+ if (!ASSERT_EQ(shared_name_off, t->name_off, "shared_name_reuses_base"))
+ goto cleanup;
+ }
+ }
+ if (!ASSERT_GE(foo_name_off, 0, "local_foo_name"))
+ goto cleanup;
+ if (!ASSERT_EQ(2, foo_funcs, "local_foo_count") ||
+ !ASSERT_EQ(1, shared_funcs, "shared_func_count"))
+ goto cleanup;
+ VALIDATE_RAW_BTF(
+ transfer_btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] INT 'long' size=8 bits_offset=0 nr_bits=64 encoding=SIGNED",
+ "[3] STRUCT 'shared' size=4 vlen=0",
+ "[4] FUNC_PROTO '(anon)' ret_type_id=1 vlen=0",
+ "[5] FUNC 'foo' type_id=4 linkage=static",
+ "[6] LOC_PARAM '(anon)' size=4 flags=0x1 vlen=1\n"
+ "\tvalue=0",
+ "[7] LOC_PROTO '(anon)' vlen=1\n"
+ "\ttype_id=6",
+ "[8] LOCSEC '.locs' vlen=2\n"
+ "\tfunc_type_id=5 loc_proto_type_id=7 offset=128\n"
+ "\tfunc_type_id=5 loc_proto_type_id=7 offset=0",
+ "[9] FUNC 'foo' type_id=4 linkage=static",
+ "[10] FUNC 'shared' type_id=4 linkage=static");
+cleanup:
+ btf__free(transfer_btf);
+ btf__free(btf);
+}
+
+/* Permuting BTF with a layout section must keep section offsets in sync. */
+static void test_permute_layout(void)
+{
+ LIBBPF_OPTS(btf_new_opts, opts, .add_layout = true);
+ LIBBPF_OPTS(btf_permute_opts, permute_opts);
+ struct btf *btf, *parsed, *transfer_btf = NULL;
+ const void *raw;
+ __u32 raw_sz;
+ __u32 permute_ids[] = { 0, 1, BTF_PERMUTE_ID_TRANSFER | 2 };
+ int err;
+
+ btf = btf__new_empty_opts(&opts);
+ if (!ASSERT_OK_PTR(btf, "empty_layout_btf"))
+ return;
+
+ btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+ btf__add_ptr(btf, 1);
+ permute_opts.transfer_btf = &transfer_btf;
+ err = btf__permute(btf, permute_ids, ARRAY_SIZE(permute_ids), &permute_opts);
+ if (!ASSERT_OK(err, "btf__permute_layout"))
+ goto cleanup;
+
+ raw = btf__raw_data(btf, &raw_sz);
+ parsed = btf__new(raw, raw_sz);
+ if (!ASSERT_OK_PTR(parsed, "parse_permuted_layout"))
+ goto cleanup;
+ btf__free(parsed);
+cleanup:
+ btf__free(transfer_btf);
+ btf__free(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_transfer"))
+ test_permute_transfer();
+ if (test__start_subtest("permute_layout"))
+ test_permute_layout();
}
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 09/18] bpftool: Handle multi-split BTF by supporting multiple base BTFs
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (7 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 08/18] selftests/bpf: Validate that btf__permute transfer works Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:13 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 10/18] bpftool: Document support for multi-split BTF Alan Maguire
` (8 subsequent siblings)
17 siblings, 1 reply; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
For bpftool to be able to dump .BTF.inline data in
/sys/kernel/btf/foo.inline for module foo, it needs to support
multi-split BTF because the parent-child relationship of BTF
inline data for modules is
vmlinux BTF data
module BTF data
module BTF inline data
So for example to dump BTF inline info for xfs we would run
$ bpftool btf dump -B /sys/kernel/btf/vmlinux -B /sys/kernel/btf/xfs file /sys/kernel/btf/xfs.inline
Multiple bases are specified with the vmlinux base BTF first (parent)
followed by the xfs BTF (child), and finally the XFS BTF extra.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
tools/bpf/bpftool/main.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
index 5ababd8f7d0a..9329f140b700 100644
--- a/tools/bpf/bpftool/main.c
+++ b/tools/bpf/bpftool/main.c
@@ -515,7 +515,8 @@ int main(int argc, char **argv)
verifier_logs = true;
break;
case 'B':
- base_btf = btf__parse(optarg, NULL);
+ /* handle multi-split BTF */
+ base_btf = btf__parse_split(optarg, base_btf);
if (!base_btf) {
p_err("failed to parse base BTF at '%s': %d\n",
optarg, -errno);
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 10/18] bpftool: Document support for multi-split BTF
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (8 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 09/18] bpftool: Handle multi-split BTF by supporting multiple base BTFs Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:12 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 11/18] bpftool: Add ability to dump LOC_PARAM, LOC_PROTO and LOCSEC Alan Maguire
` (7 subsequent siblings)
17 siblings, 1 reply; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Document the ability to pass multiple levels of split BTF, using
"-B base_btf" options.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
tools/bpf/bpftool/Documentation/bpftool-btf.rst | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/tools/bpf/bpftool/Documentation/bpftool-btf.rst b/tools/bpf/bpftool/Documentation/bpftool-btf.rst
index cf75a7fa2d6b..60c46748b866 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-btf.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-btf.rst
@@ -16,7 +16,7 @@ SYNOPSIS
**bpftool** [*OPTIONS*] **btf** *COMMAND*
-*OPTIONS* := { |COMMON_OPTIONS| | { **-B** | **--base-btf** } }
+*OPTIONS* := { |COMMON_OPTIONS| | { **-B** | **--base-btf** [ **-B** | **--base-btf** ] } }
*COMMANDS* := { **dump** | **help** }
@@ -87,7 +87,10 @@ OPTIONS
objects for kernel modules. To avoid duplicating all kernel symbols
required by modules, BTF objects for modules are "split", they are
built incrementally on top of the kernel (vmlinux) BTF object. So the
- base BTF reference should usually point to the kernel BTF.
+ base BTF reference should usually point to the kernel BTF. Multiple
+ base BTF objects can be passed, where the first is assumed to be the
+ root BTF, followed by split BTF based upon it, followed by split
+ BTF based upon the first split BTF and so on.
When the main BTF object to process (for example, the module BTF to
dump) is passed as a *FILE*, bpftool attempts to autodetect the path
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 11/18] bpftool: Add ability to dump LOC_PARAM, LOC_PROTO and LOCSEC
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (9 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 10/18] bpftool: Document support for multi-split BTF Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:16 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 12/18] resolve_btfids: Extract inline BTF Alan Maguire
` (6 subsequent siblings)
17 siblings, 2 replies; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
In raw mode ensure we can dump new BTF kinds in normal/json format.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
tools/bpf/bpftool/btf.c | 85 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 85 insertions(+)
diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
index bca0a3982f09..4d8991c99b48 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -51,6 +51,9 @@ static const char * const btf_kind_str[NR_BTF_KINDS] = {
[BTF_KIND_DECL_TAG] = "DECL_TAG",
[BTF_KIND_TYPE_TAG] = "TYPE_TAG",
[BTF_KIND_ENUM64] = "ENUM64",
+ [BTF_KIND_LOC_PARAM] = "LOC_PARAM",
+ [BTF_KIND_LOC_PROTO] = "LOC_PROTO",
+ [BTF_KIND_LOCSEC] = "LOCSEC",
};
struct sort_datum {
@@ -415,6 +418,88 @@ static int dump_btf_type(const struct btf *btf, __u32 id,
}
break;
}
+ case BTF_KIND_LOC_PARAM: {
+ const struct btf_loc_param *p = btf_loc_param(t);
+ __u32 *v = (__u32 *)(p + 1);
+ __u32 i, vlen = btf_vlen(t);
+
+ if (json_output) {
+ jsonw_uint_field(w, "size", t->size);
+ jsonw_uint_field(w, "flags", p->flags);
+ jsonw_uint_field(w, "vlen", vlen);
+ jsonw_name(w, "values");
+ jsonw_start_array(w);
+ } else {
+ printf(" size=%u flags=0x%x vlen=%u", t->size, p->flags, vlen);
+ }
+ for (i = 0; i < vlen; i++, v++) {
+ if (json_output) {
+ jsonw_start_object(w);
+ jsonw_uint_field(w, "value", *v);
+ jsonw_end_object(w);
+ } else {
+ printf("\n\t value=%u", *v);
+ }
+ }
+ if (json_output)
+ jsonw_end_array(w);
+ break;
+ }
+ case BTF_KIND_LOC_PROTO: {
+ __u32 *params = btf_loc_proto_params(t);
+ __u16 vlen = btf_vlen(t);
+ int i;
+
+ if (json_output) {
+ jsonw_uint_field(w, "vlen", vlen);
+ jsonw_name(w, "params");
+ jsonw_start_array(w);
+ } else {
+ printf(" vlen=%u", vlen);
+ }
+
+ for (i = 0; i < vlen; i++, params++) {
+ if (json_output) {
+ jsonw_start_object(w);
+ jsonw_uint_field(w, "type_id", *params);
+ jsonw_end_object(w);
+ } else {
+ printf("\n\t type_id=%u", *params);
+ }
+ }
+ if (json_output)
+ jsonw_end_array(w);
+ break;
+ }
+
+ case BTF_KIND_LOCSEC: {
+ struct btf_loc *locs = btf_locsec_locs(t);
+ __u32 i, vlen = btf_vlen(t);
+
+ if (json_output) {
+ jsonw_uint_field(w, "vlen", vlen);
+ jsonw_name(w, "locs");
+ jsonw_start_array(w);
+ } else {
+ printf(" vlen=%u", vlen);
+ }
+
+ for (i = 0; i < vlen; i++, locs++) {
+ if (json_output) {
+ jsonw_start_object(w);
+ jsonw_uint_field(w, "func_type_id", locs->func);
+ jsonw_uint_field(w, "loc_proto_type_id", locs->loc_proto);
+ jsonw_uint_field(w, "offset", locs->offset);
+ jsonw_end_object(w);
+ } else {
+ printf("\n\t func_type_id=%u loc_proto_type_id=%u offset=%u",
+ locs->func, locs->loc_proto, locs->offset);
+ }
+ }
+ if (json_output)
+ jsonw_end_array(w);
+ break;
+ }
default:
break;
}
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 12/18] resolve_btfids: Extract inline BTF
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (10 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 11/18] bpftool: Add ability to dump LOC_PARAM, LOC_PROTO and LOCSEC Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:23 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 13/18] kbuild: Add support for BTF inline information Alan Maguire
` (5 subsequent siblings)
17 siblings, 1 reply; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Add an --inline option to extract LOC_PARAM, LOC_PROTO and LOCSEC
types to a new inline split .BTF.inline while sorting BTF.
Move LOC_PARAM, LOC_PROTO, and LOCSEC types to the split BTF. Also move
a FUNC when it is referenced only by LOCSEC, has no defined STT_FUNC
symbol, and is not used by .BTF_ids. Move its FUNC_PROTO when no
retained BTF type references that prototype.
Collect defined ELF function symbols to distinguish fully inlined
functions from functions that still have an out-of-line implementation.
The resulting .BTF.inline therefore contains only fully inlined FUNCs
(or any which have name collisions with other uninlined functions).
Sort types so transferred FUNCs, eligible FUNC_PROTOs, and LOC* types
form a self-contained split BTF. Sort LOCSEC records by projected
function BTF ID, instruction offset, and location-prototype ID before
permuting, preserving lookup order after IDs are remapped.
The end state is
- strings are moved to split BTF along with inline-related types
when they do not have another consumer in vmlinux BTF; the goal
is to minimum cost for vmlinux of inline info
- partially-inlined functions referred to in LOCSEC are in vmlinux BTF
- fully inlined functions are in vmlinux inline BTF
- LOCSEC data is sorted by FUNC type id as the pattern for inline
tracers will be to look up the function by name; once that is
done a binary search based on FUNC type id can quickly identify
relevant entries in LOCSEC
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
tools/bpf/resolve_btfids/main.c | 336 +++++++++++++++++++++++++++++++-
1 file changed, 330 insertions(+), 6 deletions(-)
diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c
index 37d7e7224207..20b228c4d678 100644
--- a/tools/bpf/resolve_btfids/main.c
+++ b/tools/bpf/resolve_btfids/main.c
@@ -142,7 +142,9 @@ struct object {
struct btf *btf;
struct btf *base_btf;
+ struct btf *inline_btf;
bool distill_base;
+ bool extract_inline;
struct {
int fd;
@@ -154,6 +156,10 @@ struct object {
size_t strtabidx;
unsigned long idlist_addr;
int encoding;
+ const char **func_symbols;
+ u32 func_symbols_cnt;
+ u32 func_symbols_cap;
+ bool func_symbols_collected;
} efile;
struct rb_root sets;
@@ -572,6 +578,58 @@ static const char *find_name_by_addr(struct object *obj, Elf64_Addr addr)
return res ? res->name : NULL;
}
+static int cmp_func_symbol(const void *a, const void *b)
+{
+ const char * const *name = a;
+ const char * const *other = b;
+
+ return strcmp(*name, *other);
+}
+
+static int collect_func_symbols(struct object *obj)
+{
+ Elf_Scn *scn;
+ GElf_Shdr sh;
+ int n, i;
+
+ if (obj->efile.symbols_shndx == -1)
+ return 0;
+
+ scn = elf_getscn(obj->efile.elf, obj->efile.symbols_shndx);
+ if (!scn || gelf_getshdr(scn, &sh) != &sh)
+ return -EINVAL;
+ n = sh.sh_size / sh.sh_entsize;
+
+ for (i = 0; i < n; i++) {
+ GElf_Sym sym;
+ const char *name;
+
+ if (!gelf_getsym(obj->efile.symbols, i, &sym))
+ return -EINVAL;
+ if (GELF_ST_TYPE(sym.st_info) != STT_FUNC ||
+ sym.st_shndx == SHN_UNDEF || !sym.st_name)
+ continue;
+ name = elf_strptr(obj->efile.elf, obj->efile.strtabidx, sym.st_name);
+ if (!name)
+ return -EINVAL;
+ if (ensure_mem(&obj->efile.func_symbols, &obj->efile.func_symbols_cap,
+ obj->efile.func_symbols_cnt + 1))
+ return -ENOMEM;
+ obj->efile.func_symbols[obj->efile.func_symbols_cnt++] = name;
+ }
+
+ qsort(obj->efile.func_symbols, obj->efile.func_symbols_cnt,
+ sizeof(*obj->efile.func_symbols), cmp_func_symbol);
+ obj->efile.func_symbols_collected = true;
+ return 0;
+}
+
+static bool has_func_symbol(const struct object *obj, const char *name)
+{
+ return bsearch(&name, obj->efile.func_symbols, obj->efile.func_symbols_cnt,
+ sizeof(*obj->efile.func_symbols), cmp_func_symbol) != NULL;
+}
+
static int symbols_collect(struct object *obj)
{
Elf_Scn *scn = NULL;
@@ -1514,14 +1572,193 @@ static int btf2btf(struct object *obj)
* Sort types by name in ascending order resulting in all
* anonymous types being placed before named types.
*/
+struct btf_name_sort {
+ struct btf *btf;
+ bool extract_inline;
+ bool *transfer_funcs;
+ bool *transfer_protos;
+};
+
+static int btf_inline_kind_order(const struct btf_type *t, bool transfer_func,
+ bool transfer_proto)
+{
+ if (transfer_func)
+ return 1;
+ if (transfer_proto)
+ return 2;
+ if (btf_is_loc_param(t))
+ return 3;
+ if (btf_is_loc_proto(t))
+ return 4;
+ if (btf_is_locsec(t))
+ return 5;
+ return 0;
+}
+
+enum inline_ref_kind {
+ INLINE_REF_FUNC,
+ INLINE_REF_FUNC_PROTO,
+};
+
+static void mark_inline_ref(struct btf *btf, bool *refs, int start_id,
+ int type_cnt, __u32 type_id, enum inline_ref_kind ref_kind)
+{
+ const struct btf_type *t;
+
+ if (type_id < start_id || type_id >= type_cnt)
+ return;
+ t = btf__type_by_id(btf, type_id);
+ if ((ref_kind == INLINE_REF_FUNC && btf_is_func(t)) ||
+ (ref_kind == INLINE_REF_FUNC_PROTO && btf_is_func_proto(t)))
+ refs[type_id] = true;
+}
+
+static void mark_inline_type_refs(struct btf *btf, bool *refs, int start_id,
+ int type_cnt, const struct btf_type *t,
+ enum inline_ref_kind ref_kind)
+{
+ /*
+ * These are the only BTF kinds that can directly reference a FUNC or
+ * FUNC_PROTO (aside from LOCSEC).
+ */
+ switch (btf_kind(t)) {
+ case BTF_KIND_CONST:
+ case BTF_KIND_VOLATILE:
+ case BTF_KIND_RESTRICT:
+ case BTF_KIND_PTR:
+ case BTF_KIND_TYPEDEF:
+ case BTF_KIND_FUNC:
+ case BTF_KIND_VAR:
+ case BTF_KIND_DECL_TAG:
+ case BTF_KIND_TYPE_TAG:
+ mark_inline_ref(btf, refs, start_id, type_cnt, t->type, ref_kind);
+ break;
+ default:
+ return;
+ }
+}
+
+/* A FUNC can move to inline BTF only when LOCSEC is its sole BTF user. */
+static int find_inline_funcs(struct object *obj, bool **transfer_funcsp,
+ bool **transfer_protosp)
+{
+ bool *locsec_refs, *other_refs, *proto_candidates, *proto_base_refs;
+ int i, type_cnt, start_id = 0;
+ struct btf *btf = obj->btf;
+
+ type_cnt = btf__type_cnt(btf);
+ *transfer_funcsp = NULL;
+ *transfer_protosp = NULL;
+ if (btf__base_btf(btf))
+ start_id = btf__type_cnt(btf__base_btf(btf));
+
+ locsec_refs = calloc(type_cnt, sizeof(*locsec_refs));
+ other_refs = calloc(type_cnt, sizeof(*other_refs));
+ proto_candidates = calloc(type_cnt, sizeof(*proto_candidates));
+ proto_base_refs = calloc(type_cnt, sizeof(*proto_base_refs));
+ if (!locsec_refs || !other_refs || !proto_candidates || !proto_base_refs)
+ goto err;
+
+ /* First identify locsec FUNC references */
+ for (i = start_id; i < type_cnt; i++) {
+ const struct btf_type *t = btf__type_by_id(btf, i);
+ const struct btf_loc *loc;
+ int j;
+
+ if (!btf_is_locsec(t))
+ continue;
+ loc = btf_locsec_locs(t);
+ for (j = 0; j < btf_vlen(t); j++, loc++) {
+ if (loc->func >= start_id && loc->func < type_cnt &&
+ btf_is_func(btf__type_by_id(btf, loc->func)))
+ locsec_refs[loc->func] = true;
+ }
+ }
+
+ /*
+ * Then mark any additional references to them; references outside
+ * of LOCSEC make the FUNC ineligible for transfer.
+ */
+ for (i = start_id; i < type_cnt; i++) {
+ const struct btf_type *t = btf__type_by_id(btf, i);
+
+ if (btf_is_locsec(t))
+ continue;
+ mark_inline_type_refs(btf, other_refs, start_id, type_cnt, t,
+ INLINE_REF_FUNC);
+ }
+
+ for (i = start_id; i < type_cnt; i++) {
+ const struct btf_type *t = btf__type_by_id(btf, i);
+ const char *name;
+
+ /* If there are no locsec references, or we find additional
+ * non-locsec references, skip as ineligible.
+ */
+ if (!locsec_refs[i] || other_refs[i])
+ continue;
+ name = btf__name_by_offset(btf, t->name_off);
+ /* Keep out-of-line functions and BTF ID users in the main BTF. */
+ if (!obj->efile.func_symbols_collected || has_func_symbol(obj, name) ||
+ btf_id__find(&obj->funcs, name))
+ locsec_refs[i] = false;
+ }
+
+ for (i = start_id; i < type_cnt; i++) {
+ const struct btf_type *t;
+
+ if (!locsec_refs[i])
+ continue;
+ t = btf__type_by_id(btf, i);
+ proto_candidates[t->type] = true;
+ }
+
+ /* Mark every FUNC_PROTO directly needed by retained base BTF types. */
+ for (i = start_id; i < type_cnt; i++) {
+ const struct btf_type *t = btf__type_by_id(btf, i);
+
+ if (locsec_refs[i] || proto_candidates[i] ||
+ btf_is_loc_param(t) || btf_is_loc_proto(t) ||
+ btf_is_locsec(t))
+ continue;
+ mark_inline_type_refs(btf, proto_base_refs, start_id, type_cnt, t,
+ INLINE_REF_FUNC_PROTO);
+ }
+ for (i = start_id; i < type_cnt; i++)
+ proto_candidates[i] &= !proto_base_refs[i];
+
+ free(other_refs);
+ *transfer_funcsp = locsec_refs;
+ *transfer_protosp = proto_candidates;
+ free(proto_base_refs);
+ return 0;
+err:
+ free(locsec_refs);
+ free(other_refs);
+ free(proto_candidates);
+ free(proto_base_refs);
+ return -ENOMEM;
+}
+
static int cmp_type_names(const void *a, const void *b, void *priv)
{
- struct btf *btf = (struct btf *)priv;
+ struct btf_name_sort *sort = priv;
+ struct btf *btf = sort->btf;
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;
+ int inline_order;
int r;
+ if (sort->extract_inline) {
+ inline_order = btf_inline_kind_order(ta, sort->transfer_funcs[*(__u32 *)a],
+ sort->transfer_protos[*(__u32 *)a]) -
+ btf_inline_kind_order(tb, sort->transfer_funcs[*(__u32 *)b],
+ sort->transfer_protos[*(__u32 *)b]);
+ if (inline_order)
+ return inline_order;
+ }
+
na = btf__str_by_offset(btf, ta->name_off);
nb = btf__str_by_offset(btf, tb->name_off);
r = strcmp(na, nb);
@@ -1532,8 +1769,60 @@ static int cmp_type_names(const void *a, const void *b, void *priv)
return *(__u32 *)a < *(__u32 *)b ? -1 : 1;
}
-static int sort_btf_by_name(struct btf *btf)
+struct loc_sort {
+ const __u32 *id_map;
+ __u32 start_id;
+};
+
+static __u32 projected_type_id(const struct loc_sort *sort, __u32 id)
+{
+ if (id < sort->start_id)
+ return id;
+ return sort->id_map[id - sort->start_id] & ~BTF_PERMUTE_ID_TRANSFER;
+}
+
+static int cmp_loc(const void *a, const void *b, void *priv)
{
+ const struct loc_sort *sort = priv;
+ const struct btf_loc *la = a, *lb = b;
+ __u32 afunc = projected_type_id(sort, la->func);
+ __u32 bfunc = projected_type_id(sort, lb->func);
+ __u32 aproto = projected_type_id(sort, la->loc_proto);
+ __u32 bproto = projected_type_id(sort, lb->loc_proto);
+
+ if (afunc != bfunc)
+ return afunc < bfunc ? -1 : 1;
+ if (la->offset != lb->offset)
+ return la->offset < lb->offset ? -1 : 1;
+ if (aproto != bproto)
+ return aproto < bproto ? -1 : 1;
+ return 0;
+}
+
+static void sort_locsecs(struct btf *btf, const __u32 *id_map, __u32 start_id,
+ int nr_types)
+{
+ struct loc_sort sort = { .id_map = id_map, .start_id = start_id };
+ int i;
+
+ for (i = 0; i < nr_types; i++) {
+ struct btf_type *t = (struct btf_type *)btf__type_by_id(btf, start_id + i);
+
+ if (btf_is_locsec(t))
+ qsort_r(btf_locsec_locs(t), btf_vlen(t), sizeof(struct btf_loc),
+ cmp_loc, &sort);
+ }
+}
+
+static int sort_btf_by_name(struct object *obj)
+{
+ struct btf *btf = obj->btf;
+ bool *transfer_funcs = NULL, *transfer_protos = NULL;
+ struct btf_name_sort sort = {
+ .btf = btf,
+ .extract_inline = obj->extract_inline,
+ };
+ LIBBPF_OPTS(btf_permute_opts, opts);
__u32 *permute_ids = NULL, *id_map = NULL;
int nr_types, i, err = 0;
__u32 start_id = 0, id;
@@ -1541,6 +1830,13 @@ static int sort_btf_by_name(struct btf *btf)
if (btf__base_btf(btf))
start_id = btf__type_cnt(btf__base_btf(btf));
nr_types = btf__type_cnt(btf) - start_id;
+ if (obj->extract_inline) {
+ err = find_inline_funcs(obj, &transfer_funcs, &transfer_protos);
+ if (err)
+ goto out;
+ sort.transfer_funcs = transfer_funcs;
+ sort.transfer_protos = transfer_protos;
+ }
permute_ids = calloc(nr_types, sizeof(*permute_ids));
if (!permute_ids) {
@@ -1557,21 +1853,37 @@ static int sort_btf_by_name(struct btf *btf)
for (i = 0, id = start_id; i < nr_types; i++, id++)
permute_ids[i] = id;
- qsort_r(permute_ids, nr_types, sizeof(*permute_ids), cmp_type_names,
- btf);
+ qsort_r(permute_ids, nr_types, sizeof(*permute_ids), cmp_type_names, &sort);
for (i = 0; i < nr_types; i++) {
id = permute_ids[i] - start_id;
id_map[id] = i + start_id;
+ if (obj->extract_inline &&
+ btf_inline_kind_order(btf__type_by_id(btf, permute_ids[i]),
+ transfer_funcs[permute_ids[i]],
+ transfer_protos[permute_ids[i]]))
+ id_map[id] |= BTF_PERMUTE_ID_TRANSFER;
}
- err = btf__permute(btf, id_map, nr_types, NULL);
+ /*
+ * Key LOCSEC records by their post-permutation function ID and offset
+ * since the tracer pattern will be to look up FUNC by name, then
+ * find LOCSEC records for that function.
+ */
+ sort_locsecs(btf, id_map, start_id, nr_types);
+
+ if (obj->extract_inline)
+ opts.transfer_btf = &obj->inline_btf;
+ err = btf__permute(btf, id_map, nr_types,
+ obj->extract_inline ? &opts : NULL);
if (err)
pr_err("FAILED: btf permute: %s\n", strerror(-err));
out:
free(permute_ids);
free(id_map);
+ free(transfer_funcs);
+ free(transfer_protos);
return err;
}
@@ -1599,7 +1911,7 @@ static int finalize_btf(struct object *obj)
obj->btf = btf;
}
- err = sort_btf_by_name(obj->btf);
+ err = sort_btf_by_name(obj);
if (err) {
pr_err("FAILED to sort BTF: %s\n", strerror(errno));
goto out_err;
@@ -1773,6 +2085,8 @@ int main(int argc, const char **argv)
"turn warnings into errors"),
OPT_BOOLEAN(0, "distill_base", &obj.distill_base,
"distill --btf_base and emit .BTF.base section data"),
+ OPT_BOOLEAN(0, "inline", &obj.extract_inline,
+ "extract location BTF into a .BTF.inline file"),
OPT_STRING(0, "patch_btfids", &btfids_path, "file",
"path to .BTF_ids section data blob to patch into ELF file"),
OPT_END()
@@ -1791,6 +2105,8 @@ int main(int argc, const char **argv)
if (elf_collect(&obj))
goto out;
+ if (obj.extract_inline && collect_func_symbols(&obj))
+ goto out;
/*
* We did not find .BTF_ids section or symbols section,
@@ -1841,11 +2157,18 @@ int main(int argc, const char **argv)
if (err)
goto out;
}
+ if (obj.inline_btf) {
+ err = make_out_path(out_path, sizeof(out_path), obj.path, BTF_ELF_SEC ".inline");
+ err = err ?: dump_raw_btf(obj.inline_btf, out_path);
+ if (err)
+ goto out;
+ }
if (!(fatal_warnings && warnings))
err = 0;
out:
btf__free(obj.base_btf);
+ btf__free(obj.inline_btf);
btf__free(obj.btf);
btf_id__free_all(&obj.structs);
btf_id__free_all(&obj.unions);
@@ -1853,6 +2176,7 @@ int main(int argc, const char **argv)
btf_id__free_all(&obj.funcs);
btf_id__free_all(&obj.sets);
free(obj.addr_syms);
+ free(obj.efile.func_symbols);
if (obj.efile.elf) {
elf_end(obj.efile.elf);
close(obj.efile.fd);
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 13/18] kbuild: Add support for BTF inline information
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (11 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 12/18] resolve_btfids: Extract inline BTF Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 14/18] btf: Make vmlinux, module inline info available in /sys/kernel/btf Alan Maguire
` (4 subsequent siblings)
17 siblings, 1 reply; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Add CONFIG_DEBUG_INFO_BTF_INLINE to generate BTF inline-location
information with pahole v1.31 and later.
Enable pahole's inline feature and pass --inline to resolve_btfids.
This extracts LOC_PARAM, LOC_PROTO, and LOCSEC types into a split BTF
blob named <output>.BTF.inline while the ordinary BTF remains in .BTF.
Embed the resulting blob in a .BTF.inline ELF section for vmlinux and
modules. Add the vmlinux linker-script output section to avoid it
being treated as an orphan.
Use the inline pahole feature unconditionally for both in-tree and
external modules; the generated split BTF has the same format in both
cases.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
include/asm-generic/vmlinux.lds.h | 11 +++++++++++
lib/Kconfig.debug | 17 +++++++++++++++++
scripts/Makefile.btf | 7 +++++++
scripts/gen-btf.sh | 17 +++++++++++++++--
4 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index b2988aa12f66..a2e192854c47 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -675,12 +675,23 @@
/*
* .BTF
*/
+#ifdef CONFIG_DEBUG_INFO_BTF_INLINE
+#define BTF_INLINE \
+ . = ALIGN(PAGE_SIZE); \
+ .BTF.inline : AT(ADDR(.BTF.inline) - LOAD_OFFSET) { \
+ BOUNDED_SECTION_BY(.BTF.inline, _BTF_inline) \
+ }
+#else
+#define BTF_INLINE
+#endif
+
#ifdef CONFIG_DEBUG_INFO_BTF
#define BTF \
. = ALIGN(PAGE_SIZE); \
.BTF : AT(ADDR(.BTF) - LOAD_OFFSET) { \
BOUNDED_SECTION_BY(.BTF, _BTF) \
} \
+ BTF_INLINE \
. = ALIGN(PAGE_SIZE); \
.BTF_ids : AT(ADDR(.BTF_ids) - LOAD_OFFSET) { \
*(.BTF_ids) \
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 1244dcac2294..dd1b2d9ebe99 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -425,6 +425,12 @@ config PAHOLE_HAS_LANG_EXCLUDE
otherwise it would emit malformed kernel and module binaries when
using DEBUG_INFO_BTF_MODULES.
+config PAHOLE_HAS_INLINE
+ def_bool PAHOLE_VERSION >= 131
+ help
+ Support for the "inline" BTF feature is available. It encodes
+ information about inline sites and how to retrieve their parameters.
+
config DEBUG_INFO_BTF_MODULES
bool "Generate BTF type information for kernel modules"
default y
@@ -432,6 +438,17 @@ config DEBUG_INFO_BTF_MODULES
help
Generate compact split BTF type information for kernel modules.
+config DEBUG_INFO_BTF_INLINE
+ bool "Provide information about inline sites in BTF"
+ default n
+ depends on DEBUG_INFO_BTF && PAHOLE_HAS_INLINE && SYSFS
+ help
+ Generate information about inline sites in .BTF.inline sections.
+ These sections contain split BTF relative to the kernel or module BTF
+ and are made available in /sys/kernel/btf with a ".inline" suffix.
+ The information describes inline locations and how to retrieve their
+ associated parameters.
+
config MODULE_ALLOW_BTF_MISMATCH
bool "Allow loading modules with non-matching BTF type info"
depends on DEBUG_INFO_BTF_MODULES
diff --git a/scripts/Makefile.btf b/scripts/Makefile.btf
index a1812985a61a..d42dbc8d7199 100644
--- a/scripts/Makefile.btf
+++ b/scripts/Makefile.btf
@@ -22,7 +22,14 @@ endif
pahole-flags-$(CONFIG_PAHOLE_HAS_LANG_EXCLUDE) += --lang_exclude=rust
+btf-inline := $(CONFIG_DEBUG_INFO_BTF_INLINE)
+ifneq ($(btf-inline),)
+btf-inline-feat := inline
+pahole-flags-$(call test-ge, $(pahole-ver), 131) += --btf_features=$(btf-inline-feat)
+endif
+
export PAHOLE_FLAGS := $(pahole-flags-y)
+export BTF_INLINE := $(btf-inline)
resolve-btfids-flags-y :=
resolve-btfids-flags-$(CONFIG_WERROR) += --fatal_warnings
diff --git a/scripts/gen-btf.sh b/scripts/gen-btf.sh
index 8ca96eb10a69..a75f41878c32 100755
--- a/scripts/gen-btf.sh
+++ b/scripts/gen-btf.sh
@@ -76,6 +76,7 @@ gen_btf_data()
${RESOLVE_BTFIDS} ${RESOLVE_BTFIDS_FLAGS} \
${BTF_BASE:+--btf_base ${BTF_BASE}} \
+ ${BTF_INLINE:+--inline} \
--btf ${btf1} "${ELF_FILE}"
}
@@ -83,14 +84,21 @@ gen_btf_o()
{
btf_data=${ELF_FILE}.btf.o
- # Create ${btf_data} which contains just .BTF section but no symbols. Add
+ # Create ${btf_data} which contains just BTF sections but no symbols. Add
# SHF_ALLOC because .BTF will be part of the vmlinux image. --strip-all
# deletes all symbols including __start_BTF and __stop_BTF, which will
# be redefined in the linker script.
echo "" | ${CC} ${CLANG_FLAGS} ${KBUILD_CPPFLAGS} ${KBUILD_CFLAGS} -fno-lto -c -x c -o ${btf_data} -
${OBJCOPY} --add-section .BTF=${ELF_FILE}.BTF \
--set-section-flags .BTF=alloc,readonly ${btf_data}
- ${OBJCOPY} --only-section=.BTF --strip-all ${btf_data}
+ ONLY_SEC="--only-section=.BTF"
+ btf_inline=${ELF_FILE}.BTF.inline
+ if [ -n "${BTF_INLINE}" ] && [ -f "${btf_inline}" ]; then
+ ${OBJCOPY} --add-section .BTF.inline=${btf_inline} \
+ --set-section-flags .BTF.inline=alloc,readonly ${btf_data}
+ ONLY_SEC="${ONLY_SEC} --only-section=.BTF.inline"
+ fi
+ ${OBJCOPY} ${ONLY_SEC} --strip-all ${btf_data}
# Change e_type to ET_REL so that it can be used to link final vmlinux.
# GNU ld 2.35+ and lld do not allow an ET_EXEC input.
@@ -111,6 +119,10 @@ embed_btf_data()
if [ -f "${btf_base}" ]; then
${OBJCOPY} --add-section .BTF.base=${btf_base} ${ELF_FILE}
fi
+ btf_inline=${ELF_FILE}.BTF.inline
+ if [ -n "${BTF_INLINE}" ] && [ -f "${btf_inline}" ]; then
+ ${OBJCOPY} --add-section .BTF.inline=${btf_inline} ${ELF_FILE}
+ fi
btf_ids="${ELF_FILE}.BTF_ids"
if [ -f "${btf_ids}" ]; then
${RESOLVE_BTFIDS} --patch_btfids ${btf_ids} ${ELF_FILE}
@@ -121,6 +133,7 @@ cleanup()
{
rm -f "${ELF_FILE}.BTF.1"
rm -f "${ELF_FILE}.BTF"
+ rm -f "${ELF_FILE}.BTF.inline"
if [ "${BTFGEN_MODE}" = "module" ]; then
rm -f "${ELF_FILE}.BTF.base"
rm -f "${ELF_FILE}.BTF_ids"
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 14/18] btf: Make vmlinux, module inline info available in /sys/kernel/btf
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (12 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 13/18] kbuild: Add support for BTF inline information Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 16:57 ` [PATCH v2 bpf-next 15/18] btf: Support CONFIG_DEBUG_INFO_BTF_INLINE=m Alan Maguire
` (3 subsequent siblings)
17 siblings, 0 replies; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Expose BTF inline-location information from vmlinux/module .BTF.inline
sections in /sys/kernel/btf using a ".inline" suffix.
vmlinux inline BTF is split relative to vmlinux BTF and can be examined
with:
bpftool btf dump file /sys/kernel/btf/vmlinux.inline
Register the sysfs attribute only when the embedded .BTF.inline section
is non-empty.
Module inline BTF is split relative to the module BTF, which in turn may
be split relative to its distilled base BTF. For example:
bpftool btf dump -B /sys/kernel/btf/vmlinux -B /sys/kernel/btf/xfs \
file /sys/kernel/btf/xfs.inline
Copy the inline BTF data into a private buffer for its sysfs lifetime
and remove the attribute and buffer when the module is unloaded.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
include/linux/btf.h | 1 +
include/linux/module.h | 4 ++
kernel/bpf/Makefile | 1 +
kernel/bpf/btf.c | 109 ++++++++++++++++++++++++++------
kernel/bpf/btf_vmlinux_inline.c | 30 +++++++++
kernel/module/main.c | 4 ++
6 files changed, 128 insertions(+), 21 deletions(-)
create mode 100644 kernel/bpf/btf_vmlinux_inline.c
diff --git a/include/linux/btf.h b/include/linux/btf.h
index a4412bc16688..93f10d3ccabe 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -617,6 +617,7 @@ int get_kern_ctx_btf_id(struct bpf_verifier_log *log, enum bpf_prog_type prog_ty
bool btf_types_are_same(const struct btf *btf1, u32 id1,
const struct btf *btf2, u32 id2);
int btf_check_iter_arg(struct btf *btf, const struct btf_type *func, int arg_idx);
+struct bin_attribute *sysfs_btf_add(const char *name, void *data, size_t data_size);
static inline bool btf_type_is_struct_ptr(struct btf *btf, const struct btf_type *t)
{
diff --git a/include/linux/module.h b/include/linux/module.h
index 7566815fabbe..3d32c4e86f44 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -507,6 +507,10 @@ struct module {
void *btf_data;
void *btf_base_data;
#endif
+#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
+ unsigned int btf_inline_data_size;
+ void *btf_inline_data;
+#endif
#ifdef CONFIG_JUMP_LABEL
struct jump_entry *jump_entries;
unsigned int num_jump_entries;
diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile
index 9a92c348bbda..60aa5adb0354 100644
--- a/kernel/bpf/Makefile
+++ b/kernel/bpf/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_BPF_SYSCALL) += reuseport_array.o
endif
ifeq ($(CONFIG_SYSFS),y)
obj-$(CONFIG_DEBUG_INFO_BTF) += sysfs_btf.o
+obj-$(CONFIG_DEBUG_INFO_BTF_INLINE) += btf_vmlinux_inline.o
endif
ifeq ($(CONFIG_BPF_JIT),y)
obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index d74c8668aa3f..3e5890aed2db 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8735,12 +8735,69 @@ enum {
BTF_MODULE_F_LIVE = (1 << 0),
};
+#if IS_ENABLED(CONFIG_SYSFS)
+struct bin_attribute *sysfs_btf_add(const char *name, void *data, size_t data_size)
+{
+ struct bin_attribute *attr;
+ int err;
+
+ attr = kzalloc_obj(*attr);
+ if (!attr)
+ return ERR_PTR(-ENOMEM);
+
+ sysfs_bin_attr_init(attr);
+ attr->attr.mode = 0444;
+ attr->size = data_size;
+ attr->private = data;
+ attr->read = sysfs_bin_attr_simple_read;
+ attr->attr.name = kstrdup(name, GFP_KERNEL);
+ if (!attr->attr.name) {
+ err = -ENOMEM;
+ goto err_free;
+ }
+ err = sysfs_create_bin_file(btf_kobj, attr);
+ if (err) {
+ pr_warn("failed to register [%s] BTF in sysfs: %d\n", name, err);
+ goto err_free;
+ }
+ return attr;
+
+err_free:
+ kfree(attr->attr.name);
+ kfree(attr);
+ return ERR_PTR(err);
+}
+
+#else
+struct bin_attribute *sysfs_btf_add(const char *name, void *data, size_t data_size)
+{
+ return NULL;
+}
+#endif
+
#ifdef CONFIG_DEBUG_INFO_BTF_MODULES
+#if IS_ENABLED(CONFIG_SYSFS)
+static void sysfs_btf_remove(struct bin_attribute *attr)
+{
+ sysfs_remove_bin_file(btf_kobj, attr);
+ kfree(attr->attr.name);
+ kfree(attr);
+}
+#else
+static void sysfs_btf_remove(struct bin_attribute *attr)
+{
+}
+#endif
+
struct btf_module {
struct list_head list;
struct module *module;
struct btf *btf;
struct bin_attribute *sysfs_attr;
+#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
+ struct bin_attribute *sysfs_inline_attr;
+ void *btf_inline_data;
+#endif
int flags;
};
@@ -8754,6 +8811,7 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
{
struct btf_module *btf_mod, *tmp;
struct module *mod = module;
+ struct bin_attribute *attr;
struct btf *btf;
int err = 0;
@@ -8796,31 +8854,35 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
list_add(&btf_mod->list, &btf_modules);
mutex_unlock(&btf_module_mutex);
- if (IS_ENABLED(CONFIG_SYSFS)) {
- struct bin_attribute *attr;
-
- attr = kzalloc_obj(*attr);
- if (!attr)
- goto out;
+ attr = sysfs_btf_add(btf->name, btf->data, btf->data_size);
+ if (IS_ERR(attr)) {
+ err = 0;
+ goto out;
+ }
+ btf_mod->sysfs_attr = attr;
- sysfs_bin_attr_init(attr);
- attr->attr.name = btf->name;
- attr->attr.mode = 0444;
- attr->size = btf->data_size;
- attr->private = btf->data;
- attr->read = sysfs_bin_attr_simple_read;
+#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
+ if (mod->btf_inline_data_size > 0) {
+ char name[MODULE_NAME_LEN + sizeof(".inline")];
+ void *data;
- err = sysfs_create_bin_file(btf_kobj, attr);
- if (err) {
- pr_warn("failed to register module [%s] BTF in sysfs: %d\n",
- mod->name, err);
- kfree(attr);
+ data = kvmemdup(mod->btf_inline_data, mod->btf_inline_data_size,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!data) {
err = 0;
goto out;
}
-
- btf_mod->sysfs_attr = attr;
+ snprintf(name, sizeof(name), "%s.inline", mod->name);
+ attr = sysfs_btf_add(name, data, mod->btf_inline_data_size);
+ if (IS_ERR(attr)) {
+ err = 0;
+ kvfree(data);
+ goto out;
+ }
+ btf_mod->btf_inline_data = data;
+ btf_mod->sysfs_inline_attr = attr;
}
+#endif
break;
case MODULE_STATE_LIVE:
@@ -8849,10 +8911,15 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
btf_free_id(btf_mod->btf);
list_del(&btf_mod->list);
if (btf_mod->sysfs_attr)
- sysfs_remove_bin_file(btf_kobj, btf_mod->sysfs_attr);
+ sysfs_btf_remove(btf_mod->sysfs_attr);
+#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
+ if (btf_mod->sysfs_inline_attr) {
+ sysfs_btf_remove(btf_mod->sysfs_inline_attr);
+ kvfree(btf_mod->btf_inline_data);
+ }
+#endif
purge_cand_cache(btf_mod->btf);
btf_put(btf_mod->btf);
- kfree(btf_mod->sysfs_attr);
kfree(btf_mod);
break;
}
diff --git a/kernel/bpf/btf_vmlinux_inline.c b/kernel/bpf/btf_vmlinux_inline.c
new file mode 100644
index 000000000000..b155df9849b9
--- /dev/null
+++ b/kernel/bpf/btf_vmlinux_inline.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026, Oracle and/or its affiliates. */
+/*
+ * Provide kernel BTF inline function information for use by BPF tools.
+ */
+#include <linux/btf.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#if IS_BUILTIN(CONFIG_DEBUG_INFO_BTF_INLINE)
+extern char __start_BTF_inline[];
+extern char __stop_BTF_inline[];
+#endif
+
+static int __init btf_vmlinux_inline_init(void)
+{
+#if IS_BUILTIN(CONFIG_DEBUG_INFO_BTF_INLINE)
+ size_t data_size = __stop_BTF_inline - __start_BTF_inline;
+
+ if (data_size)
+ sysfs_btf_add("vmlinux.inline", __start_BTF_inline,
+ data_size);
+#endif
+ return 0;
+}
+subsys_initcall(btf_vmlinux_inline_init);
+
+MODULE_DESCRIPTION("BTF inline information for vmlinux");
+MODULE_LICENSE("GPL");
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 46dd8d25a605..7e06ef113113 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -2712,6 +2712,10 @@ static int find_module_sections(struct module *mod, struct load_info *info)
mod->btf_base_data = any_section_objs(info, ".BTF.base", 1,
&mod->btf_base_data_size);
#endif
+#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
+ mod->btf_inline_data = any_section_objs(info, ".BTF.inline", 1,
+ &mod->btf_inline_data_size);
+#endif
#ifdef CONFIG_JUMP_LABEL
mod->jump_entries = section_objs(info, "__jump_table",
sizeof(*mod->jump_entries),
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 15/18] btf: Support CONFIG_DEBUG_INFO_BTF_INLINE=m
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (13 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 14/18] btf: Make vmlinux, module inline info available in /sys/kernel/btf Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:24 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 16/18] btf: Relocate inline BTF for modules with distilled base BTF Alan Maguire
` (2 subsequent siblings)
17 siblings, 1 reply; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Allow vmlinux BTF inline info to be delivered via a loadable
module btf_vmlinux_inline.ko; this reduces the vmlinux binary size.
We cannot use the standard sysfs_create_bin_file() interface
for this because when the user open()s vmlinux.inline() we
want to trigger module load. To do this we need to use
a kernfs representation for vmlinux.inline which we initialize
with NULL data, 0 size. When open() is called the kernfs
callback uses request_module() to trigger the module load
and the module notifier allocates the BTF data and sets the
size in the bin_attribute. Once this is complete we can
update the file inode and the caller will see the updated
size and be able to fseek(), ftell() and fread() normally.
With all this in place vmlinux.inline is created on startup
with size 0 and when open()ed we will synchronously load
the module and assign the binary data. So a user running
"bpftool btf dump -B vmlinux file vmlinux.inline" sees identical
behaviour whether the inline info is module-delivered or otherwise;
we simply save memory allocation if the inline info is not needed.
Inline BTF module delivery relies on the module BTF notifier, so select
DEBUG_INFO_BTF_MODULES when modules are enabled. Keep built-in-only
CONFIG_DEBUG_INFO_BTF_INLINE=y configurations independent of module BTF.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
Makefile | 1 +
kernel/bpf/btf.c | 114 +++++++++++++++++++++++++++++++-
kernel/bpf/btf_vmlinux_inline.c | 7 ++
lib/Kconfig.debug | 3 +-
scripts/gen-btf.sh | 18 ++++-
5 files changed, 139 insertions(+), 4 deletions(-)
diff --git a/Makefile b/Makefile
index 130926fa592e..4fa15fa5d1ed 100644
--- a/Makefile
+++ b/Makefile
@@ -1733,6 +1733,7 @@ endif # CONFIG_MODULES
CLEAN_FILES += vmlinux.symvers modules-only.symvers \
modules.builtin modules.builtin.modinfo modules.nsdeps \
modules.builtin.ranges vmlinux.o.map vmlinux.unstripped \
+ vmlinux.BTF.inline \
vmlinux.thinlto-index builtin.order \
compile_commands.json rust/test \
rust-project.json .vmlinux.objs .vmlinux.export.c \
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 3e5890aed2db..2ac1f1d39660 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8,6 +8,7 @@
#include <linux/seq_file.h>
#include <linux/compiler.h>
#include <linux/ctype.h>
+#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/anon_inodes.h>
@@ -25,6 +26,7 @@
#include <linux/perf_event.h>
#include <linux/bsearch.h>
#include <linux/kobject.h>
+#include <linux/kernfs.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/overflow.h>
@@ -8736,10 +8738,82 @@ enum {
};
#if IS_ENABLED(CONFIG_SYSFS)
+#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
+static struct bin_attribute *vmlinux_inline_attr;
+#endif
+
+static int sysfs_btf_bin_attr_load(struct bin_attribute *attr)
+{
+ char modname[MODULE_NAME_LEN + sizeof("btf_vmlinux_inline")];
+ int retries = 0;
+
+ /* First on-demand read; load module. */
+ snprintf(modname, sizeof(modname), "btf_%s", attr->attr.name);
+ strreplace(modname, '.', '_');
+ request_module("%s", modname);
+
+ /*
+ * request_module() is synchronous, but the module notifier is
+ * responsible for updating private data, so retries are required.
+ */
+ while (retries++ < 10) {
+ if (smp_load_acquire(&attr->size))
+ return 0;
+ udelay(50);
+ }
+ return -ENODEV;
+}
+
+static int sysfs_btf_kernfs_open(struct kernfs_open_file *of)
+{
+ struct bin_attribute *attr = of->kn->priv;
+ size_t data_size;
+ int err;
+
+ if (!smp_load_acquire(&attr->size)) {
+ err = sysfs_btf_bin_attr_load(attr);
+ if (err)
+ return err;
+ }
+ /* Refresh file size or the open() caller will not see updated size. */
+ data_size = smp_load_acquire(&attr->size);
+ of->kn->attr.size = data_size;
+ if (of->file) {
+ struct inode *inode = file_inode(of->file);
+
+ if (inode)
+ i_size_write(inode, data_size);
+ }
+ return 0;
+}
+
+static ssize_t sysfs_btf_kernfs_read(struct kernfs_open_file *of, char *buf,
+ size_t bytes_requested, loff_t offset)
+{
+ struct bin_attribute *attr = of->kn->priv;
+ void *data;
+ size_t data_size;
+
+ data_size = smp_load_acquire(&attr->size);
+ if (offset >= data_size)
+ return 0;
+ if (offset + bytes_requested > data_size)
+ bytes_requested = data_size - offset;
+ data = READ_ONCE(attr->private);
+ memcpy(buf, data + offset, bytes_requested);
+
+ return bytes_requested;
+}
+
+static const struct kernfs_ops sysfs_btf_kernfs_ops = {
+ .open = sysfs_btf_kernfs_open,
+ .read = sysfs_btf_kernfs_read,
+};
+
struct bin_attribute *sysfs_btf_add(const char *name, void *data, size_t data_size)
{
struct bin_attribute *attr;
- int err;
+ int err = 0;
attr = kzalloc_obj(*attr);
if (!attr)
@@ -8755,7 +8829,18 @@ struct bin_attribute *sysfs_btf_add(const char *name, void *data, size_t data_si
err = -ENOMEM;
goto err_free;
}
- err = sysfs_create_bin_file(btf_kobj, attr);
+ if (data_size > 0) {
+ err = sysfs_create_bin_file(btf_kobj, attr);
+ } else {
+ struct kernfs_node *node;
+
+ node = __kernfs_create_file(btf_kobj->sd, attr->attr.name,
+ attr->attr.mode, GLOBAL_ROOT_UID,
+ GLOBAL_ROOT_GID, data_size,
+ &sysfs_btf_kernfs_ops, attr, NULL, NULL);
+ if (IS_ERR(node))
+ err = PTR_ERR(node);
+ }
if (err) {
pr_warn("failed to register [%s] BTF in sysfs: %d\n", name, err);
goto err_free;
@@ -8775,6 +8860,17 @@ struct bin_attribute *sysfs_btf_add(const char *name, void *data, size_t data_si
}
#endif
+#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
+static void sysfs_btf_update(struct bin_attribute *attr, void *data, size_t data_size)
+{
+ if (!attr)
+ return;
+ WRITE_ONCE(attr->private, data);
+ /* Publish data before its non-zero size makes it readable. */
+ smp_store_release(&attr->size, data_size);
+}
+#endif
+
#ifdef CONFIG_DEBUG_INFO_BTF_MODULES
#if IS_ENABLED(CONFIG_SYSFS)
static void sysfs_btf_remove(struct bin_attribute *attr)
@@ -8872,6 +8968,14 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
err = 0;
goto out;
}
+ if (strcmp(mod->name, "btf_vmlinux_inline") == 0) {
+ if (vmlinux_inline_attr)
+ sysfs_btf_update(vmlinux_inline_attr, data,
+ mod->btf_inline_data_size);
+ else
+ kvfree(data);
+ break;
+ }
snprintf(name, sizeof(name), "%s.inline", mod->name);
attr = sysfs_btf_add(name, data, mod->btf_inline_data_size);
if (IS_ERR(attr)) {
@@ -8937,6 +9041,12 @@ static struct notifier_block btf_module_nb = {
static int __init btf_module_init(void)
{
register_module_notifier(&btf_module_nb);
+#if IS_MODULE(CONFIG_DEBUG_INFO_BTF_INLINE)
+ /* Attribute data will be filled in on-demand if vmlinux.inline is read. */
+ vmlinux_inline_attr = sysfs_btf_add("vmlinux.inline", NULL, 0);
+ if (IS_ERR(vmlinux_inline_attr))
+ vmlinux_inline_attr = NULL;
+#endif
return 0;
}
diff --git a/kernel/bpf/btf_vmlinux_inline.c b/kernel/bpf/btf_vmlinux_inline.c
index b155df9849b9..13ed962ffceb 100644
--- a/kernel/bpf/btf_vmlinux_inline.c
+++ b/kernel/bpf/btf_vmlinux_inline.c
@@ -26,5 +26,12 @@ static int __init btf_vmlinux_inline_init(void)
}
subsys_initcall(btf_vmlinux_inline_init);
+#if IS_MODULE(CONFIG_DEBUG_INFO_BTF_INLINE)
+static void __exit btf_vmlinux_inline_fini(void)
+{
+}
+module_exit(btf_vmlinux_inline_fini);
+#endif
+
MODULE_DESCRIPTION("BTF inline information for vmlinux");
MODULE_LICENSE("GPL");
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index dd1b2d9ebe99..1b96d6acfdce 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -439,9 +439,10 @@ config DEBUG_INFO_BTF_MODULES
Generate compact split BTF type information for kernel modules.
config DEBUG_INFO_BTF_INLINE
- bool "Provide information about inline sites in BTF"
+ tristate "Provide information about inline sites in BTF"
default n
depends on DEBUG_INFO_BTF && PAHOLE_HAS_INLINE && SYSFS
+ select DEBUG_INFO_BTF_MODULES if MODULES
help
Generate information about inline sites in .BTF.inline sections.
These sections contain split BTF relative to the kernel or module BTF
diff --git a/scripts/gen-btf.sh b/scripts/gen-btf.sh
index a75f41878c32..cd6588588fb8 100755
--- a/scripts/gen-btf.sh
+++ b/scripts/gen-btf.sh
@@ -93,7 +93,16 @@ gen_btf_o()
--set-section-flags .BTF=alloc,readonly ${btf_data}
ONLY_SEC="--only-section=.BTF"
btf_inline=${ELF_FILE}.BTF.inline
- if [ -n "${BTF_INLINE}" ] && [ -f "${btf_inline}" ]; then
+ if [ "${BTF_INLINE}" = "m" ]; then
+ # vmlinux BTF is generated from a temporary ELF. Retain its
+ # vmlinux-relative inline BTF for btf_vmlinux_inline.ko.
+ if [ -f "${btf_inline}" ]; then
+ cp "${btf_inline}" "${objtree}/vmlinux.BTF.inline"
+ else
+ rm -f "${objtree}/vmlinux.BTF.inline"
+ fi
+ fi
+ if [ "${BTF_INLINE}" = "y" ] && [ -f "${btf_inline}" ]; then
${OBJCOPY} --add-section .BTF.inline=${btf_inline} \
--set-section-flags .BTF.inline=alloc,readonly ${btf_data}
ONLY_SEC="${ONLY_SEC} --only-section=.BTF.inline"
@@ -120,6 +129,13 @@ embed_btf_data()
${OBJCOPY} --add-section .BTF.base=${btf_base} ${ELF_FILE}
fi
btf_inline=${ELF_FILE}.BTF.inline
+ case "${ELF_FILE}" in
+ */btf_vmlinux_inline.ko)
+ # With CONFIG_DEBUG_INFO_BTF_INLINE=m, deliver vmlinux
+ # .BTF.inline via module
+ btf_inline=${BTF_BASE}.BTF.inline
+ ;;
+ esac
if [ -n "${BTF_INLINE}" ] && [ -f "${btf_inline}" ]; then
${OBJCOPY} --add-section .BTF.inline=${btf_inline} ${ELF_FILE}
fi
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 16/18] btf: Relocate inline BTF for modules with distilled base BTF
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (14 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 15/18] btf: Support CONFIG_DEBUG_INFO_BTF_INLINE=m Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:29 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 17/18] selftests/bpf: Test BTF sysfs inline representations Alan Maguire
2026-09-01 16:57 ` [PATCH v2 bpf-next 18/18] selftests/bpf: Add a test verifying inline information Alan Maguire
17 siblings, 2 replies; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Module .BTF.inline is split relative to the module BTF. For external
modules, the module BTF is initially split relative to a distilled
.BTF.base and is relocated to vmlinux when the module is loaded.
Add a common split-BTF parser that accepts an explicit base BTF. Parse
ordinary module inline BTF relative to the original module BTF, retain the
module relocation ID map, then rewrite inline type IDs and string offsets
for the relocated vmlinux/module ID space before publishing it in sysfs.
A failure to parse or relocate optional inline BTF must not prevent
regular module BTF from being registered. Warn and omit only the inline
sysfs representation in that case.
This supports valid inline BTF for cases:
vmlinux -> module BTF -> module inline BTF
distilled vmlinux base -> module BTF -> module inline BTF
and:
vmlinux -> vmlinux inline BTF
Inline BTF can also inherit strings from an external module’s distilled
.BTF.base. Preserve the string-relocation map produced while relocating
the module BTF, and use it to rewrite those inherited inline string
offsets to their vmlinux equivalents. Without this, any shared
distilled-base string causes inline-BTF relocation to fail and its sysfs
representation to be omitted.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Assisted-by: OpenAI Codex (gpt-5.6)
---
include/linux/btf.h | 5 +-
kernel/bpf/btf.c | 263 ++++++++++++++++++++++++--------
tools/lib/bpf/btf.c | 2 +-
tools/lib/bpf/btf_relocate.c | 7 +-
tools/lib/bpf/libbpf_internal.h | 3 +-
5 files changed, 213 insertions(+), 67 deletions(-)
diff --git a/include/linux/btf.h b/include/linux/btf.h
index 93f10d3ccabe..702ae246052f 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -588,7 +588,8 @@ struct btf_field_iter {
#ifdef CONFIG_BPF_SYSCALL
const struct btf_type *btf_type_by_id(const struct btf *btf, u32 type_id);
void btf_set_base_btf(struct btf *btf, const struct btf *base_btf);
-int btf_relocate(struct btf *btf, const struct btf *base_btf, __u32 **map_ids);
+int btf_relocate(struct btf *btf, const struct btf *base_btf, __u32 **map_ids,
+ __u32 **map_strs);
int btf_field_iter_init(struct btf_field_iter *it, struct btf_type *t,
enum btf_field_iter_kind iter_kind);
__u32 *btf_field_iter_next(struct btf_field_iter *it);
@@ -640,7 +641,7 @@ static inline void btf_set_base_btf(struct btf *btf, const struct btf *base_btf)
}
static inline int btf_relocate(void *log, struct btf *btf, const struct btf *base_btf,
- __u32 **map_ids)
+ __u32 **map_ids, __u32 **map_strs)
{
return -EOPNOTSUPP;
}
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 2ac1f1d39660..2f3e8cea7dfc 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -1943,7 +1943,7 @@ void btf_set_base_btf(struct btf *btf, const struct btf *base_btf)
{
btf->base_btf = (struct btf *)base_btf;
btf->start_id = btf_nr_types(base_btf);
- btf->start_str_off = base_btf->hdr.str_len;
+ btf->start_str_off = base_btf->start_str_off + base_btf->hdr.str_len;
}
static int env_resolve_init(struct btf_verifier_env *env)
@@ -6727,15 +6727,140 @@ __u32 btf_relocate_id(const struct btf *btf, __u32 id)
#ifdef CONFIG_DEBUG_INFO_BTF_MODULES
+static struct btf *btf_parse_split(struct btf_verifier_env *env, const char *name,
+ const void *data, unsigned int data_size,
+ struct btf *base_btf)
+{
+ struct btf *btf;
+ int err;
+
+ btf = kzalloc_obj(*btf, GFP_KERNEL | __GFP_NOWARN);
+ if (!btf)
+ return ERR_PTR(-ENOMEM);
+ env->btf = btf;
+
+ btf_set_base_btf(btf, base_btf);
+ btf->kernel_btf = true;
+ btf->named_start_id = 0;
+ strscpy(btf->name, name);
+
+ btf->data = kvmemdup(data, data_size, GFP_KERNEL | __GFP_NOWARN);
+ if (!btf->data) {
+ err = -ENOMEM;
+ goto errout;
+ }
+ btf->data_size = data_size;
+
+ err = btf_parse_hdr(env);
+ if (err)
+ goto errout;
+
+ btf->nohdr_data = btf->data + btf->hdr.hdr_len;
+
+ err = btf_parse_str_sec(env);
+ if (err)
+ goto errout;
+
+ err = btf_check_all_metas(env);
+ if (err)
+ goto errout;
+
+ err = btf_check_modifier_chain_length(env, btf, btf_nr_types(base_btf));
+ if (err)
+ goto errout;
+
+ return btf;
+
+errout:
+ btf_free(btf);
+ return ERR_PTR(err);
+}
+
+static int btf_rebase_inline(struct btf *inline_btf, const struct btf *module_btf,
+ const u32 *module_id_map, const u32 *module_str_map,
+ u32 old_module_type_cnt)
+{
+ u32 old_start_id = inline_btf->start_id;
+ u32 old_start_str_off = inline_btf->start_str_off;
+ u32 old_module_start_str_off = old_start_str_off - module_btf->hdr.str_len;
+ u32 new_start_id = btf_nr_types(module_btf);
+ u32 new_start_str_off = module_btf->start_str_off + module_btf->hdr.str_len;
+ s64 id_delta = (s64)new_start_id - old_start_id;
+ s64 str_delta = (s64)new_start_str_off - old_start_str_off;
+ u32 i;
+
+ /*
+ * The inline BTF was parsed relative to the original module BTF. Its
+ * base IDs must therefore use the map generated when that BTF was
+ * relocated, while IDs for inline-local types only move by the change
+ * in the module BTF's starting ID.
+ */
+ for (i = 0; i < inline_btf->nr_types; i++) {
+ struct btf_field_iter it;
+ struct btf_type *t = inline_btf->types[i];
+ u32 *id, *str_off;
+ int err;
+
+ err = btf_field_iter_init(&it, t, BTF_FIELD_ITER_IDS);
+ if (err)
+ return err;
+ while ((id = btf_field_iter_next(&it))) {
+ if (!*id)
+ continue;
+ if (*id < old_module_type_cnt) {
+ if (module_id_map)
+ *id = module_id_map[*id];
+ } else if (*id >= old_start_id) {
+ *id += id_delta;
+ } else {
+ return -EINVAL;
+ }
+ }
+
+ err = btf_field_iter_init(&it, t, BTF_FIELD_ITER_STRS);
+ if (err)
+ return err;
+ while ((str_off = btf_field_iter_next(&it))) {
+ if (!*str_off)
+ continue;
+ /*
+ * LOCSEC names its code section in the module BTF's string
+ * section. Shift those inherited strings together with
+ * inline-local strings when replacing a distilled base BTF.
+ */
+ if (*str_off < old_module_start_str_off) {
+ /* vmlinux strings retain their offsets for in-tree modules. */
+ if (!module_id_map)
+ continue;
+ if (!module_str_map || !module_str_map[*str_off])
+ return -EINVAL;
+ *str_off = module_str_map[*str_off];
+ continue;
+ }
+ *str_off += str_delta;
+ }
+ }
+
+ btf_set_base_btf(inline_btf, module_btf);
+ btf_check_sorted(inline_btf);
+ return 0;
+}
+
static struct btf *btf_parse_module(const char *module_name, const void *data,
unsigned int data_size, void *base_data,
- unsigned int base_data_size)
+ unsigned int base_data_size, const void *inline_data,
+ unsigned int inline_data_size, bool vmlinux_inline,
+ void **relocated_inline_data)
{
- struct btf *btf = NULL, *vmlinux_btf, *base_btf = NULL;
+ struct btf *btf = NULL, *inline_btf = NULL, *vmlinux_btf, *base_btf = NULL;
+ struct btf *inline_base_btf;
struct btf_verifier_env *env = NULL;
struct bpf_verifier_log *log;
+ u32 old_module_type_cnt;
+ u32 *module_str_map = NULL;
int err = 0;
+ *relocated_inline_data = NULL;
vmlinux_btf = bpf_get_btf_vmlinux();
if (IS_ERR(vmlinux_btf))
return vmlinux_btf;
@@ -6759,67 +6884,75 @@ static struct btf *btf_parse_module(const char *module_name, const void *data,
base_btf = vmlinux_btf;
}
- btf = kzalloc_obj(*btf, GFP_KERNEL | __GFP_NOWARN);
- if (!btf) {
- err = -ENOMEM;
+ btf = btf_parse_split(env, module_name, data, data_size, base_btf);
+ if (IS_ERR(btf)) {
+ err = PTR_ERR(btf);
+ btf = NULL;
goto errout;
}
- env->btf = btf;
-
- btf->base_btf = base_btf;
- 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;
- strscpy(btf->name, module_name);
- btf->data = kvmemdup(data, data_size, GFP_KERNEL | __GFP_NOWARN);
- if (!btf->data) {
- err = -ENOMEM;
- goto errout;
+ if (inline_data_size) {
+ /*
+ * Ordinary module inline BTF is split relative to the module BTF.
+ * The btf_vmlinux_inline delivery module instead carries BTF split
+ * directly relative to vmlinux.
+ */
+ inline_base_btf = vmlinux_inline ? vmlinux_btf : btf;
+ inline_btf = btf_parse_split(env, module_name, inline_data,
+ inline_data_size, inline_base_btf);
+ if (IS_ERR(inline_btf)) {
+ pr_warn("failed to validate module [%s] inline BTF: %ld\n",
+ module_name, PTR_ERR(inline_btf));
+ inline_btf = NULL;
+ }
}
- btf->data_size = data_size;
-
- err = btf_parse_hdr(env);
- if (err)
- goto errout;
-
- btf->nohdr_data = btf->data + btf->hdr.hdr_len;
-
- err = btf_parse_str_sec(env);
- if (err)
- goto errout;
-
- err = btf_check_all_metas(env);
- if (err)
- goto errout;
-
- err = btf_check_modifier_chain_length(env, btf, btf_nr_types(base_btf));
- if (err)
- goto errout;
+ old_module_type_cnt = btf_nr_types(btf);
if (base_btf != vmlinux_btf) {
- err = btf_relocate(btf, vmlinux_btf, &btf->base_id_map);
+ err = btf_relocate(btf, vmlinux_btf, &btf->base_id_map, &module_str_map);
if (err)
goto errout;
btf_free(base_btf);
base_btf = vmlinux_btf;
}
- btf_verifier_env_free(env);
+ if (inline_btf) {
+ if (!vmlinux_inline) {
+ err = btf_rebase_inline(inline_btf, btf, btf->base_id_map,
+ module_str_map, old_module_type_cnt);
+ if (err) {
+ pr_warn("failed to relocate module [%s] inline BTF: %d\n",
+ module_name, err);
+ btf_free(inline_btf);
+ inline_btf = NULL;
+ }
+ }
+ if (inline_btf) {
+ *relocated_inline_data = inline_btf->data;
+ inline_btf->data = NULL;
+ btf_free(inline_btf);
+ }
+ }
+
+ /*
+ * With a distilled base, btf_relocate() replaces the base BTF and
+ * rewrites string offsets. Check ordering only after that final BTF
+ * view has been established, so named_start_id describes the BTF used
+ * by name lookups.
+ */
btf_check_sorted(btf);
+ btf_verifier_env_free(env);
+ kvfree(module_str_map);
refcount_set(&btf->refcnt, 1);
return btf;
errout:
+ kvfree(module_str_map);
btf_verifier_env_free(env);
+ btf_free(inline_btf);
if (!IS_ERR(base_btf) && base_btf != vmlinux_btf)
btf_free(base_btf);
- if (btf) {
- kvfree(btf->data);
- kvfree(btf->types);
- kfree(btf);
- }
+ btf_free(btf);
return ERR_PTR(err);
}
@@ -8909,6 +9042,8 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
struct module *mod = module;
struct bin_attribute *attr;
struct btf *btf;
+ void *inline_data = NULL, *relocated_inline_data = NULL;
+ unsigned int inline_data_size = 0;
int err = 0;
if (mod->btf_data_size == 0 ||
@@ -8918,13 +9053,20 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
switch (op) {
case MODULE_STATE_COMING:
+#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
+ inline_data = mod->btf_inline_data;
+ inline_data_size = mod->btf_inline_data_size;
+#endif
btf_mod = kzalloc_obj(*btf_mod);
if (!btf_mod) {
err = -ENOMEM;
goto out;
}
btf = btf_parse_module(mod->name, mod->btf_data, mod->btf_data_size,
- mod->btf_base_data, mod->btf_base_data_size);
+ mod->btf_base_data, mod->btf_base_data_size,
+ inline_data, inline_data_size,
+ strcmp(mod->name, "btf_vmlinux_inline") == 0,
+ &relocated_inline_data);
if (IS_ERR(btf)) {
kfree(btf_mod);
if (!IS_ENABLED(CONFIG_MODULE_ALLOW_BTF_MISMATCH)) {
@@ -8939,6 +9081,7 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
err = btf_alloc_id(btf);
if (err) {
btf_free(btf);
+ kvfree(relocated_inline_data);
kfree(btf_mod);
goto out;
}
@@ -8947,6 +9090,9 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
mutex_lock(&btf_module_mutex);
btf_mod->module = module;
btf_mod->btf = btf;
+#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
+ btf_mod->btf_inline_data = relocated_inline_data;
+#endif
list_add(&btf_mod->list, &btf_modules);
mutex_unlock(&btf_module_mutex);
@@ -8958,32 +9104,26 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
btf_mod->sysfs_attr = attr;
#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
- if (mod->btf_inline_data_size > 0) {
+ if (relocated_inline_data) {
char name[MODULE_NAME_LEN + sizeof(".inline")];
- void *data;
- data = kvmemdup(mod->btf_inline_data, mod->btf_inline_data_size,
- GFP_KERNEL | __GFP_NOWARN);
- if (!data) {
- err = 0;
- goto out;
- }
if (strcmp(mod->name, "btf_vmlinux_inline") == 0) {
if (vmlinux_inline_attr)
- sysfs_btf_update(vmlinux_inline_attr, data,
- mod->btf_inline_data_size);
+ sysfs_btf_update(vmlinux_inline_attr,
+ relocated_inline_data, inline_data_size);
else
- kvfree(data);
+ kvfree(relocated_inline_data);
+ btf_mod->btf_inline_data = NULL;
break;
}
snprintf(name, sizeof(name), "%s.inline", mod->name);
- attr = sysfs_btf_add(name, data, mod->btf_inline_data_size);
+ attr = sysfs_btf_add(name, relocated_inline_data, inline_data_size);
if (IS_ERR(attr)) {
err = 0;
- kvfree(data);
+ kvfree(relocated_inline_data);
+ btf_mod->btf_inline_data = NULL;
goto out;
}
- btf_mod->btf_inline_data = data;
btf_mod->sysfs_inline_attr = attr;
}
#endif
@@ -9017,10 +9157,9 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
if (btf_mod->sysfs_attr)
sysfs_btf_remove(btf_mod->sysfs_attr);
#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
- if (btf_mod->sysfs_inline_attr) {
+ if (btf_mod->sysfs_inline_attr)
sysfs_btf_remove(btf_mod->sysfs_inline_attr);
- kvfree(btf_mod->btf_inline_data);
- }
+ kvfree(btf_mod->btf_inline_data);
#endif
purge_cand_cache(btf_mod->btf);
btf_put(btf_mod->btf);
diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
index 30f8c426d145..eea325ccf768 100644
--- a/tools/lib/bpf/btf.c
+++ b/tools/lib/bpf/btf.c
@@ -6630,7 +6630,7 @@ void btf_set_base_btf(struct btf *btf, const struct btf *base_btf)
int btf__relocate(struct btf *btf, const struct btf *base_btf)
{
- int err = btf_relocate(btf, base_btf, NULL);
+ int err = btf_relocate(btf, base_btf, NULL, NULL);
if (!err)
btf->owns_base = false;
diff --git a/tools/lib/bpf/btf_relocate.c b/tools/lib/bpf/btf_relocate.c
index df5fa4bd87d6..e55dd75f6c95 100644
--- a/tools/lib/bpf/btf_relocate.c
+++ b/tools/lib/bpf/btf_relocate.c
@@ -441,7 +441,8 @@ static int btf_relocate_rewrite_strs(struct btf_relocate *r, __u32 i)
/* If successful, output of relocation is updated BTF with base BTF pointing
* at base_btf, and type ids, strings adjusted accordingly.
*/
-int btf_relocate(struct btf *btf, const struct btf *base_btf, __u32 **id_map)
+int btf_relocate(struct btf *btf, const struct btf *base_btf, __u32 **id_map,
+ __u32 **str_map)
{
unsigned int nr_types = btf__type_cnt(btf);
const struct btf_header *dist_base_hdr;
@@ -512,6 +513,10 @@ int btf_relocate(struct btf *btf, const struct btf *base_btf, __u32 **id_map)
*id_map = r.id_map;
r.id_map = NULL;
}
+ if (str_map) {
+ *str_map = r.str_map;
+ r.str_map = NULL;
+ }
err_out:
free(r.id_map);
free(r.str_map);
diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
index ebac8db1ccfd..e7a6219374a9 100644
--- a/tools/lib/bpf/libbpf_internal.h
+++ b/tools/lib/bpf/libbpf_internal.h
@@ -249,7 +249,8 @@ const char *btf_kind_str(const struct btf_type *t);
const struct btf_type *skip_mods_and_typedefs(const struct btf *btf, __u32 id, __u32 *res_id);
const struct btf_header *btf_header(const struct btf *btf);
void btf_set_base_btf(struct btf *btf, const struct btf *base_btf);
-int btf_relocate(struct btf *btf, const struct btf *base_btf, __u32 **id_map);
+int btf_relocate(struct btf *btf, const struct btf *base_btf, __u32 **id_map,
+ __u32 **str_map);
bool btf_type_is_traceable_func(const struct btf *btf, const struct btf_type *t);
static inline enum btf_func_linkage btf_func_linkage(const struct btf_type *t)
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 17/18] selftests/bpf: Test BTF sysfs inline representations
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (15 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 16/18] btf: Relocate inline BTF for modules with distilled base BTF Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:22 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 18/18] selftests/bpf: Add a test verifying inline information Alan Maguire
17 siblings, 2 replies; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
Load vmlinux, vmlinux.inline and all module and module.inline
entries in /sys/fs/btf; this will allow us to sanity-check
- kernel and its inline representations
- in-tree modules and their inline multi-split BTF representations
- out-of-tree modules and the kernel relocation done for
module and module inline BTF
Since bpf_testmod.ko is built as an out-of-tree module, the
combination of vmlinux, normal in-tree module BTF and out-of-tree
testmod ensures we sanity check relocations for each case.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
.../selftests/bpf/prog_tests/btf_sysfs.c | 74 +++++++++++++++++++
1 file changed, 74 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c b/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c
index 3923e64c4c1d..97eccfd7c134 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c
@@ -3,11 +3,16 @@
#include <test_progs.h>
#include <bpf/btf.h>
+#include <dirent.h>
+#include <limits.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
+#define BTF_SYSFS_DIR "/sys/kernel/btf"
+#define BTF_INLINE_SUFFIX ".inline"
+
static void test_btf_mmap_sysfs(const char *path, struct btf *base)
{
struct stat st;
@@ -75,7 +80,76 @@ static void test_btf_mmap_sysfs(const char *path, struct btf *base)
close(fd);
}
+static void test_btf_inline_sysfs_all(void)
+{
+ struct btf *vmlinux_btf;
+ struct dirent *dentry;
+ DIR *dir;
+ int err = 0;
+
+ dir = opendir(BTF_SYSFS_DIR);
+ if (!ASSERT_OK_PTR(dir, "open_btf_sysfs"))
+ return;
+
+ vmlinux_btf = btf__parse(BTF_SYSFS_DIR "/vmlinux", NULL);
+ if (!ASSERT_OK_PTR(vmlinux_btf, "parse_vmlinux_btf")) {
+ closedir(dir);
+ return;
+ }
+
+ while ((dentry = readdir(dir)) != NULL) {
+ struct btf *base_btf = NULL, *module_btf = NULL, *inline_btf = NULL;
+ char btf_path[PATH_MAX], inline_path[PATH_MAX];
+ struct stat st;
+
+ /* Skip ".", ".." and "foo.inline" */
+ if (strstr(dentry->d_name, "."))
+ continue;
+
+ if (strcmp(dentry->d_name, "vmlinux") == 0)
+ base_btf = vmlinux_btf;
+
+ if (snprintf(btf_path, sizeof(btf_path), "%s/%s",
+ BTF_SYSFS_DIR, dentry->d_name) >= sizeof(btf_path) ||
+ snprintf(inline_path, sizeof(inline_path), "%s/%s%s",
+ BTF_SYSFS_DIR, dentry->d_name, BTF_INLINE_SUFFIX) >=
+ sizeof(inline_path)) {
+ ASSERT_FAIL("BTF sysfs path is too long\n");
+ break;
+ }
+
+ if (!base_btf) {
+ module_btf = btf__parse_split(btf_path, vmlinux_btf);
+ err = libbpf_get_error(module_btf);
+ if (err) {
+ /* A module can be unloaded while its sysfs entry is iterated. */
+ if (err == -ENOENT)
+ continue;
+ ASSERT_OK(err, "parse_module_btf");
+ continue;
+ }
+ base_btf = module_btf;
+ }
+ if (stat(inline_path, &st)) {
+ err = errno;
+ if (err == ENOENT)
+ continue;
+ ASSERT_OK(err, "stat_inline_btf");
+ }
+ inline_btf = btf__parse_split(inline_path, base_btf);
+ err = libbpf_get_error(inline_btf);
+ if (!err)
+ btf__free(inline_btf);
+ ASSERT_OK(err, "parse_inline_btf");
+ btf__free(module_btf);
+ }
+ closedir(dir);
+
+ btf__free(vmlinux_btf);
+}
+
void test_btf_sysfs(void)
{
test_btf_mmap_sysfs("/sys/kernel/btf/vmlinux", NULL);
+ test_btf_inline_sysfs_all();
}
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v2 bpf-next 18/18] selftests/bpf: Add a test verifying inline information
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
` (16 preceding siblings ...)
2026-09-01 16:57 ` [PATCH v2 bpf-next 17/18] selftests/bpf: Test BTF sysfs inline representations Alan Maguire
@ 2026-09-01 16:57 ` Alan Maguire
2026-09-01 17:28 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
17 siblings, 2 replies; 42+ messages in thread
From: Alan Maguire @ 2026-09-01 16:57 UTC (permalink / raw)
To: ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, Alan Maguire
For bpf_testmod verify that we have inline info for an
__always_inline'd function and it matches reasonable
expectations (a single location parameter encoded in a
register given that it cannot be compile-time optimized).
Also verify that the offset of the LOCSEC descriptor makes
sense, i.e. that it is in the range of the function where
it was inlined.
Because bpf_testmod is treated as an out-of-tree module,
the inline information will be in btf_testmod.inline which
is relocated using bpf_testmod.ko .BTF.base.
Test is skipped if pahole does not encode inline info.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
.../selftests/bpf/prog_tests/btf_inline.c | 110 ++++++++++++++++++
.../selftests/bpf/test_kmods/bpf_testmod.c | 2 +-
tools/testing/selftests/bpf/trace_helpers.c | 20 ++++
tools/testing/selftests/bpf/trace_helpers.h | 1 +
4 files changed, 132 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_inline.c
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_inline.c b/tools/testing/selftests/bpf/prog_tests/btf_inline.c
new file mode 100644
index 000000000000..8e0a85f6c698
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_inline.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026, Oracle and/or its affiliates. */
+
+#include <test_progs.h>
+#include <bpf/btf.h>
+#include <bpf/libbpf.h>
+
+#define BTF_SYSFS_DIR "/sys/kernel/btf"
+#define BTF_INLINE_SUFFIX ".inline"
+
+/*
+ * For a specific inline site, verify we have the right function,
+ * loc proto and loc param representation and that the offset is
+ * reasonable given the caller where it was inlined.
+ *
+ * Because bpf_testmod is compiled "out-of-tree" we have inline
+ * information in the split BTF directly rather than in btf_testmod.inline.
+ */
+void test_btf_inline(void)
+{
+ struct btf *inline_btf = NULL, *btf = NULL, *vmlinux_btf = NULL;
+ const char *inline_caller = "bpf_testmod_uprobe_write";
+ const char *inline_func = "testmod_register_uprobe";
+ bool skip = false, found_loc = false;
+ const struct btf_loc_param *lp;
+ int locsec_id, func_id, n, i;
+ long caller_addr, base_addr;
+ const struct btf_type *t;
+ struct btf_loc *l;
+ const __u32 *p;
+ int err = 0;
+
+ if (!env.has_testmod) {
+ test__skip();
+ return;
+ }
+
+ base_addr = module_get_base_addr("bpf_testmod");
+ if (!ASSERT_NEQ(base_addr, 0, "base_addr_nonzero"))
+ return;
+
+ load_kallsyms();
+ caller_addr = ksym_get_addr(inline_caller);
+ if (!ASSERT_NEQ(caller_addr, 0, "caller_addr_nonzero"))
+ return;
+
+ if (!ASSERT_GT(caller_addr, base_addr, "caller_addr_gt_base_addr"))
+ return;
+ caller_addr -= base_addr;
+
+ vmlinux_btf = btf__load_vmlinux_btf();
+ if (!ASSERT_OK_PTR(vmlinux_btf, "vmlinux_btf"))
+ return;
+
+ btf = btf__parse_split(BTF_SYSFS_DIR "/bpf_testmod", vmlinux_btf);
+ if (!ASSERT_OK_PTR(btf, "bpf_testmod_btf"))
+ goto out;
+
+ inline_btf = btf__parse_split(BTF_SYSFS_DIR "/bpf_testmod" BTF_INLINE_SUFFIX,
+ btf);
+ err = libbpf_get_error(inline_btf);
+ /* pahole may not have inline BTF feature support. */
+ if (err == -ENOENT) {
+ skip = true;
+ goto out;
+ }
+ locsec_id = btf__find_by_name_kind(inline_btf, ".text", BTF_KIND_LOCSEC);
+ if (locsec_id < 0) {
+ skip = true;
+ goto out;
+ }
+ func_id = btf__find_by_name_kind(inline_btf, inline_func, BTF_KIND_FUNC);
+ if (!ASSERT_GT(func_id, 0, "inline_caller_func"))
+ goto out;
+ t = btf__type_by_id(inline_btf, locsec_id);
+ n = btf_vlen(t);
+ for (i = 0, l = btf_locsec_locs(t); i < n; i++, l++) {
+ if (l->func == func_id) {
+ found_loc = true;
+ break;
+ }
+ }
+ if (!ASSERT_TRUE(found_loc, "found_loc"))
+ goto out;
+ if (!ASSERT_GT(l->loc_proto, 0, "loc_proto_id"))
+ goto out;
+ if (!ASSERT_GT(l->offset, 0, "loc_offset"))
+ goto out;
+ t = btf__type_by_id(inline_btf, l->loc_proto);
+ if (!ASSERT_OK_PTR(t, "loc_proto_ptr"))
+ goto out;
+ if (!ASSERT_EQ(btf_vlen(t), 1, "loc_proto_one_param"))
+ goto out;
+ p = btf_loc_proto_params(t);
+ t = btf__type_by_id(inline_btf, *p);
+ lp = btf_loc_param(t);
+ if (!ASSERT_EQ(lp->flags, BTF_LOC_PARAM_REG, "param_is_reg"))
+ goto out;
+ if (!ASSERT_GT(l->offset, caller_addr, "inline_gt_caller"))
+ goto out;
+ /* simple sanity test to roughly ensure inline site still in function */
+ if (ASSERT_LT(l->offset, caller_addr + 256, "inline_in_caller"))
+ goto out;
+out:
+ btf__free(inline_btf);
+ btf__free(btf);
+ btf__free(vmlinux_btf);
+ if (skip)
+ test__skip();
+}
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 2380b6cbdead..09d910d5e312 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -750,7 +750,7 @@ static struct testmod_uprobe uprobe = {
.consumer.ret_handler = uprobe_ret_handler,
};
-static int testmod_register_uprobe(loff_t offset)
+static __always_inline int testmod_register_uprobe(loff_t offset)
{
int err = -EBUSY;
diff --git a/tools/testing/selftests/bpf/trace_helpers.c b/tools/testing/selftests/bpf/trace_helpers.c
index 679008b310d9..bf4cb720e7b8 100644
--- a/tools/testing/selftests/bpf/trace_helpers.c
+++ b/tools/testing/selftests/bpf/trace_helpers.c
@@ -244,6 +244,26 @@ int kallsyms_find(const char *sym, unsigned long long *addr)
return err;
}
+long module_get_base_addr(const char *module)
+{
+ unsigned long long addr, base_addr = 0;
+ char name[500];
+ FILE *f;
+
+ f = fopen("/proc/modules", "r");
+ if (!f)
+ return 0;
+
+ while (fscanf(f, "%s %*s %*s %*s %*s 0x%llx", name, &addr) > 0) {
+ if (strcmp(module, name) == 0) {
+ base_addr = addr;
+ break;
+ }
+ }
+ fclose(f);
+ return base_addr;
+}
+
#ifdef PROCMAP_QUERY
int env_verbosity __weak = 0;
diff --git a/tools/testing/selftests/bpf/trace_helpers.h b/tools/testing/selftests/bpf/trace_helpers.h
index 01c8ecc45627..473508306f79 100644
--- a/tools/testing/selftests/bpf/trace_helpers.h
+++ b/tools/testing/selftests/bpf/trace_helpers.h
@@ -38,6 +38,7 @@ typedef int (*ksym_search_cmp_t)(const void *p1, const struct ksym *p2);
int load_kallsyms(void);
struct ksym *ksym_search(long key);
long ksym_get_addr(const char *name);
+long module_get_base_addr(const char *module);
struct ksyms *load_kallsyms_local(void);
struct ksym *ksym_search_local(struct ksyms *ksyms, long key);
--
2.43.5
^ permalink raw reply related [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 04/18] selftests/bpf: Test helper support for BTF_KIND_LOC[_PARAM|_PROTO|SEC]
2026-09-01 16:57 ` [PATCH v2 bpf-next 04/18] selftests/bpf: Test helper support for BTF_KIND_LOC[_PARAM|_PROTO|SEC] Alan Maguire
@ 2026-09-01 17:06 ` sashiko-bot
0 siblings, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:06 UTC (permalink / raw)
To: Alan Maguire; +Cc: linux-modules, bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Incorrect format specifiers `%d` used for unsigned `__u32` variables when formatting BTF_KIND_LOC_PARAM and BTF_KIND_LOCSEC.
--
commit 775b46a26f73799ae68ad5737114128e343d1d80
Author: Alan Maguire <alan.maguire@oracle.com>
selftests/bpf: Test helper support for BTF_KIND_LOC[_PARAM|_PROTO|SEC]
Add support to dump, encode and validate new location-related kinds.
> diff --git a/tools/testing/selftests/bpf/btf_helpers.c b/tools/testing/selftests/bpf/btf_helpers.c
> index 1c1c2c26690a7..6065ee4ba00a6 100644
> --- a/tools/testing/selftests/bpf/btf_helpers.c
> +++ b/tools/testing/selftests/bpf/btf_helpers.c
[ ... ]
> @@ -203,6 +206,37 @@ int fprintf_btf_type_raw(FILE *out, const struct btf *btf, __u32 id)
> fprintf(out, " type_id=%u component_idx=%d",
> t->type, btf_decl_tag(t)->component_idx);
> break;
> + case BTF_KIND_LOC_PARAM: {
> + struct btf_loc_param *p = btf_loc_param(t);
> + __u32 *v = (__u32 *)(p + 1);
> +
> + fprintf(out, " size=%d flags=0x%x vlen=%u", t->size, p->flags, vlen);
[Severity: Low]
Does this format string interpret the unsigned __u32 t->size as a signed
integer? If t->size exceeds 2^31-1, it could be printed as a negative number
here, which differs from how other BTF kinds format their sizes using %u.
> + for (i = 0; i < vlen; i++, v++) {
> + if (p->flags & BTF_LOC_PARAM_SIGNED)
> + fprintf(out, "\n\tvalue=%d", (__s32)*v);
> + else
> + fprintf(out, "\n\tvalue=%u", *v);
> + }
> + break;
> + }
[ ... ]
> + case BTF_KIND_LOCSEC: {
> + const struct btf_loc *l = btf_locsec_locs(t);
> +
> + fprintf(out, " vlen=%u", vlen);
> + for (i = 0; i < vlen; i++, l++) {
> + fprintf(out, "\n\tfunc_type_id=%u loc_proto_type_id=%u offset=%d",
> + l->func, l->loc_proto, l->offset);
[Severity: Low]
Similarly, l->offset is defined as __u32 in struct btf_loc but is formatted
with %d. Should this use %u as well to prevent large offsets from appearing
negative in the output?
> + }
> + break;
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=4
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 02/18] libbpf: Add support for BTF kinds LOC[_PARAM|_PROTO|SEC]
2026-09-01 16:57 ` [PATCH v2 bpf-next 02/18] libbpf: Add support for BTF kinds LOC[_PARAM|_PROTO|SEC] Alan Maguire
@ 2026-09-01 17:11 ` sashiko-bot
0 siblings, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:11 UTC (permalink / raw)
To: Alan Maguire; +Cc: linux-modules, bpf
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] btf_equal_loc_param() fails to compare the flags field of BTF_KIND_LOC_PARAM, leading to incorrect deduplication.
- [Medium] Public libbpf API functions (e.g., btf__add_locsec, btf__add_loc_param, btf__add_loc_proto) return negative error codes directly without using the required libbpf_err() wrapper.
- [Low] The m_offs array in struct btf_field_desc is mistakenly inflated to size 22 instead of 2, unnecessarily wasting stack memory.
--
commit ce9959576588b24d58d6a9f71651d919ddebad03
Author: Alan Maguire <alan.maguire@oracle.com>
Subject: libbpf: Add support for BTF kinds LOC[_PARAM|_PROTO|SEC]
This patch adds support for new BTF kinds to libbpf, including deduplication
logic, data accessors, and construction APIs. It also extends the BTF field
iterator to handle the new section types.
> diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
> index c783359977b46..9449f6f50f18f 100644
> --- a/tools/lib/bpf/btf.c
> +++ b/tools/lib/bpf/btf.c
[ ... ]
> +int btf__add_loc_param(struct btf *btf, __u32 size, __u32 flags)
> +{
[ ... ]
> + p = btf_loc_param(t);
> + p->flags = flags;
> +
> + return btf_commit_type(btf, sz);
[Severity: Medium]
Since btf__add_loc_param() is a public libbpf API, should its error return
paths be wrapped with libbpf_err() to ensure errno is set properly for
userspace callers? Here, btf_commit_type() can return a negative error code
directly.
> +}
[ ... ]
> +int btf__add_loc_proto(struct btf *btf)
> +{
[ ... ]
> + t->info = btf_type_info(BTF_KIND_LOC_PROTO, 0, 0);
> + t->size = 0;
> +
> + return btf_commit_type(btf, sizeof(struct btf_type));
[Severity: Medium]
Does this unwrapped negative return code also apply to btf__add_loc_proto()?
> +}
[ ... ]
> +int btf__add_locsec(struct btf *btf, const char *name)
> +{
[ ... ]
> + if (!str_is_empty(name)) {
> + name_off = btf__add_str(btf, name);
> + if (name_off < 0)
> + return name_off;
[Severity: Medium]
Could this leak a negative error code without setting errno? The name_off
result can be negative on failure, and btf__add_locsec is a public API.
> + }
> + t->name_off = name_off;
> + t->info = btf_type_info(BTF_KIND_LOCSEC, 0, 0);
> + t->size = 0;
> +
> + return btf_commit_type(btf, sizeof(struct btf_type));
[Severity: Medium]
Similarly, should the return value of btf_commit_type() be wrapped here?
> +}
[ ... ]
> +static bool btf_equal_loc_param(struct btf_type *t1, struct btf_type *t2)
> +{
> + struct btf_loc_param *p1 = btf_loc_param(t1);
> + struct btf_loc_param *p2 = btf_loc_param(t2);
> + __u32 *v1 = (__u32 *)(p1 + 1);
> + __u32 *v2 = (__u32 *)(p2 + 1);
> + int i, vlen = btf_vlen(t1);
> +
> + if (!btf_equal_common(t1, t2))
> + return false;
> + for (i = 0; i < vlen; i++, v1++, v2++) {
> + if (*v1 != *v2)
> + return false;
> + }
> + return true;
[Severity: High]
Does this loop need to compare the flags field of the two btf_loc_param
structures? The current logic checks the trailing parameters but appears
to skip checking if p1->flags equals p2->flags, which might cause
LOC_PARAM types with different metadata to be incorrectly deduplicated.
> +}
[ ... ]
> diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
> index cb4d962338441..ebac8db1ccfda 100644
> --- a/tools/lib/bpf/libbpf_internal.h
> +++ b/tools/lib/bpf/libbpf_internal.h
> @@ -580,7 +580,7 @@ struct btf_field_desc {
> /* member struct size, or zero, if no members */
> int m_sz;
> /* repeated per-member offsets */
> - int m_off_cnt, m_offs[1];
> + int m_off_cnt, m_offs[22];
[Severity: Low]
Is m_offs sized to 22 here because BTF_KIND_LOCSEC equals 22?
Since the iter_init function only specifies 2 offsets for LOCSEC (func and
loc_proto), could this array be sized to the maximum offset count needed
(e.g., 2) to avoid unnecessary stack memory consumption when
struct btf_field_desc is instantiated?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=2
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 10/18] bpftool: Document support for multi-split BTF
2026-09-01 16:57 ` [PATCH v2 bpf-next 10/18] bpftool: Document support for multi-split BTF Alan Maguire
@ 2026-09-01 17:12 ` sashiko-bot
0 siblings, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:12 UTC (permalink / raw)
To: Alan Maguire; +Cc: bpf, linux-modules
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] The synopsis in the manual page incorrectly implies a maximum of two base BTF options.
- [Low] The interactive help text for the `btf` subcommand was not updated to reflect multiple `-B` options.
--
commit 42ebee481eb7237c0598d57bed53fc37d0f50a61
Author: Alan Maguire <alan.maguire@oracle.com>
Subject: bpftool: Document support for multi-split BTF
This commit documents the ability to pass multiple levels of split BTF using
the "-B base_btf" options.
> diff --git a/tools/bpf/bpftool/Documentation/bpftool-btf.rst b/tools/bpf/bpftool/Documentation/bpftool-btf.rst
> index cf75a7fa2d6bc..60c46748b8663 100644
> --- a/tools/bpf/bpftool/Documentation/bpftool-btf.rst
> +++ b/tools/bpf/bpftool/Documentation/bpftool-btf.rst
> @@ -16,7 +16,7 @@ SYNOPSIS
>
> **bpftool** [*OPTIONS*] **btf** *COMMAND*
>
> -*OPTIONS* := { |COMMON_OPTIONS| | { **-B** | **--base-btf** } }
> +*OPTIONS* := { |COMMON_OPTIONS| | { **-B** | **--base-btf** [ **-B** | **--base-btf** ] } }
[Severity: Low]
Does this syntax incorrectly imply a maximum of two base BTF options? The text
below states that multiple base BTF objects can be passed, but the synopsis
seems to limit it to two.
Additionally, should the interactive help text be updated to match?
Looking at tools/bpf/bpftool/btf.c:do_help(), it still shows:
" " HELP_SPEC_OPTIONS " |\n"
" {-B|--base-btf} }\n"
which lacks the documented multi-split BTF option syntax.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=10
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 09/18] bpftool: Handle multi-split BTF by supporting multiple base BTFs
2026-09-01 16:57 ` [PATCH v2 bpf-next 09/18] bpftool: Handle multi-split BTF by supporting multiple base BTFs Alan Maguire
@ 2026-09-01 17:13 ` sashiko-bot
0 siblings, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:13 UTC (permalink / raw)
To: Alan Maguire; +Cc: bpf, linux-modules
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Memory leak of intermediate base BTF objects in the multi-split chain on the success path, and leak of the entire chain on the error path.
--
commit af485a13fcb0c9c0f013599abed8bb48b059b6e0
Author: Alan Maguire <alan.maguire@oracle.com>
bpftool: Handle multi-split BTF by supporting multiple base BTFs
This commit updates bpftool to support multi-split BTF by allowing multiple
base BTFs to be specified via multiple -B arguments.
> diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
> index 5ababd8f7d0a0..9329f140b700f 100644
> --- a/tools/bpf/bpftool/main.c
> +++ b/tools/bpf/bpftool/main.c
> @@ -515,7 +515,8 @@ int main(int argc, char **argv)
> verifier_logs = true;
> break;
> case 'B':
> - base_btf = btf__parse(optarg, NULL);
> + /* handle multi-split BTF */
> + base_btf = btf__parse_split(optarg, base_btf);
[Severity: Medium]
Does this code leak the previous base_btf objects on error?
If a user supplies multiple -B arguments and parsing fails on a subsequent
-B, the base_btf pointer is overwritten with NULL, losing the reference to
the prior chain.
Does this setup also leak the intermediate bases on the success path?
The chain is built properly, but at exit btf__free(base_btf) only frees the
top-level BTF because objects loaded via btf__parse_split() do not take
ownership (owns_base == false) of their parent base BTF.
> if (!base_btf) {
> p_err("failed to parse base BTF at '%s': %d\n",
> optarg, -errno);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=9
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 03/18] libbpf: Support moving permuted BTF types into split BTF
2026-09-01 16:57 ` [PATCH v2 bpf-next 03/18] libbpf: Support moving permuted BTF types into split BTF Alan Maguire
@ 2026-09-01 17:15 ` sashiko-bot
2026-09-01 18:14 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:15 UTC (permalink / raw)
To: Alan Maguire; +Cc: bpf, linux-modules
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] If `btf_compact_strings()` fails, `btf__permute()` returns an error but leaves the BTF object in an irreversibly truncated and permuted state without updating the caller's `id_map`.
- [Low] LIBBPF-001 API rule violation: `btf_add_local_str` incorrectly uses `libbpf_err()` as an internal function, and `btf__add_str` fails to use it on its error return.
Pre-existing issues:
- [High] `btf__permute` completely corrupts base BTF permutations due to incorrect handling of `start_offs`, resulting in heap out-of-bounds accesses and total deletion of the VOID type.
--
commit 9de40ca1824ca5dbac1f65a763fe3a6beedba81f
Author: Alan Maguire <alan.maguire@oracle.com>
libbpf: Support moving permuted BTF types into split BTF
This commit extends btf__permute() with an optional transfer mode to move
specific permuted BTF types into a newly-created split BTF. It handles
remapping type IDs, migrating referenced strings, and compacting string
tables so that strings exclusive to the moved types reside only in the
split BTF.
> diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
> index 9449f6f50f18f..30f8c426d1453 100644
> --- a/tools/lib/bpf/btf.c
> +++ b/tools/lib/bpf/btf.c
> @@ -2147,16 +2147,9 @@ int btf__find_str(struct btf *btf, const char *s)
> * - > 0 offset into string section, on success;
> * - < 0, on error.
> */
> -int btf__add_str(struct btf *btf, const char *s)
> +static int btf_add_local_str(struct btf *btf, const char *s)
> {
> - int off;
> - int err;
> -
> - if (btf->base_btf) {
> - off = btf__find_str(btf->base_btf, s);
> - if (off != -ENOENT)
> - return off;
> - }
> + int off, err;
>
> err = btf_ensure_modifiable(btf);
> if (err)
> @@ -2167,10 +2160,24 @@ int btf__add_str(struct btf *btf, const char *s)
> return libbpf_err(off);
^^^^^^^^^^^^^^^^^^^^^^
[Severity: Low]
Does the internal static function btf_add_local_str() incorrectly use the
libbpf_err() wrapper here, while the public API btf__add_str() below fails
to wrap its own error return?
>
> btf->hdr.str_len = strset__data_size(btf->strs_set);
> + btf->strs_deduped = false;
>
> return btf->start_str_off + off;
> }
>
> +int btf__add_str(struct btf *btf, const char *s)
> +{
> + int off;
> +
> + if (btf->base_btf) {
> + off = btf__find_str(btf->base_btf, s);
> + if (off != -ENOENT)
> + return off;
> + }
> +
> + return btf_add_local_str(btf, s);
> +}
[ ... ]
> @@ -6678,33 +6759,82 @@ int btf__permute(struct btf *btf, __u32 *id_map, __u32 id_map_cnt,
> if (err)
> goto done;
>
> + /*
> + * Build the inverse map: for each requested destination ID, record the
> + * original type ID that will occupy it. order_map is indexed by the
> + * destination ID relative to the first ID represented by id_map. The
> + * caller id_map maps old IDs to requested destinations, but BTF types
> + * must be emitted in destination-ID order; the inverse map supplies the
> + * old type to copy for each output slot without repeatedly searching
> + * id_map.
> + */
> for (i = start_offs; i < id_map_cnt; i++) {
> - id = id_map[i];
> - if (id < btf->start_id || id >= btf__type_cnt(btf)) {
> + __u32 requested_id = new_id_map[i];
> + __u32 order_idx;
> +
> + if (btf_permute_id_is_transfer(requested_id)) {
> + if (!transfer_btfp) {
> + err = -EINVAL;
> + goto done;
> + }
> + requested_id &= ~BTF_PERMUTE_ID_TRANSFER;
> + nr_transfer++;
> + }
> + if (requested_id < btf->start_id || requested_id >= btf__type_cnt(btf)) {
> err = -EINVAL;
> goto done;
> }
> - id -= btf->start_id - start_offs;
> + order_idx = requested_id - btf->start_id + start_offs;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
This is a pre-existing issue, but does the order_idx calculation go out of
bounds for base BTFs?
For a base BTF, btf->start_id is 0 and start_offs is 1. If requested_id
reaches id_map_cnt - 1 (the maximum valid ID), order_idx will become
id_map_cnt.
This would cause an out-of-bounds access when writing to order_map[order_idx]
below, since order_map is allocated with size id_map_cnt.
> /* cannot be mapped to the same ID */
> - if (order_map[id]) {
> + if (order_map[order_idx]) {
> err = -EINVAL;
> goto done;
> }
> - order_map[id] = i + btf->start_id - start_offs;
> + order_map[order_idx] = i + btf->start_id - start_offs;
> + }
[ ... ]
> @@ -6729,18 +6916,58 @@ int btf__permute(struct btf *btf, __u32 *id_map, __u32 id_map_cnt,
> goto done;
> }
>
> - for (nt = new_types, i = 0; i < id_map_cnt - start_offs; i++) {
> + /* Install the compacted parent types and rebuild their index. */
> + for (nt = new_types, i = 0; i < nr_base_types; i++) {
> btf->type_offs[i] = nt - new_types;
> nt += btf_type_size(btf, nt);
> }
> -
> - free(order_map);
> free(btf->types_data);
> btf->types_data = new_types;
> + committed = true;
> + btf_hdr_update_type_len(btf, nt - new_types);
> + btf->nr_types = nr_base_types;
> +
> + /*
> + * An important goal is to also transfer strings to the transfer
> + * BTF where they do not have duplicates in BTF. This is important
> + * for cases like inline BTF where inline function names could
> + * comprise a significant portion of the string table. The approach
> + * is to first compact the set of strings in the base now transfer
> + * is complete; this will remove unreferenced strings. Then
> + * local references in the transfer BTF have to be moved downwards
> + * based upon that compaction. Finally also compact transfer BTF
> + * references so that we replace duplicate references in split with
> + * base references where present.
> + *
> + * Once all this is done, we end up with transfer-only strings in
> + * transfer split BTF and shared strings in BTF. The result is
> + * a transfer BTF with a lot of transfer-only strings will not
> + * pollute the string table of the non-transfer BTF.
> + */
> + if (transfer_btf) {
> + err = btf_compact_strings(btf, btf_ext);
> + if (err)
> + goto done;
^^^^^^^^^
[Severity: High]
If btf_compact_strings() fails here, we return an error but the
btf->types_data replacement has already been committed.
Does this leave the BTF object in an irreversibly corrupted state from the
caller's perspective, since id_map is not updated on the error path and the
transferred types are lost?
> + err = btf_permute_rebase_split_strs(transfer_btf, old_str_off);
> + if (err)
> + goto done;
> + err = btf_compact_strings(transfer_btf, NULL);
> + if (err)
> + goto done;
> + *transfer_btfp = transfer_btf;
> + }
> +
> + /* On success return the final old-to-new type-ID map to the caller. */
> + memcpy(id_map, new_id_map, id_map_cnt * sizeof(*id_map));
> + free(new_id_map);
> + free(order_map);
> return 0;
>
> done:
> + free(new_id_map);
> free(order_map);
> - free(new_types);
> + if (!committed)
> + free(new_types);
> + btf__free(transfer_btf);
> return libbpf_err(err);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=3
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 08/18] selftests/bpf: Validate that btf__permute transfer works
2026-09-01 16:57 ` [PATCH v2 bpf-next 08/18] selftests/bpf: Validate that btf__permute transfer works Alan Maguire
@ 2026-09-01 17:16 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:16 UTC (permalink / raw)
To: Alan Maguire; +Cc: bpf, linux-modules
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Missing NULL check on btf__raw_data() return value leads to segfault and uninitialized size usage in btf__new().
--
commit 843335a53af929d0f8f7676cbbf1cb2a7296aacb
Author: Alan Maguire <alan.maguire@oracle.com>
selftests/bpf: Validate that btf__permute transfer works
This patch adds test coverage for the btf__permute() transfer mode. It
verifies type-ID remapping, LOCSEC record ordering in split BTF, and string
handling and deduplication.
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_permute.c b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
> index 04ade5ad77aca..ef75fd71df96e 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf_permute.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
[ ... ]
> +/* Permuting BTF with a layout section must keep section offsets in sync. */
> +static void test_permute_layout(void)
> +{
> + LIBBPF_OPTS(btf_new_opts, opts, .add_layout = true);
> + LIBBPF_OPTS(btf_permute_opts, permute_opts);
> + struct btf *btf, *parsed, *transfer_btf = NULL;
> + const void *raw;
> + __u32 raw_sz;
> + __u32 permute_ids[] = { 0, 1, BTF_PERMUTE_ID_TRANSFER | 2 };
> + int err;
> +
> + btf = btf__new_empty_opts(&opts);
> + if (!ASSERT_OK_PTR(btf, "empty_layout_btf"))
> + return;
> +
> + btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
> + btf__add_ptr(btf, 1);
> + permute_opts.transfer_btf = &transfer_btf;
> + err = btf__permute(btf, permute_ids, ARRAY_SIZE(permute_ids), &permute_opts);
> + if (!ASSERT_OK(err, "btf__permute_layout"))
> + goto cleanup;
> +
> + raw = btf__raw_data(btf, &raw_sz);
[Severity: Medium]
Could this missing return check lead to passing a NULL pointer and
uninitialized size to btf__new() if btf__raw_data() fails?
> + parsed = btf__new(raw, raw_sz);
> + if (!ASSERT_OK_PTR(parsed, "parse_permuted_layout"))
> + goto cleanup;
> + btf__free(parsed);
> +cleanup:
> + btf__free(transfer_btf);
> + btf__free(btf);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=8
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 11/18] bpftool: Add ability to dump LOC_PARAM, LOC_PROTO and LOCSEC
2026-09-01 16:57 ` [PATCH v2 bpf-next 11/18] bpftool: Add ability to dump LOC_PARAM, LOC_PROTO and LOCSEC Alan Maguire
@ 2026-09-01 17:16 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:16 UTC (permalink / raw)
To: Alan Maguire; +Cc: bpf, linux-modules
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The `vlen` for BTF_KIND_LOC_PROTO is incorrectly cast to `__u16`, causing implicit truncation for large vlen values.
- [Medium] The `BTF_LOC_PARAM_SIGNED` flag is ignored when printing values for `BTF_KIND_LOC_PARAM`, causing negative signed values to be printed as unsigned.
--
commit 979370a25e9a748392e761a4d7cd6d5cda14a0ed
Author: Alan Maguire <alan.maguire@oracle.com>
bpftool: Add ability to dump LOC_PARAM, LOC_PROTO and LOCSEC
This commit updates bpftool to support dumping the new BTF kinds
BTF_KIND_LOC_PARAM, BTF_KIND_LOC_PROTO, and BTF_KIND_LOCSEC. It adds raw and
JSON formatting support for these new types.
> diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
> index bca0a3982f09b..4d8991c99b484 100644
> --- a/tools/bpf/bpftool/btf.c
> +++ b/tools/bpf/bpftool/btf.c
[ ... ]
> @@ -415,6 +418,88 @@ static int dump_btf_type(const struct btf *btf, __u32 id,
[ ... ]
> + case BTF_KIND_LOC_PARAM: {
> + const struct btf_loc_param *p = btf_loc_param(t);
> + __u32 *v = (__u32 *)(p + 1);
> + __u32 i, vlen = btf_vlen(t);
[ ... ]
> + for (i = 0; i < vlen; i++, v++) {
> + if (json_output) {
> + jsonw_start_object(w);
> + jsonw_uint_field(w, "value", *v);
[Severity: Medium]
Since the BTF_LOC_PARAM_SIGNED flag can be set in p->flags, will this
jsonw_uint_field() unconditionally format negative signed values as large
unsigned integers?
> + jsonw_end_object(w);
> + } else {
> + printf("\n\t value=%u", *v);
[Severity: Medium]
Similarly for the plain-text output, does this ignore the BTF_LOC_PARAM_SIGNED
flag and print signed negative values as large unsigned integers?
> + }
> + }
> + if (json_output)
> + jsonw_end_array(w);
> + break;
> + }
> + case BTF_KIND_LOC_PROTO: {
> + __u32 *params = btf_loc_proto_params(t);
> + __u16 vlen = btf_vlen(t);
[Severity: Medium]
The vlen field in BTF types uses 24 bits. Does explicitly casting it to __u16
here silently drop the upper 8 bits?
For a BTF_KIND_LOC_PROTO type with a vlen greater than 65535, this could
truncate the output and display an incorrect number of parameters.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=11
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 01/18] btf: Extend UAPI to support BTF location (inline site) info
2026-09-01 16:57 ` [PATCH v2 bpf-next 01/18] btf: Extend UAPI to support BTF location (inline site) info Alan Maguire
@ 2026-09-01 17:17 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:17 UTC (permalink / raw)
To: Alan Maguire; +Cc: bpf, linux-modules
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] btf_loc_proto_resolve fails to validate the return type location parameter (t->type).
- [Medium] Missing strict validation of unused fields and flags in check_meta callbacks for new BTF kinds.
--
commit 32008454060b92e68c858ee6b661e8885cb28034
Author: Alan Maguire <alan.maguire@oracle.com>
btf: Extend UAPI to support BTF location (inline site) info
This commit adds BTF_KIND_LOC_PARAM, BTF_KIND_LOC_PROTO, and
BTF_KIND_LOCSEC to help represent location information for functions.
It enables representing fully or partially inlined functions at
potentially multiple inline sites.
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 9c2cab08bb794..d74c8668aa3f6 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
[ ... ]
> +static s32 btf_loc_param_check_meta(struct btf_verifier_env *env,
> + const struct btf_type *t,
> + u32 meta_left)
> +{
> + const struct btf_loc_param *p = btf_loc_param(t);
> + u32 meta_needed;
> + u32 size;
> +
> + meta_needed = sizeof(*p) + sizeof(__u32) * btf_vlen(t);
[Severity: Medium]
Should this function strictly validate that p->flags only contains valid
enum bits, and check that btf_type_kflag(t) is 0?
The BTF verifier typically rejects non-zero unused fields and unknown flags
to ensure forward compatibility. If malformed BTF objects with arbitrary
garbage in these fields are accepted, they might silently alter behavior
or break if these bits are repurposed in the future.
> + if (meta_left < meta_needed) {
> + btf_verifier_log_basic(env, t,
> + "meta_left:%u meta_needed:%u",
> + meta_left, meta_needed);
> + return -EINVAL;
> + }
[ ... ]
> +static int btf_loc_proto_resolve(struct btf_verifier_env *env,
> + const struct resolve_vertex *v)
> +{
> + const __u32 *params = btf_loc_params(v->t);
> + u32 i, nr_params = btf_type_vlen(v->t);
> + struct btf *btf = env->btf;
> +
> + for (i = 0; i < nr_params; i++) {
[Severity: High]
Does this function need to validate the return type location parameter
encoded in v->t->type?
It appears to validate the vlen parameters (params[i]) but entirely skips
verifying v->t->type. If a malformed BTF object specifies an out-of-bounds
type ID or an incorrect BTF kind in the t->type field, could it result in
type confusion or out-of-bounds memory accesses when subsequent code trusts
this field?
> + const struct btf_type *param_type;
> + u32 param_type_id = params[i];
> +
> + if (!param_type_id)
> + continue;
[ ... ]
> +static s32 btf_locsec_check_meta(struct btf_verifier_env *env,
> + const struct btf_type *t,
> + u32 meta_left)
> +{
> + u32 meta_needed;
> +
> + meta_needed = sizeof(struct btf_loc) * btf_type_vlen(t);
[Severity: Medium]
Similar to the location parameter check, should this callback enforce that
the unused t->size/t->type union and the kflag are 0? Without these
checks, the verifier might accept garbage data in these unused fields which
violates the strict validation design pattern.
> +
> + if (meta_left < meta_needed) {
> + btf_verifier_log_basic(env, t,
> + "meta_left:%u meta_needed:%u",
> + meta_left, meta_needed);
> + return -EINVAL;
> + }
> + return meta_needed;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=1
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 17/18] selftests/bpf: Test BTF sysfs inline representations
2026-09-01 16:57 ` [PATCH v2 bpf-next 17/18] selftests/bpf: Test BTF sysfs inline representations Alan Maguire
@ 2026-09-01 17:22 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:22 UTC (permalink / raw)
To: Alan Maguire; +Cc: bpf, linux-modules
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Memory leak of `module_btf` when a module does not have a corresponding `.inline` sysfs file.
--
commit ae17979b11eeb1a7bef7bec7a8817be3cb2cd64a
Author: Alan Maguire <alan.maguire@oracle.com>
selftests/bpf: Test BTF sysfs inline representations
This commit adds a selftest to validate BTF sysfs inline representations for
the kernel and modules. It parses the base BTF, module BTF, and inline BTF
files to ensure proper loading and relocation across different scenarios.
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c b/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c
> index 3923e64c4c1d0..97eccfd7c1344 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c
[ ... ]
> +static void test_btf_inline_sysfs_all(void)
> +{
[ ... ]
> + while ((dentry = readdir(dir)) != NULL) {
[ ... ]
> + if (!base_btf) {
> + module_btf = btf__parse_split(btf_path, vmlinux_btf);
> + err = libbpf_get_error(module_btf);
> + if (err) {
> + /* A module can be unloaded while its sysfs entry is iterated. */
> + if (err == -ENOENT)
> + continue;
> + ASSERT_OK(err, "parse_module_btf");
> + continue;
> + }
> + base_btf = module_btf;
> + }
> + if (stat(inline_path, &st)) {
> + err = errno;
> + if (err == ENOENT)
> + continue;
[Severity: Medium]
Does this code leak module_btf?
In test_btf_inline_sysfs_all(), when a module is parsed successfully but
lacks a corresponding .inline sysfs file, stat() fails with ENOENT and this
path calls continue. Since this ends the loop iteration early, the
btf__free(module_btf) at the bottom of the loop is bypassed.
> + ASSERT_OK(err, "stat_inline_btf");
> + }
> + inline_btf = btf__parse_split(inline_path, base_btf);
> + err = libbpf_get_error(inline_btf);
> + if (!err)
> + btf__free(inline_btf);
> + ASSERT_OK(err, "parse_inline_btf");
> + btf__free(module_btf);
> + }
> + closedir(dir);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=17
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 12/18] resolve_btfids: Extract inline BTF
2026-09-01 16:57 ` [PATCH v2 bpf-next 12/18] resolve_btfids: Extract inline BTF Alan Maguire
@ 2026-09-01 17:23 ` sashiko-bot
0 siblings, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:23 UTC (permalink / raw)
To: Alan Maguire; +Cc: linux-modules, bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Division by zero when parsing ELF symbol table sections if `sh_entsize` is maliciously or erroneously set to 0.
--
commit df03e392d8f5364e6eef1e60fcadca00e2b031f8
Author: Alan Maguire <alan.maguire@oracle.com>
resolve_btfids: Extract inline BTF
This commit adds an --inline option to extract LOC_PARAM, LOC_PROTO and
LOCSEC types to a new inline split .BTF.inline while sorting BTF.
> diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c
> index 37d7e7224207d..20b228c4d6789 100644
> --- a/tools/bpf/resolve_btfids/main.c
> +++ b/tools/bpf/resolve_btfids/main.c
[ ... ]
> @@ -572,6 +578,58 @@ static const char *find_name_by_addr(struct object *obj, Elf64_Addr addr)
[ ... ]
> +static int collect_func_symbols(struct object *obj)
> +{
> + Elf_Scn *scn;
> + GElf_Shdr sh;
> + int n, i;
> +
> + if (obj->efile.symbols_shndx == -1)
> + return 0;
> +
> + scn = elf_getscn(obj->efile.elf, obj->efile.symbols_shndx);
> + if (!scn || gelf_getshdr(scn, &sh) != &sh)
> + return -EINVAL;
> + n = sh.sh_size / sh.sh_entsize;
[Severity: Medium]
Could this result in a division by zero if a malformed ELF object is
provided to the build system?
If sh.sh_entsize is 0, this will trigger a SIGFPE and crash the resolve_btfids
tool. While this vulnerable pattern already exists in symbols_collect(), it
appears to be newly introduced here in collect_func_symbols(). Should there be
a check to ensure sh.sh_entsize is non-zero before performing the division?
> +
> + for (i = 0; i < n; i++) {
> + GElf_Sym sym;
> + const char *name;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=12
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 15/18] btf: Support CONFIG_DEBUG_INFO_BTF_INLINE=m
2026-09-01 16:57 ` [PATCH v2 bpf-next 15/18] btf: Support CONFIG_DEBUG_INFO_BTF_INLINE=m Alan Maguire
@ 2026-09-01 17:24 ` sashiko-bot
0 siblings, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: linux-modules, bpf
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Unsafe lockless call to i_size_write() causes seqcount corruption on 32-bit SMP systems.
- [High] TOCTOU race in sysfs_btf_kernfs_read allows out-of-bounds kernel memory read on module reload.
- [Medium] Reloading the btf_vmlinux_inline module permanently leaks the previously allocated BTF inline data.
- [Medium] Unconditional dereference of btf_kobj->sd can lead to a boot-time kernel panic.
--
commit 79f9afc658508547f1298b89f81b2e99fe704e3c
Author: Alan Maguire <alan.maguire@oracle.com>
btf: Support CONFIG_DEBUG_INFO_BTF_INLINE=m
Allow vmlinux BTF inline info to be delivered via a loadable
module btf_vmlinux_inline.ko; this reduces the vmlinux binary size.
A kernfs representation is created for vmlinux.inline which loads
the module on demand.
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 3e5890aed2dba..2ac1f1d396608 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
[ ... ]
> @@ -8736,10 +8738,82 @@ enum {
> };
>
> #if IS_ENABLED(CONFIG_SYSFS)
> +#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
> +static struct bin_attribute *vmlinux_inline_attr;
> +#endif
[ ... ]
> +static int sysfs_btf_kernfs_open(struct kernfs_open_file *of)
> +{
> + struct bin_attribute *attr = of->kn->priv;
> + size_t data_size;
> + int err;
[ ... ]
> + if (of->file) {
> + struct inode *inode = file_inode(of->file);
> +
> + if (inode)
> + i_size_write(inode, data_size);
[Severity: High]
Does this code call i_size_write() locklessly?
On 32-bit SMP systems, calling i_size_write() without holding inode_lock(inode)
can cause seqcount corruption. This can lead to infinite loops in i_size_read()
and a potential denial of service when unprivileged users concurrently open the
sysfs file.
> + }
> + return 0;
> +}
> +
> +static ssize_t sysfs_btf_kernfs_read(struct kernfs_open_file *of, char *buf,
> + size_t bytes_requested, loff_t offset)
> +{
> + struct bin_attribute *attr = of->kn->priv;
> + void *data;
> + size_t data_size;
> +
> + data_size = smp_load_acquire(&attr->size);
> + if (offset >= data_size)
> + return 0;
> + if (offset + bytes_requested > data_size)
> + bytes_requested = data_size - offset;
> + data = READ_ONCE(attr->private);
> + memcpy(buf, data + offset, bytes_requested);
[Severity: High]
Can this TOCTOU race lead to an out-of-bounds read?
If a module reload triggers sysfs_btf_update() between the smp_load_acquire()
of attr->size and the READ_ONCE() of attr->private, the new data pointer could
be read while still using the old, potentially larger data_size. This might
allow a local user to extract out-of-bounds kernel memory via the memcpy().
> +
> + return bytes_requested;
> +}
[ ... ]
> @@ -8755,7 +8829,18 @@ struct bin_attribute *sysfs_btf_add(const char *name, void *data, size_t data_si
> err = -ENOMEM;
> goto err_free;
> }
> - err = sysfs_create_bin_file(btf_kobj, attr);
> + if (data_size > 0) {
> + err = sysfs_create_bin_file(btf_kobj, attr);
> + } else {
> + struct kernfs_node *node;
> +
> + node = __kernfs_create_file(btf_kobj->sd, attr->attr.name,
[Severity: Medium]
Is it possible for btf_kobj to be NULL here?
If btf_kobj failed to allocate during btf_sysfs_init() and sysfs_btf_add() is
called with data_size == 0 for vmlinux.inline, this unconditional dereference
of btf_kobj->sd could lead to a boot-time kernel panic.
> + attr->attr.mode, GLOBAL_ROOT_UID,
> + GLOBAL_ROOT_GID, data_size,
> + &sysfs_btf_kernfs_ops, attr, NULL, NULL);
> + if (IS_ERR(node))
> + err = PTR_ERR(node);
> + }
[ ... ]
> @@ -8775,6 +8860,17 @@ struct bin_attribute *sysfs_btf_add(const char *name, void *data, size_t data_si
> }
> #endif
>
> +#if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
> +static void sysfs_btf_update(struct bin_attribute *attr, void *data, size_t data_size)
> +{
> + if (!attr)
> + return;
> + WRITE_ONCE(attr->private, data);
[Severity: Medium]
Does this overwrite attr->private without freeing the previous allocation?
When the btf_vmlinux_inline module is reloaded, this appears to permanently
leak the previously allocated BTF inline data pointer.
> + /* Publish data before its non-zero size makes it readable. */
> + smp_store_release(&attr->size, data_size);
> +}
> +#endif
[ ... ]
> @@ -8872,6 +8968,14 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
> err = 0;
> goto out;
> }
> + if (strcmp(mod->name, "btf_vmlinux_inline") == 0) {
> + if (vmlinux_inline_attr)
> + sysfs_btf_update(vmlinux_inline_attr, data,
> + mod->btf_inline_data_size);
[Severity: Medium]
Does this bypass assigning the data pointer to the module's tracking struct?
Because btf_mod->btf_inline_data is left NULL in this branch, the newly
allocated data won't be cleaned up when the module is unloaded, further
contributing to the memory leak on module reload.
> + else
> + kvfree(data);
> + break;
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=15
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 18/18] selftests/bpf: Add a test verifying inline information
2026-09-01 16:57 ` [PATCH v2 bpf-next 18/18] selftests/bpf: Add a test verifying inline information Alan Maguire
@ 2026-09-01 17:28 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:28 UTC (permalink / raw)
To: Alan Maguire; +Cc: linux-modules, bpf
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] Parser desynchronization in module_get_base_addr() when reading trailing fields in /proc/modules.
- [Medium] Inverted logic in ASSERT_LT check causes early exit on test success.
- [Medium] Unhandled error from btf__parse_split leads to invalid pointer dereference.
- [Medium] Missing NULL pointer check after btf__type_by_id allows segmentation fault on malformed BTF.
--
commit 2cf9036457ef4f9129e585e625be7f2e1a33c7fa
Author: Alan Maguire <alan.maguire@oracle.com>
selftests/bpf: Add a test verifying inline information
This commit adds a selftest to verify inline information for an
__always_inline function in bpf_testmod. It checks that the LOCSEC
descriptor offset is within the expected bounds of the function.
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_inline.c b/tools/testing/selftests/bpf/prog_tests/btf_inline.c
> index 0000000000000..8e0a85f6c6980
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_inline.c
[ ... ]
> + inline_btf = btf__parse_split(BTF_SYSFS_DIR "/bpf_testmod" BTF_INLINE_SUFFIX,
> + btf);
> + err = libbpf_get_error(inline_btf);
> + /* pahole may not have inline BTF feature support. */
> + if (err == -ENOENT) {
> + skip = true;
> + goto out;
> + }
> + locsec_id = btf__find_by_name_kind(inline_btf, ".text", BTF_KIND_LOCSEC);
[Severity: Medium]
Does this code handle errors other than -ENOENT correctly?
If btf__parse_split() fails with an error like -ENOMEM, err will be extracted,
but since it is not -ENOENT, execution continues and the unhandled ERR_PTR in
inline_btf is passed to btf__find_by_name_kind(), leading to a dereference of
an invalid pointer.
[ ... ]
> + t = btf__type_by_id(inline_btf, l->loc_proto);
> + if (!ASSERT_OK_PTR(t, "loc_proto_ptr"))
> + goto out;
> + if (!ASSERT_EQ(btf_vlen(t), 1, "loc_proto_one_param"))
> + goto out;
> + p = btf_loc_proto_params(t);
> + t = btf__type_by_id(inline_btf, *p);
> + lp = btf_loc_param(t);
[Severity: Medium]
Can this lead to a NULL pointer dereference on malformed BTF data?
Unlike the safe pattern used earlier with ASSERT_OK_PTR, the return value of
btf__type_by_id() is passed directly to btf_loc_param() and dereferenced via
lp->flags without a NULL check.
> + if (!ASSERT_EQ(lp->flags, BTF_LOC_PARAM_REG, "param_is_reg"))
> + goto out;
> + if (!ASSERT_GT(l->offset, caller_addr, "inline_gt_caller"))
> + goto out;
> + /* simple sanity test to roughly ensure inline site still in function */
> + if (ASSERT_LT(l->offset, caller_addr + 256, "inline_in_caller"))
> + goto out;
[Severity: Medium]
Does this missing negation cause the test to exit early on success?
The ASSERT_LT macro evaluates to true when the check succeeds. Because the
condition lacks the negation ! used in all other assertions here, it will
execute the goto out branch when the sanity check passes.
> diff --git a/tools/testing/selftests/bpf/trace_helpers.c b/tools/testing/selftests/bpf/trace_helpers.c
> index 679008b310d9a..bf4cb720e7b81 100644
> --- a/tools/testing/selftests/bpf/trace_helpers.c
> +++ b/tools/testing/selftests/bpf/trace_helpers.c
[ ... ]
> +long module_get_base_addr(const char *module)
> +{
> + unsigned long long addr, base_addr = 0;
> + char name[500];
> + FILE *f;
> +
> + f = fopen("/proc/modules", "r");
> + if (!f)
> + return 0;
> +
> + while (fscanf(f, "%s %*s %*s %*s %*s 0x%llx", name, &addr) > 0) {
[Severity: Medium]
Can this parser become desynchronized if trailing fields are present in
/proc/modules?
The fscanf format string expects exactly 6 tokens and lacks a way to consume
the remainder of the line. Out-of-tree modules natively produce a 7th token
(such as the (O) taint flag).
When this happens, the trailing token is not consumed and the next loop
iteration will mistakenly read it as the name, breaking the parsing structure
for all subsequent lines.
> + if (strcmp(module, name) == 0) {
> + base_addr = addr;
> + break;
> + }
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=18
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 16/18] btf: Relocate inline BTF for modules with distilled base BTF
2026-09-01 16:57 ` [PATCH v2 bpf-next 16/18] btf: Relocate inline BTF for modules with distilled base BTF Alan Maguire
@ 2026-09-01 17:29 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-09-01 17:29 UTC (permalink / raw)
To: Alan Maguire; +Cc: linux-modules, bpf
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Kernel panic via unconditional btf_free(NULL) on error path
- [Medium] Memory leak of vmlinux.inline sysfs attribute data across module unloads and reloads
- [Medium] Concurrent updates and reads of `vmlinux.inline` BTF sysfs data can cause an out-of-bounds read
--
commit aad19c968ab1d821749bb6d06563d7c786f0721d
Author: Alan Maguire <alan.maguire@oracle.com>
btf: Relocate inline BTF for modules with distilled base BTF
This commit adds support for parsing, relocating, and publishing inline BTF
for modules that use a distilled base BTF. It parses the module's inline
BTF relative to the original module BTF, rewrites the IDs and string
offsets, and ensures a failure to process inline BTF doesn't block the
regular module BTF registration.
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 2ac1f1d396608..2f3e8cea7dfcd 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
[ ... ]
> @@ -6727,15 +6727,140 @@ __u32 btf_relocate_id(const struct btf *btf, __u32 id)
[ ... ]
> errout:
> + kvfree(module_str_map);
> btf_verifier_env_free(env);
> + btf_free(inline_btf);
> if (!IS_ERR(base_btf) && base_btf != vmlinux_btf)
> btf_free(base_btf);
> - if (btf) {
> - kvfree(btf->data);
> - kvfree(btf->types);
> - kfree(btf);
> - }
> + btf_free(btf);
[Severity: High]
Is it safe to unconditionally call btf_free() on these pointers here?
If btf_parse_split() returns an error, btf or inline_btf can be NULL before
jumping to errout. Looking at btf_free(), it immediately passes the pointer
to btf_free_struct_meta_tab(), which dereferences it without checking for
NULL:
static void btf_free(struct btf *btf)
{
btf_free_struct_meta_tab(btf);
...
static void btf_free_struct_meta_tab(struct btf *btf)
{
struct btf_struct_metas *tab = btf->struct_meta_tab;
Could this lead to a NULL pointer dereference and a kernel panic during an
error path?
> return ERR_PTR(err);
> }
[ ... ]
> @@ -8958,32 +9104,26 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
> btf_mod->sysfs_attr = attr;
>
> #if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE)
> - if (mod->btf_inline_data_size > 0) {
> + if (relocated_inline_data) {
> char name[MODULE_NAME_LEN + sizeof(".inline")];
>
> if (strcmp(mod->name, "btf_vmlinux_inline") == 0) {
> if (vmlinux_inline_attr)
> - sysfs_btf_update(vmlinux_inline_attr, data,
> - mod->btf_inline_data_size);
> + sysfs_btf_update(vmlinux_inline_attr,
> + relocated_inline_data, inline_data_size);
[Severity: Medium]
Could the concurrent update of this sysfs attribute cause an out-of-bounds
read?
Looking at sysfs_btf_update(), the new pointer is written before the new
size:
static void sysfs_btf_update(struct bin_attribute *attr, void *data,
size_t data_size)
{
...
WRITE_ONCE(attr->private, data);
smp_store_release(&attr->size, data_size);
}
If a concurrent reader in sysfs_btf_kernfs_read() picks up the old, larger
size via smp_load_acquire(), but then reads the newly updated pointer from
attr->private via READ_ONCE(), wouldn't the subsequent memcpy() read
out-of-bounds past the end of the new, smaller data buffer?
> else
> - kvfree(data);
> + kvfree(relocated_inline_data);
> + btf_mod->btf_inline_data = NULL;
[Severity: Medium]
Does setting btf_mod->btf_inline_data to NULL here cause a memory leak when
the btf_vmlinux_inline module is unloaded?
Because it's set to NULL, the normal cleanup path in btf_module_notify()
won't free it. Then, if the module is reloaded, sysfs_btf_update() will
overwrite the vmlinux_inline_attr->private pointer with a new allocation,
leaving the old relocated_inline_data stranded.
> break;
> }
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901165757.801449-1-alan.maguire@oracle.com?part=16
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 07/18] selftests/bpf: BTF distill tests to ensure LOC[_PARAM|_PROTO] add to split BTF
2026-09-01 16:57 ` [PATCH v2 bpf-next 07/18] selftests/bpf: BTF distill tests to ensure LOC[_PARAM|_PROTO] add to split BTF Alan Maguire
@ 2026-09-01 17:55 ` bot+bpf-ci
0 siblings, 0 replies; 42+ messages in thread
From: bot+bpf-ci @ 2026-09-01 17:55 UTC (permalink / raw)
To: alan.maguire, ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, alan.maguire, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4919 bytes --]
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_distill.c b/tools/testing/selftests/bpf/prog_tests/btf_distill.c
> index fb67ae195a73..f433b29ffce9 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf_distill.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_distill.c
> @@ -671,6 +671,80 @@ static void test_distilled_base_embedded_err(void)
> btf__free(btf1);
> }
>
> +/* LOC_PARAM, LOC_PROTO should be added to split BTF. */
> +static void test_distilled_loc(void)
> +{
> + struct btf *btf1 = NULL, *btf2 = NULL, *btf3 = NULL, *btf4 = NULL;
> +
> + btf1 = btf__new_empty();
> + if (!ASSERT_OK_PTR(btf1, "empty_main_btf"))
> + return;
> +
> + btf__add_int(btf1, "int", 4, BTF_INT_SIGNED); /* [1] int */
> + btf__add_func_proto(btf1, 1); /* [2] int (*)(int); */
> + btf__add_func_param(btf1, "p1", 1);
> + btf__add_func(btf1, "foo", BTF_FUNC_STATIC, 2); /* [3] int foo(int); */
> + btf__add_loc_param(btf1, 4, BTF_LOC_PARAM_SIGNED | BTF_LOC_PARAM_CONST);
> + btf__add_loc_param_value(btf1, -1); /* [4] loc value */
> +
> + VALIDATE_RAW_BTF(
> + btf1,
> + "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
> + "[2] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
> + "\t'p1' type_id=1",
> + "[3] FUNC 'foo' type_id=2 linkage=static",
> + "[4] LOC_PARAM '(anon)' size=4 flags=0x3 vlen=1\n"
> + "\tvalue=-1");
> +
> + btf2 = btf__new_empty_split(btf1);
> + if (!ASSERT_OK_PTR(btf2, "empty_split_btf"))
> + goto cleanup;
> +
> + btf__add_loc_proto(btf2); /* [5] loc proto */
> + btf__add_loc_proto_param(btf2, 4); /* param value */
> +
> + btf__add_locsec(btf2, ".locs"); /* [6] locsec */
> + btf__add_locsec_loc(btf2, 3, 5, 256); /* "foo" offset 256 */
Does this test actually cover the LOC_PROTO relocation case? Looking at
btf__distill_base() in tools/lib/bpf/btf.c, the LOC_PROTO here is created
in the split BTF (btf2, id [5]), not in the base BTF.
When btf_add_distilled_type_ids() runs, the only reference to that
LOC_PROTO is the LOCSEC's loc_proto=5. Since split_start_id equals
btf__type_cnt(btf1) which is 5, the condition:
if (*id >= dist->split_start_id)
continue;
means id_map is only populated for ids 1 (INT), 2 (FUNC_PROTO), 3 (FUNC)
and 4 (LOC_PARAM). The BTF_KIND_LOC_PROTO case in btf_add_distilled_types()
is never reached.
The LOC_PROTO that appears in the expected btf4 dump is just the split
type copied verbatim by the unconditional "First add all split types" loop,
so it would be produced even if the LOC_PROTO handling in
btf_add_distilled_types() were missing.
The realistic case this series addresses is a module LOCSEC in split BTF
referring to a LOC_PROTO that dedup mapped into the vmlinux base BTF.
Wouldn't putting the LOC_PROTO (and its LOC_PARAM) in btf1 and only the
LOCSEC in btf2 test that scenario?
> + VALIDATE_RAW_BTF(
> + btf2,
> + "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
> + "[2] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
> + "\t'p1' type_id=1",
> + "[3] FUNC 'foo' type_id=2 linkage=static",
> + "[4] LOC_PARAM '(anon)' size=4 flags=0x3 vlen=1\n"
> + "\tvalue=-1",
> + "[5] LOC_PROTO '(anon)' vlen=1\n"
> + "\ttype_id=4",
> + "[6] LOCSEC '.locs' vlen=1\n"
> + "\tfunc_type_id=3 loc_proto_type_id=5 offset=256");
> +
> + if (!ASSERT_EQ(0, btf__distill_base(btf2, &btf3, &btf4),
> + "distilled_base") ||
> + !ASSERT_OK_PTR(btf3, "distilled_base") ||
> + !ASSERT_OK_PTR(btf4, "distilled_split") ||
> + !ASSERT_EQ(2, btf__type_cnt(btf3), "distilled_base_type_cnt"))
> + goto cleanup;
> +
> + VALIDATE_RAW_BTF(
> + btf4,
> + "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
> + /* remainder is split BTF */
> + "[2] LOC_PROTO '(anon)' vlen=1\n"
> + "\ttype_id=6",
> + "[3] LOCSEC '.locs' vlen=1\n"
> + "\tfunc_type_id=5 loc_proto_type_id=2 offset=256",
> + "[4] FUNC_PROTO '(anon)' ret_type_id=1 vlen=1\n"
> + "\t'p1' type_id=1",
> + "[5] FUNC 'foo' type_id=4 linkage=static",
> + "[6] LOC_PARAM '(anon)' size=4 flags=0x3 vlen=1\n"
> + "\tvalue=-1");
Also, six of the eight sibling tests in this file finish with a
btf__relocate() step plus a re-validation. Should this test similarly
round-trip the id fixups through relocation?
> +cleanup:
> + btf__free(btf4);
> + btf__free(btf3);
> + btf__free(btf2);
> + btf__free(btf1);
> +}
> +
> void test_btf_distill(void)
> {
> if (test__start_subtest("distilled_base"))
> @@ -689,4 +763,6 @@ void test_btf_distill(void)
> test_distilled_base_vmlinux();
> if (test__start_subtest("distilled_endianness"))
> test_distilled_endianness();
> + if (test__start_subtest("distilled_loc"))
> + test_distilled_loc();
> }
---
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/33537080133
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 06/18] selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to dedup split tests
2026-09-01 16:57 ` [PATCH v2 bpf-next 06/18] selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to dedup split tests Alan Maguire
@ 2026-09-01 17:55 ` bot+bpf-ci
0 siblings, 0 replies; 42+ messages in thread
From: bot+bpf-ci @ 2026-09-01 17:55 UTC (permalink / raw)
To: alan.maguire, ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, alan.maguire, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4043 bytes --]
> commit e7a800a771162d1c8e3c4ae6c2032fa58e5ba5d9
> Author: Alan Maguire <alan.maguire@oracle.com>
>
> selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to dedup split tests
>
> Ensure that location params/protos are deduplicated and location
> sections are not, and that references to deduplicated locations within
> location prototypes and sections are updated after deduplication.
>
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
The commit message states that location sections are not deduplicated,
but the test contains only a single LOCSEC and no second LOCSEC to
merge against.
A single LOCSEC survives dedup regardless of whether the
btf_is_locsec() exemption exists in btf_dedup_new(), since there's
nothing to dedup it with. Adding a second .locs LOCSEC with identical
contents (or an identical btf_loc record) and asserting both survive
would directly pin the self-canonical branch for BTF_KIND_LOCSEC.
The test does still catch total breakage, because BTF_KIND_LOCSEC has
no case in btf_dedup_ref_type() and would hit its default return
-EINVAL if it were not self-canonical, which would trip the
ASSERT_OK(err, "btf_dedup"). However, that doesn't distinguish "LOCSEC
is exempt from dedup" from "LOCSEC had nothing to dedup against."
Would a second identical LOCSEC better verify the "location sections
are not [deduplicated]" claim?
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c b/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c
> index 9d6161151593..34ca20f88afd 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c
[ ... ]
> @@ -554,6 +554,113 @@ static void test_split_module(void)
> btf__free(vmlinux_btf);
> }
>
> +static void test_split_loc(void)
> +{
[ ... ]
> + btf2 = btf__new_empty_split(btf1);
> + if (!ASSERT_OK_PTR(btf2, "empty_split_btf"))
> + goto cleanup;
> + btf__add_loc_param(btf2, 8, BTF_LOC_PARAM_REG);
> + btf__add_loc_param_value(btf2, 1); /* [8] loc reg 1 */
> + btf__add_loc_proto(btf2); /* [9] loc proto */
> + btf__add_loc_proto_param(btf2, 5); /* param value */
> + btf__add_loc_proto_param(btf2, 8); /* param reg 1 */
> + btf__add_locsec(btf2, ".locs"); /* [9] locsec ".locs" */
^^^^
The type-ID annotation on this line is off by one. The comment says
/* [9] locsec ".locs" */ but [9] is the LOC_PROTO added three lines
earlier (also annotated /* [9] loc proto */). The LOCSEC actually gets
ID 10, as the VALIDATE_RAW_BTF block immediately below states:
"[10] LOCSEC '.locs' vlen=1\n"
"\tfunc_type_id=4 loc_proto_type_id=9 offset=128"
Traced against the builders: btf2 is split on top of btf1 (7 base
types), so btf__add_loc_param() creates [8], btf__add_loc_proto()
creates [9], and btf__add_locsec() creates [10].
In this file the /* [N] ... */ trailing comments are the only mapping
from builder calls to the expected raw-dump lines (see test_split_simple),
so a duplicated index misleads anyone updating the expectations.
Should this read /* [10] locsec ".locs" */?
> + btf__add_locsec_loc(btf2, 4, 9, 128);
> +
> + VALIDATE_RAW_BTF(
> + btf2,
[ ... ]
> + "[10] LOCSEC '.locs' vlen=1\n"
> + "\tfunc_type_id=4 loc_proto_type_id=9 offset=128");
> +
> + err = btf__dedup(btf2, NULL);
> + if (!ASSERT_OK(err, "btf_dedup"))
> + goto cleanup;
[ ... ]
> +cleanup:
> + btf__free(btf2);
> + btf__free(btf1);
> +}
> +
> void test_btf_dedup_split()
> {
> if (test__start_subtest("split_simple"))
> @@ -566,4 +673,6 @@ void test_btf_dedup_split()
> test_split_dup_struct_in_cu();
> if (test__start_subtest("split_module"))
> test_split_module();
> + if (test__start_subtest("split_loc"))
> + test_split_loc();
> }
---
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/33537080133
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 17/18] selftests/bpf: Test BTF sysfs inline representations
2026-09-01 16:57 ` [PATCH v2 bpf-next 17/18] selftests/bpf: Test BTF sysfs inline representations Alan Maguire
2026-09-01 17:22 ` sashiko-bot
@ 2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: bot+bpf-ci @ 2026-09-01 17:55 UTC (permalink / raw)
To: alan.maguire, ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, alan.maguire, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 6221 bytes --]
> selftests/bpf: Test BTF sysfs inline representations
>
> Load vmlinux, vmlinux.inline and all module and module.inline
> entries in /sys/fs/btf; this will allow us to sanity-check
The changelog references '/sys/fs/btf', but the directory is
/sys/kernel/btf. The code uses BTF_SYSFS_DIR "/sys/kernel/btf", the
existing call at test_btf_sysfs() uses "/sys/kernel/btf/vmlinux", and
the kernel side (89015dbacfe6) says "available in /sys/kernel/btf".
Should the message say /sys/kernel/btf instead?
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c b/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c
> index 3923e64c4c1d..97eccfd7c134 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_sysfs.c
[ ... ]
> +static void test_btf_inline_sysfs_all(void)
> +{
[ ... ]
> + while ((dentry = readdir(dir)) != NULL) {
> + struct btf *base_btf = NULL, *module_btf = NULL, *inline_btf = NULL;
> + char btf_path[PATH_MAX], inline_path[PATH_MAX];
> + struct stat st;
> +
> + /* Skip ".", ".." and "foo.inline" */
> + if (strstr(dentry->d_name, "."))
> + continue;
> +
> + if (strcmp(dentry->d_name, "vmlinux") == 0)
> + base_btf = vmlinux_btf;
> +
> + if (snprintf(btf_path, sizeof(btf_path), "%s/%s",
> + BTF_SYSFS_DIR, dentry->d_name) >= sizeof(btf_path) ||
> + snprintf(inline_path, sizeof(inline_path), "%s/%s%s",
> + BTF_SYSFS_DIR, dentry->d_name, BTF_INLINE_SUFFIX) >=
> + sizeof(inline_path)) {
> + ASSERT_FAIL("BTF sysfs path is too long\n");
> + break;
> + }
> +
> + if (!base_btf) {
> + module_btf = btf__parse_split(btf_path, vmlinux_btf);
> + err = libbpf_get_error(module_btf);
> + if (err) {
> + /* A module can be unloaded while its sysfs entry is iterated. */
> + if (err == -ENOENT)
> + continue;
> + ASSERT_OK(err, "parse_module_btf");
> + continue;
> + }
> + base_btf = module_btf;
> + }
> + if (stat(inline_path, &st)) {
> + err = errno;
> + if (err == ENOENT)
> + continue;
> + ASSERT_OK(err, "stat_inline_btf");
> + }
Is there a missing continue after the ASSERT_OK? On a stat() error other
than ENOENT (EACCES, EIO, ELOOP), the assertion records the failure but
then execution falls into the btf__parse_split() call below, which fails
on the same path for the same reason. Every other error branch in this
function continues; this is the only fall-through. Compare also
test_btf_mmap_sysfs() where every failed check goes to cleanup.
> + inline_btf = btf__parse_split(inline_path, base_btf);
> + err = libbpf_get_error(inline_btf);
> + if (!err)
> + btf__free(inline_btf);
> + ASSERT_OK(err, "parse_inline_btf");
> + btf__free(module_btf);
> + }
The module-unload race documented at the module BTF read is not handled
for the inline BTF read. The comment above notes 'A module can be
unloaded while its sysfs entry is iterated', and the module BTF parse
tolerates err == -ENOENT. The inline parse has no such tolerance:
btf__parse_split() returns NULL with errno = ENOENT if the file has gone
away, and ASSERT_OK(-ENOENT, "parse_inline_btf") fails the test.
The stat() above does not protect this because it is a check performed
strictly before the use (TOCTOU pre-check, not exclusion). Concrete
sequence:
1. readdir() returns "bpf_test_rqspinlock"
2. module BTF parse succeeds
3. stat(inline_path) succeeds
4. concurrently another test unloads the module:
prog_tests/res_spin_lock.c unload_module("bpf_test_rqspinlock"),
or prog_tests/bpf_mod_race.c, or test_progs.c unload_bpf_testmod()
reaches btf_module_notify(MODULE_STATE_GOING) and calls
sysfs_btf_remove(btf_mod->sysfs_inline_attr), removing the file
5. btf__parse_split() open() fails with ENOENT, err = -ENOENT
6. ASSERT_OK fails, spurious test failure
Could the inline parse apply the same tolerance already used for the
module parse (treat -ENOENT as 'entry vanished, skip')?
Also, with CONFIG_DEBUG_INFO_BTF_INLINE off (the default, and the current
selftests/bpf/config), does this test report PASS without exercising
anything?
CONFIG_DEBUG_INFO_BTF_INLINE is 'default n' (lib/Kconfig.debug) and
'depends on PAHOLE_HAS_INLINE' (pahole >= 1.31). Neither
tools/testing/selftests/bpf/config nor any config.<arch> gains
CONFIG_DEBUG_INFO_BTF_INLINE anywhere in the series (verified over
a04bcbfa9ea1~1..f197212706a6: no commit touches those files).
With the option off, kernel/bpf/btf_vmlinux_inline.c registers nothing
(guarded by IS_BUILTIN(CONFIG_DEBUG_INFO_BTF_INLINE)), so
/sys/kernel/btf/vmlinux.inline does not exist. The module path in
btf_module_notify() is #if IS_ENABLED(CONFIG_DEBUG_INFO_BTF_INLINE), so
no <mod>.inline exists either.
Every readdir() iteration therefore reaches the stat() at the line marked
above, stat() fails with ENOENT, and 'continue' is taken. The loop runs
to completion, no ASSERT_* is ever evaluated against inline BTF, and
test_btf_sysfs is reported as OK. The very feature the series adds is
never touched by the test that is supposed to cover it, in exactly the
configuration the BPF CI builds.
A subsystem pattern flags this as potentially concerning: tests that
assert/fail on EOPNOTSUPP, ENOSYS, or similar "capability absent" errno
values instead of skipping, or that skip silently with no message,
receive this flag. The vacuous pass is not a crash and does not
destabilize the system, but this is the test not running at all in the
default/CI configuration, which is the 'silent pass' failure mode
selftests.md names.
The next patch in the same series (f197212706a6, 'selftests/bpf: Add a
test verifying inline information') explicitly states 'Test is skipped if
pahole does not encode inline info', so the skip idiom is being used
elsewhere in the series.
Could the test track whether any *.inline entry was found and call
test__skip() with a reason if none were, and add
CONFIG_DEBUG_INFO_BTF_INLINE=y to tools/testing/selftests/bpf/config?
---
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/33537080133
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 18/18] selftests/bpf: Add a test verifying inline information
2026-09-01 16:57 ` [PATCH v2 bpf-next 18/18] selftests/bpf: Add a test verifying inline information Alan Maguire
2026-09-01 17:28 ` sashiko-bot
@ 2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: bot+bpf-ci @ 2026-09-01 17:55 UTC (permalink / raw)
To: alan.maguire, ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, alan.maguire, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 9331 bytes --]
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_inline.c b/tools/testing/selftests/bpf/prog_tests/btf_inline.c
> new file mode 100644
> index 000000000000..8e0a85f6c698
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_inline.c
> @@ -0,0 +1,110 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2026, Oracle and/or its affiliates. */
> +
> +#include <test_progs.h>
> +#include <bpf/btf.h>
> +#include <bpf/libbpf.h>
> +
> +#define BTF_SYSFS_DIR "/sys/kernel/btf"
> +#define BTF_INLINE_SUFFIX ".inline"
> +
> +/*
> + * For a specific inline site, verify we have the right function,
> + * loc proto and loc param representation and that the offset is
> + * reasonable given the caller where it was inlined.
> + *
> + * Because bpf_testmod is compiled "out-of-tree" we have inline
> + * information in the split BTF directly rather than in btf_testmod.inline.
> + */
The comment states the inline information is "in the split BTF directly
rather than in btf_testmod.inline", but the code below parses
"/sys/kernel/btf/bpf_testmod.inline", which is exactly the file the
comment says is not used:
> + inline_btf = btf__parse_split(BTF_SYSFS_DIR "/bpf_testmod" BTF_INLINE_SUFFIX,
> + btf);
The commit message agrees with the code ("the inline information will be
in btf_testmod.inline which is relocated using bpf_testmod.ko .BTF.base"),
so the in-file comment appears to be inverted.
> +void test_btf_inline(void)
> +{
[ ... ]
> + inline_btf = btf__parse_split(BTF_SYSFS_DIR "/bpf_testmod" BTF_INLINE_SUFFIX,
> + btf);
> + err = libbpf_get_error(inline_btf);
> + /* pahole may not have inline BTF feature support. */
> + if (err == -ENOENT) {
> + skip = true;
> + goto out;
> + }
> + locsec_id = btf__find_by_name_kind(inline_btf, ".text", BTF_KIND_LOCSEC);
Can inline_btf be NULL here? btf__parse_split() returns libbpf_ptr() of
the btf_parse() result, and libbpf_ptr() returns NULL with errno set for
errors other than -ENOENT. If btf__parse_split() fails with -EINVAL,
-EPROTO, -ENOMEM or -EPERM (malformed BTF, allocation failure, restricted
read), inline_btf is NULL and err is not -ENOENT, so the code falls
through and calls btf__find_by_name_kind(NULL, ...), which dereferences
the NULL pointer in btf__type_cnt().
Every other pointer in this function is checked with ASSERT_OK_PTR(); this
is the only exception. This matters because the test is meant to catch
malformed inline BTF from earlier commits in the series, but the crash
kills test_progs and takes every remaining test with it instead of
recording a test failure.
> + p = btf_loc_proto_params(t);
> + t = btf__type_by_id(inline_btf, *p);
> + lp = btf_loc_param(t);
> + if (!ASSERT_EQ(lp->flags, BTF_LOC_PARAM_REG, "param_is_reg"))
The second btf__type_by_id() result is used without the ASSERT_OK_PTR()
check that the first call three lines earlier receives. btf__type_by_id()
returns NULL for an out-of-range id, and btf_loc_param(t) is just
'(struct btf_loc_param *)(t + 1)', so lp->flags reads address 4 and
crashes.
There's also a legal-input case: the BTF spec allows a LOC_PROTO
parameter id to be 0 (meaning "no location info"). For *p == 0, libbpf
returns &btf_void, and lp->flags then reads past that static object,
producing a confusing failure message instead of a clear diagnosis.
Checking the pointer and the type kind would catch both cases.
> + if (!ASSERT_GT(l->offset, caller_addr, "inline_gt_caller"))
> + goto out;
> + /* simple sanity test to roughly ensure inline site still in function */
> + if (ASSERT_LT(l->offset, caller_addr + 256, "inline_in_caller"))
> + goto out;
> +out:
This branches to out when the assertion passes, but every other check in
the function is 'if (!ASSERT_xxx(...)) goto out;'. The failure is still
recorded, and 'out:' is the next label, so today the inversion has no
observable effect, but the sense is backwards and will silently do the
wrong thing the moment another check is appended after it.
Worth noting: the '+ 256' window is a compiler/config-dependent bound on
the size of bpf_testmod_uprobe_write(), so once the '!' is added this
becomes the one assertion in the test that can fail for reasons unrelated
to inline BTF correctness.
> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> index 2380b6cbdead..09d910d5e312 100644
> --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
[ ... ]
> -static int testmod_register_uprobe(loff_t offset)
> +static __always_inline int testmod_register_uprobe(loff_t offset)
> {
testmod_register_uprobe and its sole caller bpf_testmod_uprobe_write are
only compiled on x86_64. Both live inside the '#ifdef __x86_64__' region
that opens at line 721 ("bpf_testmod_uprobe sysfs attribute is so far
enabled for x86_64 only") and closes at line 838 with empty stubs.
The new test added by this commit keys off both names unconditionally:
const char *inline_caller = "bpf_testmod_uprobe_write";
...
caller_addr = ksym_get_addr(inline_caller);
if (!ASSERT_NEQ(caller_addr, 0, "caller_addr_nonzero"))
return;
On arm64, s390x, riscv64 or i386 with bpf_testmod loaded,
ksym_get_addr("bpf_testmod_uprobe_write") returns 0 because the symbol
does not exist, so ASSERT_NEQ fires and 'btf_inline' reports a hard FAIL
instead of a skip.
The commit adds no arch guard and no DENYLIST entry. The commit's only
stated skip conditions are '!env.has_testmod' and pahole lacking inline-
BTF support, so architecture is an unhandled third prerequisite.
Either the annotation should be moved to a function that is compiled on
all architectures, or the test needs an explicit architecture skip. Absent
prerequisite must skip, not fail, so it does not turn an environment
difference into a false regression signal.
> diff --git a/tools/testing/selftests/bpf/trace_helpers.c b/tools/testing/selftests/bpf/trace_helpers.c
> index 679008b310d9..bf4cb720e7b8 100644
> --- a/tools/testing/selftests/bpf/trace_helpers.c
> +++ b/tools/testing/selftests/bpf/trace_helpers.c
> @@ -244,6 +244,26 @@ int kallsyms_find(const char *sym, unsigned long long *addr)
> return err;
> }
>
> +long module_get_base_addr(const char *module)
> +{
> + unsigned long long addr, base_addr = 0;
> + char name[500];
> + FILE *f;
> +
> + f = fopen("/proc/modules", "r");
> + if (!f)
> + return 0;
> +
> + while (fscanf(f, "%s %*s %*s %*s %*s 0x%llx", name, &addr) > 0) {
> + if (strcmp(module, name) == 0) {
> + base_addr = addr;
> + break;
> + }
> + }
Does the fscanf pattern handle /proc/modules lines with taint suffixes?
kernel/module/procfs.c:m_show() emits:
seq_printf(m, "%s %u", mod->name, size);
print_unload_info(m, mod); /* " <refcount> <deps>" */
seq_printf(m, " %s", ...state...);
seq_printf(m, " 0x%px", value);
if (mod->taints)
seq_printf(m, " %s", module_flags(mod, buf, true)); /* " (OE)" */
seq_puts(m, "\n");
The format "%s %*s %*s %*s %*s 0x%llx" matches a line correctly, but
leaves the optional taint token unread. Because %s matches non-white-space
and white-space directives in the format match any amount of white space
including newlines, nothing ties a conversion to a line. The next
iteration reads "(OE)" into name, skips the following line's
name/size/refcount/deps with the four %*s, and tries to match " 0x"
against the state string "Live", which fails. fscanf() returns 1 (name was
assigned), and `1 > 0` keeps the loop spinning while one token out of
phase, so strcmp() never sees a real module name again.
This is reachable in the BPF CI environment. tools/testing/selftests/bpf/
config sets CONFIG_MODULE_SIG=y, and the selftest kmods are unsigned and
built out-of-tree, so every module line carries a "(OE)" suffix.
/proc/modules is emitted in load order (newest first), so any module
loaded after bpf_testmod is listed ahead of it, and test_progs loads extra
modules mid-run (bpf_test_modorder_x/y.ko, bpf_test_rqspinlock.ko,
bpf_test_no_cfi.ko, livepatch-sample.ko, plus modprobe rc-loopback/loop/
dm-verity). When one of those lines precedes bpf_testmod,
module_get_base_addr("bpf_testmod") returns 0 and the new test fails at
ASSERT_NEQ(base_addr, 0, "base_addr_nonzero"), a spurious failure rather
than a real BTF problem.
The immediately preceding function, kallsyms_find(), avoids exactly this:
fscanf(f, "%llx %c %499s%*[^\n]\n", &value, &type, name)
explicitly consumes the remainder of the line with "%*[^\n]\n", and bounds
the string with %499s. The idiomatic fix here is the same shape: either
read a line at a time with fgets() + sscanf(), or append a "%*[^\n]\n"
tail and require the full conversion count (== 2) instead of > 0. That
also removes a latent overflow: %s into char name[500] is unbounded, and
while a module name is capped at MODULE_NAME_LEN (56), the desynchronised
scan can steer the comma-separated deps token into name, whose length
grows with the number of dependent modules.
[ ... ]
---
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/33537080133
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 08/18] selftests/bpf: Validate that btf__permute transfer works
2026-09-01 16:57 ` [PATCH v2 bpf-next 08/18] selftests/bpf: Validate that btf__permute transfer works Alan Maguire
2026-09-01 17:16 ` sashiko-bot
@ 2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: bot+bpf-ci @ 2026-09-01 17:55 UTC (permalink / raw)
To: alan.maguire, ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, alan.maguire, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2865 bytes --]
> selftests/bpf: Validate that btf__permute transfer works
>
> Add coverage for btf__permute() transfer mode, including type-ID
> remapping and LOCSEC record ordering in the resulting split BTF.
>
> Verify string handling for both cases: a string used only by transferred
> types is removed from the base BTF and deduplicated locally in the split
> BTF, while a string also referenced by a retained base type remains a
> base-string reference.
>
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
This isn't a bug, but would it be worth a sentence in the changelog for
the new permute_layout subtest too, so the layout-section coverage is
visible without reading the diff?
The commit message describes the transfer mode coverage in detail, but the
patch also adds test_permute_layout() which builds a BTF with .add_layout
= true, permutes it with a transfer, and re-parses btf__raw_data() to
verify the layout/string section offsets stayed consistent.
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_permute.c b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
> index 04ade5ad77ac..ef75fd71df96 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf_permute.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_permute.c
[ ... ]
> +/* Permuting BTF with a layout section must keep section offsets in sync. */
> +static void test_permute_layout(void)
> +{
> + LIBBPF_OPTS(btf_new_opts, opts, .add_layout = true);
> + LIBBPF_OPTS(btf_permute_opts, permute_opts);
> + struct btf *btf, *parsed, *transfer_btf = NULL;
> + const void *raw;
> + __u32 raw_sz;
> + __u32 permute_ids[] = { 0, 1, BTF_PERMUTE_ID_TRANSFER | 2 };
> + int err;
> +
> + btf = btf__new_empty_opts(&opts);
> + if (!ASSERT_OK_PTR(btf, "empty_layout_btf"))
> + return;
> +
> + btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
> + btf__add_ptr(btf, 1);
> + permute_opts.transfer_btf = &transfer_btf;
> + err = btf__permute(btf, permute_ids, ARRAY_SIZE(permute_ids), &permute_opts);
> + if (!ASSERT_OK(err, "btf__permute_layout"))
> + goto cleanup;
> +
> + raw = btf__raw_data(btf, &raw_sz);
> + parsed = btf__new(raw, raw_sz);
> + if (!ASSERT_OK_PTR(parsed, "parse_permuted_layout"))
> + goto cleanup;
> + btf__free(parsed);
> +cleanup:
> + btf__free(transfer_btf);
> + btf__free(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_transfer"))
> + test_permute_transfer();
> + if (test__start_subtest("permute_layout"))
> + test_permute_layout();
> }
---
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/33537080133
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 11/18] bpftool: Add ability to dump LOC_PARAM, LOC_PROTO and LOCSEC
2026-09-01 16:57 ` [PATCH v2 bpf-next 11/18] bpftool: Add ability to dump LOC_PARAM, LOC_PROTO and LOCSEC Alan Maguire
2026-09-01 17:16 ` sashiko-bot
@ 2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: bot+bpf-ci @ 2026-09-01 17:55 UTC (permalink / raw)
To: alan.maguire, ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, alan.maguire, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1884 bytes --]
> diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
> index bca0a3982f09..4d8991c99b48 100644
> --- a/tools/bpf/bpftool/btf.c
> +++ b/tools/bpf/bpftool/btf.c
[ ... ]
> @@ -415,6 +418,88 @@ static int dump_btf_type(const struct btf *btf, __u32 id,
> }
> break;
> }
> + case BTF_KIND_LOC_PARAM: {
> + const struct btf_loc_param *p = btf_loc_param(t);
> + __u32 *v = (__u32 *)(p + 1);
> + __u32 i, vlen = btf_vlen(t);
> +
> + if (json_output) {
> + jsonw_uint_field(w, "size", t->size);
> + jsonw_uint_field(w, "flags", p->flags);
> + jsonw_uint_field(w, "vlen", vlen);
> + jsonw_name(w, "values");
> + jsonw_start_array(w);
> + } else {
> + printf(" size=%u flags=0x%x vlen=%u", t->size, p->flags, vlen);
> + }
> + for (i = 0; i < vlen; i++, v++) {
> + if (json_output) {
> + jsonw_start_object(w);
> + jsonw_uint_field(w, "value", *v);
> + jsonw_end_object(w);
> + } else {
> + printf("\n\t value=%u", *v);
^^^^^
This isn't a bug, but was the extra space after '\n\t' in the format
strings intentional? Looking at the existing dump_btf_type() arms,
DATASEC prints "\n\ttype_id=%u offset=%u size=%u" with just '\n\t' and
no leading space. The new LOC_PARAM, LOC_PROTO, and LOCSEC arms all use
'\n\t ' (tab followed by a space), which indents the output one column
further than the rest of the function's raw dump lines. Could the space
be dropped to align with the DATASEC style?
> + }
> + }
> + if (json_output)
> + jsonw_end_array(w);
> + break;
> + }
> + case BTF_KIND_LOC_PROTO: {
[ ... ]
> + case BTF_KIND_LOCSEC: {
[ ... ]
---
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/33537080133
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 13/18] kbuild: Add support for BTF inline information
2026-09-01 16:57 ` [PATCH v2 bpf-next 13/18] kbuild: Add support for BTF inline information Alan Maguire
@ 2026-09-01 17:55 ` bot+bpf-ci
0 siblings, 0 replies; 42+ messages in thread
From: bot+bpf-ci @ 2026-09-01 17:55 UTC (permalink / raw)
To: alan.maguire, ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, alan.maguire, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4640 bytes --]
> diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
> index b2988aa12f66..a2e192854c47 100644
> --- a/include/asm-generic/vmlinux.lds.h
> +++ b/include/asm-generic/vmlinux.lds.h
> @@ -675,12 +675,23 @@
> /*
> * .BTF
> */
[ ... ]
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index 1244dcac2294..dd1b2d9ebe99 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -425,6 +425,12 @@ config PAHOLE_HAS_LANG_EXCLUDE
[ ... ]
> @@ -432,6 +438,17 @@ config DEBUG_INFO_BTF_MODULES
> help
> Generate compact split BTF type information for kernel modules.
>
> +config DEBUG_INFO_BTF_INLINE
> + bool "Provide information about inline sites in BTF"
> + default n
> + depends on DEBUG_INFO_BTF && PAHOLE_HAS_INLINE && SYSFS
> + help
> + Generate information about inline sites in .BTF.inline sections.
> + These sections contain split BTF relative to the kernel or module BTF
> + and are made available in /sys/kernel/btf with a ".inline" suffix.
> + The information describes inline locations and how to retrieve their
> + associated parameters.
> +
This isn't a bug, but could the changelog lead with why inline location
info is wanted and what a consumer does with it, rather than one sentence
per hunk?
In particular, is the in-tree/external-module paragraph still describing
something in this patch, and would it help to note that the SYSFS
dependency and the /sys/kernel/btf exposure mentioned in the help text
arrive in a later patch?
> diff --git a/scripts/Makefile.btf b/scripts/Makefile.btf
> index a1812985a61a..d42dbc8d7199 100644
> --- a/scripts/Makefile.btf
> +++ b/scripts/Makefile.btf
> @@ -22,7 +22,14 @@ endif
>
> pahole-flags-$(CONFIG_PAHOLE_HAS_LANG_EXCLUDE) += --lang_exclude=rust
>
> +btf-inline := $(CONFIG_DEBUG_INFO_BTF_INLINE)
> +ifneq ($(btf-inline),)
> +btf-inline-feat := inline
> +pahole-flags-$(call test-ge, $(pahole-ver), 131) += --btf_features=$(btf-inline-feat)
> +endif
> +
> export PAHOLE_FLAGS := $(pahole-flags-y)
> +export BTF_INLINE := $(btf-inline)
This isn't a bug, but could this collapse to something closer to the
surrounding one-liners, e.g. `pahole-flags-$(CONFIG_DEBUG_INFO_BTF_INLINE)
+= --btf_features=inline` plus `export BTF_INLINE :=
$(CONFIG_DEBUG_INFO_BTF_INLINE)`?
Since DEBUG_INFO_BTF_INLINE already depends on PAHOLE_HAS_INLINE
(PAHOLE_VERSION >= 131), is the extra test-ge 131 check inside the block
doing anything, and is the `btf-inline-feat` indirection needed?
> diff --git a/scripts/gen-btf.sh b/scripts/gen-btf.sh
> index 8ca96eb10a69..a75f41878c32 100755
> --- a/scripts/gen-btf.sh
> +++ b/scripts/gen-btf.sh
> @@ -76,6 +76,7 @@ gen_btf_data()
>
> ${RESOLVE_BTFIDS} ${RESOLVE_BTFIDS_FLAGS} \
> ${BTF_BASE:+--btf_base ${BTF_BASE}} \
> + ${BTF_INLINE:+--inline} \
> --btf ${btf1} "${ELF_FILE}"
> }
>
> @@ -83,14 +84,21 @@ gen_btf_o()
> {
> btf_data=${ELF_FILE}.btf.o
>
> - # Create ${btf_data} which contains just .BTF section but no symbols. Add
> + # Create ${btf_data} which contains just BTF sections but no symbols. Add
> # SHF_ALLOC because .BTF will be part of the vmlinux image. --strip-all
> # deletes all symbols including __start_BTF and __stop_BTF, which will
> # be redefined in the linker script.
> echo "" | ${CC} ${CLANG_FLAGS} ${KBUILD_CPPFLAGS} ${KBUILD_CFLAGS} -fno-lto -c -x c -o ${btf_data} -
> ${OBJCOPY} --add-section .BTF=${ELF_FILE}.BTF \
> --set-section-flags .BTF=alloc,readonly ${btf_data}
> - ${OBJCOPY} --only-section=.BTF --strip-all ${btf_data}
> + ONLY_SEC="--only-section=.BTF"
> + btf_inline=${ELF_FILE}.BTF.inline
> + if [ -n "${BTF_INLINE}" ] && [ -f "${btf_inline}" ]; then
> + ${OBJCOPY} --add-section .BTF.inline=${btf_inline} \
> + --set-section-flags .BTF.inline=alloc,readonly ${btf_data}
> + ONLY_SEC="${ONLY_SEC} --only-section=.BTF.inline"
> + fi
> + ${OBJCOPY} ${ONLY_SEC} --strip-all ${btf_data}
This isn't a bug, but would a lower-case name like `only_sec` fit better
here, given `btf_inline` right below it and the rest of the locals in this
script?
> @@ -111,6 +119,10 @@ embed_btf_data()
> if [ -f "${btf_base}" ]; then
> ${OBJCOPY} --add-section .BTF.base=${btf_base} ${ELF_FILE}
> fi
> + btf_inline=${ELF_FILE}.BTF.inline
> + if [ -n "${BTF_INLINE}" ] && [ -f "${btf_inline}" ]; then
> + ${OBJCOPY} --add-section .BTF.inline=${btf_inline} ${ELF_FILE}
> + fi
[ ... ]
---
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/33537080133
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 01/18] btf: Extend UAPI to support BTF location (inline site) info
2026-09-01 16:57 ` [PATCH v2 bpf-next 01/18] btf: Extend UAPI to support BTF location (inline site) info Alan Maguire
2026-09-01 17:17 ` sashiko-bot
@ 2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: bot+bpf-ci @ 2026-09-01 17:55 UTC (permalink / raw)
To: alan.maguire, ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, alan.maguire, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 6250 bytes --]
> diff --git a/include/linux/btf.h b/include/linux/btf.h
> index ddd0f4f32d24..a4412bc16688 100644
> --- a/include/linux/btf.h
> +++ b/include/linux/btf.h
> @@ -559,7 +574,7 @@ struct btf_field_desc {
> /* member struct size, or zero, if no members */
> int m_sz;
> /* repeated per-member offsets */
> - int m_off_cnt, m_offs[1];
> + int m_off_cnt, m_offs[2];
> };
Eduard Zingerman noted in the RFC v1 review that this change to m_offs[2]
should be moved to patch #2, since that patch's commit message explains
why it is needed. The change was acknowledged but not addressed in v2.
See: https://lore.kernel.org/bpf/531305ee76a5ef186b1204dc8281ebc7ebb2b1c0.camel@gmail.com/
> diff --git a/include/uapi/linux/btf.h b/include/uapi/linux/btf.h
> index 618167cab4e6..6062c9958034 100644
> --- a/include/uapi/linux/btf.h
> +++ b/include/uapi/linux/btf.h
[ ... ]
> +/*
> + * The combination of size, vlen and flags gives us the means to interpret
> + * the following vlen-specified set of 4-byte values:
> + *
> + * - a BTF_LOC_PARAM_CONST is a constant value; combination
> + * of size, vlen and _SIGNED flag determines it. If the value requires
> + * 64 bits it is stored in {lo,hi} order.
> + * - a BTF_LOC_PARAM_ADDR is an address that will be normalized with
> + * respect to kernel base address.
> + * - a BTF_LOC_PARAM_REG with vlen 1 is a simple register number;
> + * with vlen 2 it is a multi-register parameter.
> + * - a _REG | DEREF with vlen 1 dereferences the value in the register
> + * number specified.
> + * - a REG | DEREF | OFFSET with vlen specifies the register value in
> + * the first 4-byte value and the offset in the remainder.
> + * - binary logical operators operate on a combination of register
> + * number and constant value, aside from _NOT which operates on
> + * a register
> + */
> +enum btf_loc_param_flags {
> + BTF_LOC_PARAM_SIGNED = 0x1,
> + BTF_LOC_PARAM_CONST = 0x2,
> + BTF_LOC_PARAM_ADDR = 0x4,
> + BTF_LOC_PARAM_REG = 0x8,
> + BTF_LOC_PARAM_DEREF = 0x10,
> + BTF_LOC_PARAM_OFFSET = 0x20,
> +};
Does the comment above the enum match the actual flags? The text mentions
"binary logical operators" and "_NOT which operates on a register," but
the enum defines only SIGNED, CONST, ADDR, REG, DEREF and OFFSET. Where
is BTF_LOC_PARAM_NOT and where are the logical operator flags?
This same mismatch appears in tools/include/uapi/linux/btf.h as well.
> +/*
> + * BTF_KIND_LOC_PROTO specifies location prototypes; i.e. how locations relate
> + * to parameters; a struct btf_type of BTF_KIND_LOC_PROTO is followed by a
> + * a vlen-specified number of __u32 BTF type ids which specify the associated
> + * BTF_KIND_LOC_PARAM for each function parameter associated with the
> + * location. The type should either be 0 (no location info) or point at
> + * a BTF_KIND_LOC_PARAM.
> + */
This isn't a bug, but there's a duplicated word: "followed by a / a
vlen-specified number". The same duplication appears in the tools copy at
tools/include/uapi/linux/btf.h.
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 9c2cab08bb79..d74c8668aa3f 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
[ ... ]
> +static s32 btf_loc_param_check_meta(struct btf_verifier_env *env,
> + const struct btf_type *t,
> + u32 meta_left)
> +{
> + const struct btf_loc_param *p = btf_loc_param(t);
> + u32 meta_needed;
> + u32 size;
> +
> + meta_needed = sizeof(*p) + sizeof(__u32) * btf_vlen(t);
> + if (meta_left < meta_needed) {
> + btf_verifier_log_basic(env, t,
> + "meta_left:%u meta_needed:%u",
> + meta_left, meta_needed);
> + return -EINVAL;
> + }
> +
> + if (t->name_off) {
> + btf_verifier_log_type(env, t, "Invalid name");
> + return -EINVAL;
> + }
> + size = t->size;
> + if (size > 16 || !is_power_of_2(size)) {
> + btf_verifier_log_type(env, t, "Unexpected size");
> + return -EINVAL;
> + }
> +
> + return meta_needed;
> +}
Does this handler miss the trailing btf_verifier_log_type(env, t, NULL)
call before returning successfully? Looking at __btf_verifier_log_type()
in this file, the comment states: "btf verifier prints all types it is
processing via btf_verifier_log_type(..., fmt = NULL)". Every other
check_meta handler in kernel/bpf/btf.c ends with that call on the
success path (int, ptr, array, struct, enum, enum64, fwd, typedef,
func_proto, func, var, datasec, decl_tag, float), but
btf_loc_param_check_meta, btf_loc_proto_check_meta, and
btf_locsec_check_meta all return successfully without it.
When userspace passes a log buffer to BPF_BTF_LOAD, wouldn't the per-type
"[id] KIND name ..." trace skip every LOC_PARAM/LOC_PROTO/LOCSEC type,
creating gaps in the [id] sequence? Since env->log_type_id advances
inside btf_verifier_log_type() and not on the successful path here, could
later error messages for subsequent types be attributed to the wrong id?
> +static void btf_locsec_log(struct btf_verifier_env *env,
> + const struct btf_type *t)
> +{
> + const struct btf_loc *loc = btf_type_loc_secinfo(t);
> + u32 nr_locs = btf_type_vlen(t), i;
> + const struct btf *btf = env->btf;
> +
> + btf_verifier_log(env, "locsec %s locs=(",
> + __btf_name_by_offset(btf, t->name_off));
> + for (i = 0; i < nr_locs; i++, loc++) {
> + btf_verifier_log(env, "\n\tfunc %u loc_proto %u offset 0x%x%s",
> + loc->func, loc->loc_proto, loc->offset,
> + i + 1 == nr_locs ? ")" : ", ");
> + }
> +}
A subsystem pattern flags this as potentially concerning: the log_details
callback re-prints the kind and name that __btf_verifier_log_type() has
already emitted, so the output would read "[7] LOCSEC name locsec name
locs=(...)". Every other log_details handler (btf_datasec_log,
btf_struct_log, etc.) prints only the kind-specific tail.
Also, if vlen is zero, does the loop body that would print the closing
parenthesis ever run? A zero-vlen LOCSEC would pass check_meta and leave
a dangling "(" in the log. The same pattern appears in btf_loc_proto_log
and btf_loc_param_log.
---
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/33537080133
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 16/18] btf: Relocate inline BTF for modules with distilled base BTF
2026-09-01 16:57 ` [PATCH v2 bpf-next 16/18] btf: Relocate inline BTF for modules with distilled base BTF Alan Maguire
2026-09-01 17:29 ` sashiko-bot
@ 2026-09-01 17:55 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: bot+bpf-ci @ 2026-09-01 17:55 UTC (permalink / raw)
To: alan.maguire, ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, alan.maguire, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 5243 bytes --]
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 2ac1f1d39660..2f3e8cea7dfc 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
[ ... ]
> @@ -6727,15 +6727,140 @@ __u32 btf_relocate_id(const struct btf *btf, __u32 id)
>
> #ifdef CONFIG_DEBUG_INFO_BTF_MODULES
>
> +static struct btf *btf_parse_split(struct btf_verifier_env *env, const char *name,
> + const void *data, unsigned int data_size,
> + struct btf *base_btf)
> +{
> + struct btf *btf;
> + int err;
> +
> + btf = kzalloc_obj(*btf, GFP_KERNEL | __GFP_NOWARN);
> + if (!btf)
> + return ERR_PTR(-ENOMEM);
> + env->btf = btf;
> +
> + btf_set_base_btf(btf, base_btf);
> + btf->kernel_btf = true;
> + btf->named_start_id = 0;
> + strscpy(btf->name, name);
> +
> + btf->data = kvmemdup(data, data_size, GFP_KERNEL | __GFP_NOWARN);
> + if (!btf->data) {
> + err = -ENOMEM;
> + goto errout;
> + }
> + btf->data_size = data_size;
> +
> + err = btf_parse_hdr(env);
> + if (err)
> + goto errout;
> +
> + btf->nohdr_data = btf->data + btf->hdr.hdr_len;
> +
> + err = btf_parse_str_sec(env);
> + if (err)
> + goto errout;
> +
> + err = btf_check_all_metas(env);
> + if (err)
> + goto errout;
> +
> + err = btf_check_modifier_chain_length(env, btf, btf_nr_types(base_btf));
> + if (err)
> + goto errout;
> +
> + return btf;
> +
> +errout:
> + btf_free(btf);
> + return ERR_PTR(err);
> +}
[ ... ]
> @@ -6759,67 +6884,75 @@ static struct btf *btf_parse_module(const char *module_name, const void *data,
> unsigned int data_size, void *base_data,
> - unsigned int base_data_size)
> + unsigned int base_data_size, const void *inline_data,
> + unsigned int inline_data_size, bool vmlinux_inline,
> + void **relocated_inline_data)
> {
> - struct btf *btf = NULL, *vmlinux_btf, *base_btf = NULL;
> + struct btf *btf = NULL, *inline_btf = NULL, *vmlinux_btf, *base_btf = NULL;
> + struct btf *inline_base_btf;
> struct btf_verifier_env *env = NULL;
> struct bpf_verifier_log *log;
> + u32 old_module_type_cnt;
> + u32 *module_str_map = NULL;
> int err = 0;
[ ... ]
> - btf = kzalloc_obj(*btf, GFP_KERNEL | __GFP_NOWARN);
> - if (!btf) {
> - err = -ENOMEM;
> + btf = btf_parse_split(env, module_name, data, data_size, base_btf);
> + if (IS_ERR(btf)) {
> + err = PTR_ERR(btf);
> + btf = NULL;
> goto errout;
> }
[ ... ]
> + if (inline_data_size) {
> + /*
> + * Ordinary module inline BTF is split relative to the module BTF.
> + * The btf_vmlinux_inline delivery module instead carries BTF split
> + * directly relative to vmlinux.
> + */
> + inline_base_btf = vmlinux_inline ? vmlinux_btf : btf;
> + inline_btf = btf_parse_split(env, module_name, inline_data,
> + inline_data_size, inline_base_btf);
> + if (IS_ERR(inline_btf)) {
> + pr_warn("failed to validate module [%s] inline BTF: %ld\n",
> + module_name, PTR_ERR(inline_btf));
> + inline_btf = NULL;
> + }
> + }
[ ... ]
> errout:
> + kvfree(module_str_map);
> btf_verifier_env_free(env);
> + btf_free(inline_btf);
> if (!IS_ERR(base_btf) && base_btf != vmlinux_btf)
> btf_free(base_btf);
> - if (btf) {
> - kvfree(btf->data);
> - kvfree(btf->types);
> - kfree(btf);
> - }
> + btf_free(btf);
> return ERR_PTR(err);
> }
Can btf_free() handle NULL pointers safely? Both inline_btf and btf are
initialized to NULL and can remain NULL on several error paths:
btf_parse_module() {
struct btf *btf = NULL, *inline_btf = NULL, ...
If btf_parse_split() fails for the module BTF at the call site above,
the code sets btf = NULL and jumps to errout, which then calls:
errout:
btf_free(inline_btf); /* NULL */
...
btf_free(btf); /* NULL */
Looking at btf_free(), its first statement is
btf_free_struct_meta_tab(btf), which dereferences btf->struct_meta_tab
without a NULL check. Similarly, inline_btf is only assigned inside the
if (inline_data_size) block, so it remains NULL on modules without inline
BTF or when inline parsing fails.
The pre-patch code guarded the btf free with:
if (btf) {
kvfree(btf->data);
kvfree(btf->types);
kfree(btf);
}
while this patch replaces it with bare btf_free() calls. btf_parse() in
this same file still uses the guarded pattern: if (btf) btf_free(btf).
Three paths that can trigger this:
1. btf_parse_split() fails for the module BTF due to -ENOMEM from
kvmemdup, bad BTF magic/version in btf_parse_hdr(), malformed type
metadata in btf_check_all_metas(), or -ELOOP from
btf_check_modifier_chain_length(). Both btf and inline_btf are NULL at
errout.
2. btf_parse_base(".BTF.base", ...) fails for an out-of-tree module.
Both btf and inline_btf are still NULL.
3. btf_relocate() fails on a module with no .BTF.inline section, or whose
inline parse already set inline_btf = NULL. btf_free(NULL) at errout.
CONFIG_MODULE_ALLOW_BTF_MISMATCH exists so BTF validation failure
degrades gracefully instead of failing module load, but with this patch
the errout path would crash first.
---
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/33537080133
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v2 bpf-next 03/18] libbpf: Support moving permuted BTF types into split BTF
2026-09-01 16:57 ` [PATCH v2 bpf-next 03/18] libbpf: Support moving permuted BTF types into split BTF Alan Maguire
2026-09-01 17:15 ` sashiko-bot
@ 2026-09-01 18:14 ` bot+bpf-ci
1 sibling, 0 replies; 42+ messages in thread
From: bot+bpf-ci @ 2026-09-01 18:14 UTC (permalink / raw)
To: alan.maguire, ast, andrii
Cc: daniel, eddyz87, jolsa, ihor.solodrai, yonghong.song, song, qmo,
martin.lau, memxor, emil, mcgrof, petr.pavlu, tj, kees, bpf,
nathan, nsc, arnd, puranjay, yatsenko, atenart, ojeda,
linux-modules, alan.maguire, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4502 bytes --]
> diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
> index 9449f6f50f18..30f8c426d145 100644
> --- a/tools/lib/bpf/btf.c
> +++ b/tools/lib/bpf/btf.c
> @@ -2147,16 +2147,9 @@ int btf__find_str(struct btf *btf, const char *s)
> * - > 0 offset into string section, on success;
> * - < 0, on error.
> */
> -int btf__add_str(struct btf *btf, const char *s)
> +static int btf_add_local_str(struct btf *btf, const char *s)
> {
> - int off;
> - int err;
> -
> - if (btf->base_btf) {
> - off = btf__find_str(btf->base_btf, s);
> - if (off != -ENOENT)
> - return off;
> - }
> + int off, err;
>
> err = btf_ensure_modifiable(btf);
> if (err)
> @@ -2167,10 +2160,24 @@ int btf__add_str(struct btf *btf, const char *s)
> return libbpf_err(off);
>
> btf->hdr.str_len = strset__data_size(btf->strs_set);
> + btf->strs_deduped = false;
The refactoring left the API doc comment attached to the new static
helper btf_add_local_str(), so the public btf__add_str() (btf.c:2168) now
has no documentation at all, and its declaration at tools/lib/bpf/btf.h:202
is bare without doxygen. The comment is also no longer descriptive of what
it now documents: the distinguishing property of btf_add_local_str() versus
btf__add_str() is that it always adds to this BTF's own string section and
never reuses a string already present in base_btf, which is exactly what
makes the new transfer mode able to keep split-only strings out of the base.
Should the comment be moved back onto btf__add_str() and btf_add_local_str()
given a one-liner about bypassing the base string table?
The new btf->strs_deduped = false sits in btf_add_local_str(), which is on
the path of every public btf__add_str() call (and therefore of btf__add_int,
btf__add_var, btf__add_field, btf__add_enum_value, btf__add_func_param,
btf__add_datasec, btf__add_locsec, btf_add_composite, btf_add_decl_tag,
btf_add_ref_kind, btf_add_enum_common, and more), not just the new transfer
path.
Before this commit nothing in tools/lib/bpf ever cleared strs_deduped. The
complete set of writers post-commit is: btf_ensure_modifiable() sets it true
when hdr.str_len == 0 or when !base_btf && str_len == 1, end of
btf_dedup_strings() sets it true, plus the two new clears here and in
btf_compact_strings().
For BTF built from scratch, btf__new_empty() (str_len == 1) and
btf__new_empty_split() (str_len == 0) both make btf_ensure_modifiable() set
strs_deduped = true, so pre-commit btf_dedup_strings() returned 0 at its
early-return check and the string table was never rebuilt. Post-commit the
first added string clears the flag, so btf__dedup() now always rebuilds the
string table for such BTF: strings are reordered and unreferenced strings
are dropped. Any offset a caller previously obtained from the public
btf__add_str() and stashed outside a BTF type's name field is therefore
silently invalidated across btf__dedup().
This is not needed by the commit: both btf_compact_strings() call sites
already force btf->strs_deduped = false themselves at btf.c:4536, so the
transfer path does not depend on btf_add_local_str() clearing it.
All in-tree consumers were checked and are safe: bpf_linker's
finalize_btf() (tools/lib/bpf/linker.c:2860) runs finalize_btf_ext() first
and then passes opts.btf_ext = linker->btf_ext to btf__dedup(), so the
offsets produced at linker.c:1968, 2697, 2703, 2740 and 2952 all get
remapped; bpftool merge_btf_files() (tools/bpf/bpftool/btf.c:880) and
resolve_btfids finalize_btf() (tools/bpf/resolve_btfids/main.c:1578) hold no
external string offsets. So there is no in-tree breakage, but this is an
unadvertised behaviour change to a public API plus a full string-table
rebuild on every static link, and the commit message does not mention
btf__add_str() or strs_deduped at all.
Is the clearing of strs_deduped here intentional for non-transfer use of
btf__add_str(), or should it only be cleared in btf_compact_strings() where
the transfer path already sets it?
>
> return btf->start_str_off + off;
> }
>
> +int btf__add_str(struct btf *btf, const char *s)
> +{
> + int off;
> +
> + if (btf->base_btf) {
> + off = btf__find_str(btf->base_btf, s);
> + if (off != -ENOENT)
> + return off;
> + }
> +
> + return btf_add_local_str(btf, s);
> +}
[ ... ]
---
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/33537080133
^ permalink raw reply [flat|nested] 42+ messages in thread
end of thread, other threads:[~2026-09-01 18:14 UTC | newest]
Thread overview: 42+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-01 16:57 [PATCH v2 bpf-next 00/18] Support inline functions in BTF Alan Maguire
2026-09-01 16:57 ` [PATCH v2 bpf-next 01/18] btf: Extend UAPI to support BTF location (inline site) info Alan Maguire
2026-09-01 17:17 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 02/18] libbpf: Add support for BTF kinds LOC[_PARAM|_PROTO|SEC] Alan Maguire
2026-09-01 17:11 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 03/18] libbpf: Support moving permuted BTF types into split BTF Alan Maguire
2026-09-01 17:15 ` sashiko-bot
2026-09-01 18:14 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 04/18] selftests/bpf: Test helper support for BTF_KIND_LOC[_PARAM|_PROTO|SEC] Alan Maguire
2026-09-01 17:06 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 05/18] selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to field iter tests Alan Maguire
2026-09-01 16:57 ` [PATCH v2 bpf-next 06/18] selftests/bpf: Add LOC_PARAM, LOC_PROTO, LOCSEC to dedup split tests Alan Maguire
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 07/18] selftests/bpf: BTF distill tests to ensure LOC[_PARAM|_PROTO] add to split BTF Alan Maguire
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 08/18] selftests/bpf: Validate that btf__permute transfer works Alan Maguire
2026-09-01 17:16 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 09/18] bpftool: Handle multi-split BTF by supporting multiple base BTFs Alan Maguire
2026-09-01 17:13 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 10/18] bpftool: Document support for multi-split BTF Alan Maguire
2026-09-01 17:12 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 11/18] bpftool: Add ability to dump LOC_PARAM, LOC_PROTO and LOCSEC Alan Maguire
2026-09-01 17:16 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 12/18] resolve_btfids: Extract inline BTF Alan Maguire
2026-09-01 17:23 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 13/18] kbuild: Add support for BTF inline information Alan Maguire
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 14/18] btf: Make vmlinux, module inline info available in /sys/kernel/btf Alan Maguire
2026-09-01 16:57 ` [PATCH v2 bpf-next 15/18] btf: Support CONFIG_DEBUG_INFO_BTF_INLINE=m Alan Maguire
2026-09-01 17:24 ` sashiko-bot
2026-09-01 16:57 ` [PATCH v2 bpf-next 16/18] btf: Relocate inline BTF for modules with distilled base BTF Alan Maguire
2026-09-01 17:29 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 17/18] selftests/bpf: Test BTF sysfs inline representations Alan Maguire
2026-09-01 17:22 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
2026-09-01 16:57 ` [PATCH v2 bpf-next 18/18] selftests/bpf: Add a test verifying inline information Alan Maguire
2026-09-01 17:28 ` sashiko-bot
2026-09-01 17:55 ` bot+bpf-ci
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox