Since recently [1][2] resolve_btfids executes final adjustments to the
kernel/module BTF before it's embedded into the target binary.
To keep the implementation simple, a clear and stable "pipeline" of
how BTF data flows through resolve_btfids would be helpful. Some BTF
modifications may change the ids of the types, so it is important to
maintain correct order of operations with respect to .BTF_ids
resolution too.
This patch refactors the BTF handling to establish the following
sequence:
- load target ELF sections
- load .BTF_ids symbols
- this will be a dependency of btf2btf transformations in
subsequent patches
- load BTF and its base as is
- (*) btf2btf transformations will happen here
- finalize_btf(), introduced in this patch
- does distill base and sort BTF
- resolve and patch .BTF_ids
This approach helps to avoid fixups in .BTF_ids data in case the ids
change at any point of BTF processing, because symbol resolution
happens on the finalized, ready to dump, BTF data.
This also gives flexibility in BTF transformations, because they will
happen on BTF that is not distilled and/or sorted yet, allowing to
freely add, remove and modify BTF types.
[1] https://lore.kernel.org/bpf/20251219181321.1283664-1-ihor.solodrai@linux.dev/
[2] https://lore.kernel.org/bpf/20260109130003.3313716-1-dolinux.peng@gmail.com/
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
tools/bpf/resolve_btfids/main.c | 69 +++++++++++++++++++++++----------
1 file changed, 48 insertions(+), 21 deletions(-)
diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c
index 343d08050116..1fcf37af6764 100644
--- a/tools/bpf/resolve_btfids/main.c
+++ b/tools/bpf/resolve_btfids/main.c
@@ -563,19 +563,6 @@ static int load_btf(struct object *obj)
obj->base_btf = base_btf;
obj->btf = btf;
- if (obj->base_btf && obj->distill_base) {
- err = btf__distill_base(obj->btf, &base_btf, &btf);
- if (err) {
- pr_err("FAILED to distill base BTF: %s\n", strerror(errno));
- goto out_err;
- }
-
- btf__free(obj->base_btf);
- btf__free(obj->btf);
- obj->base_btf = base_btf;
- obj->btf = btf;
- }
-
return 0;
out_err:
@@ -911,6 +898,41 @@ static int sort_btf_by_name(struct btf *btf)
return err;
}
+static int finalize_btf(struct object *obj)
+{
+ struct btf *base_btf = obj->base_btf, *btf = obj->btf;
+ int err;
+
+ if (obj->base_btf && obj->distill_base) {
+ err = btf__distill_base(obj->btf, &base_btf, &btf);
+ if (err) {
+ pr_err("FAILED to distill base BTF: %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ btf__free(obj->base_btf);
+ btf__free(obj->btf);
+ obj->base_btf = base_btf;
+ obj->btf = btf;
+ }
+
+ err = sort_btf_by_name(obj->btf);
+ if (err) {
+ pr_err("FAILED to sort BTF: %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ return 0;
+
+out_err:
+ btf__free(base_btf);
+ btf__free(btf);
+ obj->base_btf = NULL;
+ obj->btf = NULL;
+
+ return err;
+}
+
static inline int make_out_path(char *buf, u32 buf_sz, const char *in_path, const char *suffix)
{
int len = snprintf(buf, buf_sz, "%s%s", in_path, suffix);
@@ -1054,6 +1076,7 @@ int main(int argc, const char **argv)
};
const char *btfids_path = NULL;
bool fatal_warnings = false;
+ bool resolve_btfids = true;
char out_path[PATH_MAX];
struct option btfid_options[] = {
@@ -1083,12 +1106,6 @@ int main(int argc, const char **argv)
if (btfids_path)
return patch_btfids(btfids_path, obj.path);
- if (load_btf(&obj))
- goto out;
-
- if (sort_btf_by_name(obj.btf))
- goto out;
-
if (elf_collect(&obj))
goto out;
@@ -1099,12 +1116,22 @@ int main(int argc, const char **argv)
if (obj.efile.idlist_shndx == -1 ||
obj.efile.symbols_shndx == -1) {
pr_debug("Cannot find .BTF_ids or symbols sections, skip symbols resolution\n");
- goto dump_btf;
+ resolve_btfids = false;
}
- if (symbols_collect(&obj))
+ if (resolve_btfids)
+ if (symbols_collect(&obj))
+ goto out;
+
+ if (load_btf(&obj))
goto out;
+ if (finalize_btf(&obj))
+ goto out;
+
+ if (!resolve_btfids)
+ goto dump_btf;
+
if (symbols_resolve(&obj))
goto out;
--
2.52.0
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
> Since recently [1][2] resolve_btfids executes final adjustments to the
> kernel/module BTF before it's embedded into the target binary.
>
> To keep the implementation simple, a clear and stable "pipeline" of
> how BTF data flows through resolve_btfids would be helpful. Some BTF
> modifications may change the ids of the types, so it is important to
> maintain correct order of operations with respect to .BTF_ids
> resolution too.
>
> This patch refactors the BTF handling to establish the following
> sequence:
> - load target ELF sections
> - load .BTF_ids symbols
> - this will be a dependency of btf2btf transformations in
> subsequent patches
> - load BTF and its base as is
> - (*) btf2btf transformations will happen here
> - finalize_btf(), introduced in this patch
> - does distill base and sort BTF
> - resolve and patch .BTF_ids
>
> This approach helps to avoid fixups in .BTF_ids data in case the ids
> change at any point of BTF processing, because symbol resolution
> happens on the finalized, ready to dump, BTF data.
>
> This also gives flexibility in BTF transformations, because they will
> happen on BTF that is not distilled and/or sorted yet, allowing to
> freely add, remove and modify BTF types.
>
> [1] https://lore.kernel.org/bpf/20251219181321.1283664-1-ihor.solodrai@linux.dev/
> [2] https://lore.kernel.org/bpf/20260109130003.3313716-1-dolinux.peng@gmail.com/
>
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
> @@ -1099,12 +1116,22 @@ int main(int argc, const char **argv)
> if (obj.efile.idlist_shndx == -1 ||
> obj.efile.symbols_shndx == -1) {
> pr_debug("Cannot find .BTF_ids or symbols sections, skip symbols resolution\n");
> - goto dump_btf;
> + resolve_btfids = false;
> }
>
> - if (symbols_collect(&obj))
> + if (resolve_btfids)
> + if (symbols_collect(&obj))
> + goto out;
Nit: check obj.efile.idlist_shndx and obj.efile.symbols_shndx inside symbols_collect()?
To avoid resolve_btfids flag and the `goto dump_btf;` below.
> +
> + if (load_btf(&obj))
> goto out;
>
> + if (finalize_btf(&obj))
> + goto out;
> +
> + if (!resolve_btfids)
> + goto dump_btf;
> +
> if (symbols_resolve(&obj))
> goto out;
>
On 1/19/26 4:13 PM, Eduard Zingerman wrote:
> On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
>> Since recently [1][2] resolve_btfids executes final adjustments to the
>> kernel/module BTF before it's embedded into the target binary.
>>
>> To keep the implementation simple, a clear and stable "pipeline" of
>> how BTF data flows through resolve_btfids would be helpful. Some BTF
>> modifications may change the ids of the types, so it is important to
>> maintain correct order of operations with respect to .BTF_ids
>> resolution too.
>>
>> This patch refactors the BTF handling to establish the following
>> sequence:
>> - load target ELF sections
>> - load .BTF_ids symbols
>> - this will be a dependency of btf2btf transformations in
>> subsequent patches
>> - load BTF and its base as is
>> - (*) btf2btf transformations will happen here
>> - finalize_btf(), introduced in this patch
>> - does distill base and sort BTF
>> - resolve and patch .BTF_ids
>>
>> This approach helps to avoid fixups in .BTF_ids data in case the ids
>> change at any point of BTF processing, because symbol resolution
>> happens on the finalized, ready to dump, BTF data.
>>
>> This also gives flexibility in BTF transformations, because they will
>> happen on BTF that is not distilled and/or sorted yet, allowing to
>> freely add, remove and modify BTF types.
>>
>> [1] https://lore.kernel.org/bpf/20251219181321.1283664-1-ihor.solodrai@linux.dev/
>> [2] https://lore.kernel.org/bpf/20260109130003.3313716-1-dolinux.peng@gmail.com/
>>
>> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
>> ---
>
> Acked-by: Eduard Zingerman <eddyz87@gmail.com>
>
>> @@ -1099,12 +1116,22 @@ int main(int argc, const char **argv)
>> if (obj.efile.idlist_shndx == -1 ||
>> obj.efile.symbols_shndx == -1) {
>> pr_debug("Cannot find .BTF_ids or symbols sections, skip symbols resolution\n");
>> - goto dump_btf;
>> + resolve_btfids = false;
>> }
>>
>> - if (symbols_collect(&obj))
>> + if (resolve_btfids)
>> + if (symbols_collect(&obj))
>> + goto out;
>
> Nit: check obj.efile.idlist_shndx and obj.efile.symbols_shndx inside symbols_collect()?
> To avoid resolve_btfids flag and the `goto dump_btf;` below.
Hi Eduard, thank you for review.
The issue is that in case of .BTF_ids section absent we have to skip
some of the steps, specifically:
- symbols_collect()
- sequence between symbols_resolve() and dump_raw_btf_ids()
It's not an exit condition, we still have to do load/dump of the BTF.
I tried in symbols_collect():
if (obj.efile.idlist_shndx == -1 || obj.efile.symbols_shndx == -1)
return 0;
But then, we either have to do the same check in symbols_resolve() and
co, or maybe store a flag in the struct object. So I decided it's
better to have an explicit flag in the main control flow, instead of
hiding it.
lmk if you had something else in mind
>
>> +
>> + if (load_btf(&obj))
>> goto out;
>>
>> + if (finalize_btf(&obj))
>> + goto out;
>> +
>> + if (!resolve_btfids)
>> + goto dump_btf;
>> +
>> if (symbols_resolve(&obj))
>> goto out;
>>
On Tue, 2026-01-20 at 10:11 -0800, Ihor Solodrai wrote:
[...]
> > > @@ -1099,12 +1116,22 @@ int main(int argc, const char **argv)
> > > if (obj.efile.idlist_shndx == -1 ||
> > > obj.efile.symbols_shndx == -1) {
> > > pr_debug("Cannot find .BTF_ids or symbols sections, skip symbols resolution\n");
> > > - goto dump_btf;
> > > + resolve_btfids = false;
> > > }
> > >
> > > - if (symbols_collect(&obj))
> > > + if (resolve_btfids)
> > > + if (symbols_collect(&obj))
> > > + goto out;
> >
> > Nit: check obj.efile.idlist_shndx and obj.efile.symbols_shndx inside symbols_collect()?
> > To avoid resolve_btfids flag and the `goto dump_btf;` below.
>
> Hi Eduard, thank you for review.
>
> The issue is that in case of .BTF_ids section absent we have to skip
> some of the steps, specifically:
> - symbols_collect()
> - sequence between symbols_resolve() and dump_raw_btf_ids()
> It's not an exit condition, we still have to do load/dump of the BTF.
>
> I tried in symbols_collect():
>
> if (obj.efile.idlist_shndx == -1 || obj.efile.symbols_shndx == -1)
> return 0;
>
> But then, we either have to do the same check in symbols_resolve() and
> co, or maybe store a flag in the struct object. So I decided it's
> better to have an explicit flag in the main control flow, instead of
> hiding it.
For symbols_resolve() is any special logic necessary?
I think that `id = btf_id__find(root, str);` will just return NULL for
every type, thus the whole function would be a noop passing through
BTF types once.
symbols_patch() will be a noop, as it will attempt traversing empty roots.
dump_raw_btf_ids() already returns if there are no .BTF_ids.
[...]
On 1/20/26 10:19 AM, Eduard Zingerman wrote:
> On Tue, 2026-01-20 at 10:11 -0800, Ihor Solodrai wrote:
>
> [...]
>
>>>> @@ -1099,12 +1116,22 @@ int main(int argc, const char **argv)
>>>> if (obj.efile.idlist_shndx == -1 ||
>>>> obj.efile.symbols_shndx == -1) {
>>>> pr_debug("Cannot find .BTF_ids or symbols sections, skip symbols resolution\n");
>>>> - goto dump_btf;
>>>> + resolve_btfids = false;
>>>> }
>>>>
>>>> - if (symbols_collect(&obj))
>>>> + if (resolve_btfids)
>>>> + if (symbols_collect(&obj))
>>>> + goto out;
>>>
>>> Nit: check obj.efile.idlist_shndx and obj.efile.symbols_shndx inside symbols_collect()?
>>> To avoid resolve_btfids flag and the `goto dump_btf;` below.
>>
>> Hi Eduard, thank you for review.
>>
>> The issue is that in case of .BTF_ids section absent we have to skip
>> some of the steps, specifically:
>> - symbols_collect()
>> - sequence between symbols_resolve() and dump_raw_btf_ids()
>
>> It's not an exit condition, we still have to do load/dump of the BTF.
>>
>> I tried in symbols_collect():
>>
>> if (obj.efile.idlist_shndx == -1 || obj.efile.symbols_shndx == -1)
>> return 0;
>>
>> But then, we either have to do the same check in symbols_resolve() and
>> co, or maybe store a flag in the struct object. So I decided it's
>> better to have an explicit flag in the main control flow, instead of
>> hiding it.
>
> For symbols_resolve() is any special logic necessary?
> I think that `id = btf_id__find(root, str);` will just return NULL for
> every type, thus the whole function would be a noop passing through
> BTF types once.
>
> symbols_patch() will be a noop, as it will attempt traversing empty roots.
> dump_raw_btf_ids() already returns if there are no .BTF_ids.
Hm... Looks like you're right, those would be noops.
Still, I think it's clearer what steps are skipped with a toplevel
flag. Otherwise to figure out that those are noops you need to check
every subroutine (as you just did), and a future change may
unintentionally break the expectation of noop creating an unnecessary
debugging session.
And re symbols_resolve(), if we don't like allocating unnecessary
memory, why are we ok with traversing the BTF with noops? Seems
a bit inconsistent to me.
>
> [...]
On Tue, 2026-01-20 at 10:35 -0800, Ihor Solodrai wrote:
> On 1/20/26 10:19 AM, Eduard Zingerman wrote:
> > On Tue, 2026-01-20 at 10:11 -0800, Ihor Solodrai wrote:
> >
> > [...]
> >
> > > > > @@ -1099,12 +1116,22 @@ int main(int argc, const char **argv)
> > > > > if (obj.efile.idlist_shndx == -1 ||
> > > > > obj.efile.symbols_shndx == -1) {
> > > > > pr_debug("Cannot find .BTF_ids or symbols sections, skip symbols resolution\n");
> > > > > - goto dump_btf;
> > > > > + resolve_btfids = false;
> > > > > }
> > > > >
> > > > > - if (symbols_collect(&obj))
> > > > > + if (resolve_btfids)
> > > > > + if (symbols_collect(&obj))
> > > > > + goto out;
> > > >
> > > > Nit: check obj.efile.idlist_shndx and obj.efile.symbols_shndx inside symbols_collect()?
> > > > To avoid resolve_btfids flag and the `goto dump_btf;` below.
> > >
> > > Hi Eduard, thank you for review.
> > >
> > > The issue is that in case of .BTF_ids section absent we have to skip
> > > some of the steps, specifically:
> > > - symbols_collect()
> > > - sequence between symbols_resolve() and dump_raw_btf_ids()
> >
> > > It's not an exit condition, we still have to do load/dump of the BTF.
> > >
> > > I tried in symbols_collect():
> > >
> > > if (obj.efile.idlist_shndx == -1 || obj.efile.symbols_shndx == -1)
> > > return 0;
> > >
> > > But then, we either have to do the same check in symbols_resolve() and
> > > co, or maybe store a flag in the struct object. So I decided it's
> > > better to have an explicit flag in the main control flow, instead of
> > > hiding it.
> >
> > For symbols_resolve() is any special logic necessary?
> > I think that `id = btf_id__find(root, str);` will just return NULL for
> > every type, thus the whole function would be a noop passing through
> > BTF types once.
> >
> > symbols_patch() will be a noop, as it will attempt traversing empty roots.
> > dump_raw_btf_ids() already returns if there are no .BTF_ids.
>
> Hm... Looks like you're right, those would be noops.
>
> Still, I think it's clearer what steps are skipped with a toplevel
> flag. Otherwise to figure out that those are noops you need to check
> every subroutine (as you just did), and a future change may
> unintentionally break the expectation of noop creating an unnecessary
> debugging session.
I'd argue that resolve_btfids is written in a clear read data /
process data manner, so it should behave correctly even if collected
data is empty. Is there a difference between no .BTF_ids and empty
.BTF_ids?
> And re symbols_resolve(), if we don't like allocating unnecessary
> memory, why are we ok with traversing the BTF with noops? Seems
> a bit inconsistent to me.
He-he, I never heard about consistency :)
Just don't like flags, they make me think more.
© 2016 - 2026 Red Hat, Inc.