From: Jay Wang <wanjay@amazon.com>
To: <bpf@vger.kernel.org>, Alexei Starovoitov <ast@kernel.org>,
"Daniel Borkmann" <daniel@iogearbox.net>,
Andrii Nakryiko <andrii@kernel.org>,
"Eduard Zingerman" <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>
Cc: Alan Maguire <alan.maguire@oracle.com>,
Martin KaFai Lau <martin.lau@linux.dev>,
Yonghong Song <yonghong.song@linux.dev>,
"Nathan Chancellor" <nathan@kernel.org>,
Nicolas Schier <nsc@kernel.org>, <linux-kbuild@vger.kernel.org>,
Luis Chamberlain <mcgrof@kernel.org>,
"Petr Pavlu" <petr.pavlu@suse.com>,
<linux-modules@vger.kernel.org>, Arnd Bergmann <arnd@arndb.de>,
<linux-kernel@vger.kernel.org>,
Hazem Mohamed Abuelfotoh <abuehaze@amazon.com>,
Bjoern Doebel <doebel@amazon.de>, <jay.wang.upstream@gmail.com>
Subject: [PATCH bpf-next 5/6] bpf: defer registrations until the vmlinux BTF is available
Date: Wed, 23 Sep 2026 05:39:47 +0000 [thread overview]
Message-ID: <20260923053948.30617-6-wanjay@amazon.com> (raw)
In-Reply-To: <20260923053948.30617-1-wanjay@amazon.com>
With CONFIG_DEBUG_INFO_BTF=m the vmlinux BTF is loaded on first use. For
that to save anything, nothing may pull it in at boot. The verifier no
longer does since the previous patches; two things still do:
- register_btf_kfunc_id_set(), register_btf_id_dtor_kfuncs() and
register_bpf_struct_ops() for vmlinux run from initcalls and need the
parsed BTF. Queue them instead (btf_defer_reg()) and apply them in
btf_parse_vmlinux(), before the BTF is published, so that no program
can see a vmlinux BTF without its kfuncs and struct_ops. Applying a
struct_ops runs its ->init(), which registers the kfunc sets of its
hook; those land back on the queue, so it is drained in a loop until a
pass adds nothing, and only then are new registrations applied
directly. The dtor arrays are copied: every caller in the tree builds
them on the stack of its initcall.
- Module BTF is split BTF against the vmlinux BTF and used to be parsed
in the module notifier. The notifier cannot load btf_vmlinux (that
would nest a module load in a module load), so a module loaded before
the vmlinux BTF keeps a copy of its .BTF and .BTF.base, is exposed in
/sys/kernel/btf right away (the raw bytes need no parsing) and gets a
list entry with btf == NULL. Its kfunc, dtor kfunc and struct_ops
registrations wait on that entry. When the vmlinux BTF arrives,
btf_parse_deferred_modules() parses the kept copies, gives them ids
and applies the waiting registrations; the copy is the one
btf_parse_module() makes anyway, so the sysfs file keeps pointing at
valid data. Registrations are applied after dropping btf_module_mutex
because they walk the module list (btf_check_kfunc_name()).
A module whose BTF turns out to mismatch at that point is already
running and keeps running without BTF, with a warning. With =y such a
module would have been refused at load time unless
CONFIG_MODULE_ALLOW_BTF_MISMATCH; that check only applies to modules
loaded after the vmlinux BTF.
Walkers of the module BTF list skip entries whose BTF is not parsed yet.
With =y the BTF is present from boot, so the queues never fill and the
notifier takes the existing path. Still nothing is reachable until the
Kconfig symbol becomes a tristate.
Signed-off-by: Jay Wang <wanjay@amazon.com>
---
include/linux/btf.h | 5 +
kernel/bpf/btf.c | 426 +++++++++++++++++++++++++++++++++++++++++-
kernel/bpf/verifier.c | 9 +-
3 files changed, 433 insertions(+), 7 deletions(-)
diff --git a/include/linux/btf.h b/include/linux/btf.h
index 0bf10811fe53..3b99d6386dec 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -583,6 +583,11 @@ const char *btf_str_by_offset(const struct btf *btf, u32 offset);
struct btf *btf_parse_vmlinux(void);
void *btf_vmlinux_data(u32 *size, bool load);
u32 btf_vmlinux_size(void);
+#if IS_MODULE(CONFIG_DEBUG_INFO_BTF)
+void btf_parse_deferred_modules(void);
+#else
+static inline void btf_parse_deferred_modules(void) {}
+#endif
struct btf *bpf_prog_get_target_btf(const struct bpf_prog *prog);
u32 *btf_kfunc_flags(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_prog *prog);
int btf_kfunc_check_flag(const struct btf *btf, u32 kfunc_btf_id, u32 flag);
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 50eb7a95fd82..cbba20a908e9 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -6551,6 +6551,8 @@ static struct btf *btf_parse_base(struct btf_verifier_env *env, const char *name
return ERR_PTR(err);
}
+static void btf_apply_deferred_vmlinux_regs(struct btf *btf);
+
struct btf *btf_parse_vmlinux(void)
{
struct btf_verifier_env *env = NULL;
@@ -6581,7 +6583,15 @@ struct btf *btf_parse_vmlinux(void)
if (err) {
btf_free(btf);
btf = ERR_PTR(err);
+ goto err_out;
}
+
+ /*
+ * With CONFIG_DEBUG_INFO_BTF=m, kfunc, dtor kfunc and struct_ops
+ * registrations for vmlinux made before the BTF was available were
+ * queued; apply them now, before the BTF becomes visible to anyone.
+ */
+ btf_apply_deferred_vmlinux_regs(btf);
err_out:
btf_verifier_env_free(env);
return btf;
@@ -8697,13 +8707,62 @@ enum {
#define BTF_MODULE_NOTIFIER 1
#endif
+/*
+ * CONFIG_DEBUG_INFO_BTF=m: a kfunc, dtor kfunc or struct_ops registration
+ * made while the BTF it applies to is not available yet. Kept until the BTF
+ * arrives, see btf_defer_reg() and btf_apply_deferred_regs().
+ */
+enum btf_deferred_reg_kind {
+ BTF_DEFERRED_KFUNC_SET,
+ BTF_DEFERRED_DTOR_KFUNCS,
+ BTF_DEFERRED_STRUCT_OPS,
+};
+
+struct btf_deferred_reg {
+ struct list_head list;
+ enum btf_deferred_reg_kind kind;
+ /* set while the registration is queued for a module BTF */
+ struct btf *btf;
+ struct module *module;
+ union {
+ struct {
+ enum btf_kfunc_hook hook;
+ const struct btf_kfunc_id_set *kset;
+ } kfunc;
+ struct {
+ const struct btf_id_dtor_kfunc *dtors;
+ u32 cnt;
+ } dtor;
+ struct bpf_struct_ops *st_ops;
+ };
+};
+
#ifdef BTF_MODULE_NOTIFIER
+static void btf_free_deferred_regs(struct list_head *regs);
+#if IS_MODULE(CONFIG_DEBUG_INFO_BTF)
+static void btf_free_deferred_reg(struct btf_deferred_reg *reg);
+static void btf_apply_deferred_regs(struct list_head *regs);
+#endif
+
struct btf_module {
struct list_head list;
struct module *module;
struct btf *btf;
struct bin_attribute *sysfs_attr;
int flags;
+ /*
+ * CONFIG_DEBUG_INFO_BTF=m: a module loaded before the vmlinux BTF is
+ * available cannot have its BTF parsed yet. Its .BTF and .BTF.base
+ * sections are copied here and parsed once the vmlinux BTF arrives
+ * (btf_parse_deferred_modules()); @btf is NULL until then.
+ * Registrations of the module's kfuncs, dtor kfuncs and struct_ops
+ * wait in @deferred_regs.
+ */
+ void *data;
+ void *base_data;
+ u32 data_size;
+ u32 base_data_size;
+ struct list_head deferred_regs;
};
static LIST_HEAD(btf_modules);
@@ -8747,8 +8806,14 @@ static void btf_module_free(struct btf_module *btf_mod)
{
if (btf_mod->sysfs_attr)
sysfs_remove_bin_file(btf_kobj, btf_mod->sysfs_attr);
- purge_cand_cache(btf_mod->btf);
- btf_put(btf_mod->btf);
+ if (btf_mod->btf) {
+ purge_cand_cache(btf_mod->btf);
+ btf_put(btf_mod->btf);
+ } else {
+ kvfree(btf_mod->data);
+ kvfree(btf_mod->base_data);
+ }
+ btf_free_deferred_regs(&btf_mod->deferred_regs);
kfree(btf_mod->sysfs_attr);
kfree(btf_mod);
}
@@ -8792,11 +8857,55 @@ static int btf_vmlinux_module_coming(struct module *mod)
smp_store_release(&btf_vmlinux_raw, data);
return 0;
}
+
+/*
+ * The vmlinux BTF is not available yet and must not be loaded from the
+ * module notifier (that would nest a module load into a module load). Keep
+ * the module's BTF for btf_parse_deferred_modules(). The .BTF data can be
+ * exposed in sysfs right away, it needs no parsing.
+ */
+static int btf_module_defer(struct btf_module *btf_mod, struct module *mod)
+{
+ int err;
+
+ btf_mod->data = kvmemdup(mod->btf_data, mod->btf_data_size,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!btf_mod->data)
+ return -ENOMEM;
+ btf_mod->data_size = mod->btf_data_size;
+
+ if (mod->btf_base_data) {
+ btf_mod->base_data = kvmemdup(mod->btf_base_data,
+ mod->btf_base_data_size,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!btf_mod->base_data) {
+ kvfree(btf_mod->data);
+ return -ENOMEM;
+ }
+ btf_mod->base_data_size = mod->btf_base_data_size;
+ }
+
+ err = btf_module_sysfs_add(btf_mod, mod->name, btf_mod->data,
+ btf_mod->data_size);
+ if (err) {
+ kvfree(btf_mod->data);
+ kvfree(btf_mod->base_data);
+ return err;
+ }
+
+ list_add(&btf_mod->list, &btf_modules);
+ return 0;
+}
#else
static int btf_vmlinux_module_coming(struct module *mod)
{
return 0;
}
+
+static int btf_module_defer(struct btf_module *btf_mod, struct module *mod)
+{
+ return 0;
+}
#endif
static int btf_module_notify(struct notifier_block *nb, unsigned long op,
@@ -8828,6 +8937,24 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
goto out;
}
btf_mod->module = module;
+ INIT_LIST_HEAD(&btf_mod->deferred_regs);
+
+ if (IS_MODULE(CONFIG_DEBUG_INFO_BTF)) {
+ mutex_lock(&btf_module_mutex);
+ /* Pairs with the publication in bpf_get_btf_vmlinux() */
+ if (!smp_load_acquire(&btf_vmlinux)) {
+ err = btf_module_defer(btf_mod, mod);
+ mutex_unlock(&btf_module_mutex);
+ if (err) {
+ pr_warn("failed to keep module [%s] BTF: %d\n",
+ mod->name, err);
+ kfree(btf_mod);
+ err = 0;
+ }
+ goto out;
+ }
+ mutex_unlock(&btf_module_mutex);
+ }
btf = btf_parse_module(mod->name, bpf_get_btf_vmlinux(),
mod->btf_data, mod->btf_data_size, false,
@@ -8882,7 +9009,8 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
* btf_try_get_module() on such BTFs will fail. This may
* be called again on btf_put(), but it's ok to do so.
*/
- btf_free_id(btf_mod->btf);
+ if (btf_mod->btf)
+ btf_free_id(btf_mod->btf);
list_del(&btf_mod->list);
btf_module_free(btf_mod);
break;
@@ -8905,6 +9033,87 @@ static int __init btf_module_init(void)
}
fs_initcall(btf_module_init);
+
+#if IS_MODULE(CONFIG_DEBUG_INFO_BTF)
+/*
+ * CONFIG_DEBUG_INFO_BTF=m: the vmlinux BTF has just become available. Parse
+ * the BTF of the modules that were loaded before it, and apply the
+ * registrations that waited for them. Called from bpf_get_btf_vmlinux()
+ * once btf_vmlinux is published, with no locks held.
+ */
+void btf_parse_deferred_modules(void)
+{
+ /* Pairs with the publication in bpf_get_btf_vmlinux() */
+ struct btf *vmlinux_btf = smp_load_acquire(&btf_vmlinux);
+ struct btf_deferred_reg *reg, *rtmp;
+ struct btf_module *btf_mod, *tmp;
+ bool parsed = false;
+ LIST_HEAD(regs);
+ struct btf *btf;
+ int err;
+
+ if (IS_ERR_OR_NULL(vmlinux_btf))
+ return;
+
+ mutex_lock(&btf_module_mutex);
+ list_for_each_entry_safe(btf_mod, tmp, &btf_modules, list) {
+ if (btf_mod->btf)
+ continue;
+
+ btf = btf_parse_module(btf_mod->module->name, vmlinux_btf,
+ btf_mod->data, btf_mod->data_size, true,
+ btf_mod->base_data, btf_mod->base_data_size);
+ err = PTR_ERR_OR_ZERO(btf);
+ if (!err) {
+ err = btf_alloc_id(btf);
+ if (err) {
+ /* btf owns the data now, btf_free() drops it */
+ btf_mod->data = NULL;
+ btf_free(btf);
+ }
+ }
+ if (err) {
+ /*
+ * The module is loaded and stays. Unlike at load time
+ * there is no way to reject it, so drop its BTF.
+ */
+ pr_warn("failed to validate module [%s] BTF: %d\n",
+ btf_mod->module->name, err);
+ list_del(&btf_mod->list);
+ btf_module_free(btf_mod);
+ continue;
+ }
+
+ /* btf->data is btf_mod->data now, the sysfs file keeps working */
+ btf_mod->data = NULL;
+ kvfree(btf_mod->base_data);
+ btf_mod->base_data = NULL;
+ btf_mod->btf = btf;
+ parsed = true;
+
+ /*
+ * Registrations are applied after dropping the mutex (they
+ * walk btf_modules); pin what they need until then.
+ */
+ list_for_each_entry_safe(reg, rtmp, &btf_mod->deferred_regs, list) {
+ list_del(®->list);
+ if (!try_module_get(btf_mod->module)) {
+ btf_free_deferred_reg(reg);
+ continue;
+ }
+ btf_get(btf);
+ reg->btf = btf;
+ reg->module = btf_mod->module;
+ list_add_tail(®->list, ®s);
+ }
+ }
+ mutex_unlock(&btf_module_mutex);
+
+ if (parsed)
+ purge_cand_cache(NULL);
+ btf_apply_deferred_regs(®s);
+}
+#endif /* IS_MODULE(CONFIG_DEBUG_INFO_BTF) */
#endif /* BTF_MODULE_NOTIFIER */
struct module *btf_try_get_module(const struct btf *btf)
@@ -8957,8 +9166,11 @@ struct btf *btf_get_module_btf(const struct module *module)
if (btf_mod->module != module)
continue;
- btf_get(btf_mod->btf);
- btf = btf_mod->btf;
+ /* NULL while waiting for the vmlinux BTF (CONFIG_DEBUG_INFO_BTF=m) */
+ if (btf_mod->btf) {
+ btf_get(btf_mod->btf);
+ btf = btf_mod->btf;
+ }
break;
}
mutex_unlock(&btf_module_mutex);
@@ -9135,7 +9347,8 @@ static int btf_check_kfunc_name(struct btf *btf, const char *func_name, u32 kind
#ifdef CONFIG_DEBUG_INFO_BTF_MODULES
guard(mutex)(&btf_module_mutex);
list_for_each_entry_safe(btf_mod, tmp, &btf_modules, list) {
- if (btf_mod->btf == btf)
+ /* skip ourselves and, with CONFIG_DEBUG_INFO_BTF=m, unparsed BTF */
+ if (btf_mod->btf == btf || !btf_mod->btf)
continue;
id = btf_find_by_name_kind(btf_mod->btf, func_name, kind);
if (id >= 0) {
@@ -9475,12 +9688,22 @@ static int btf_kfunc_id_set_add(struct btf *btf, enum btf_kfunc_hook hook,
return btf_populate_kfunc_set(btf, hook, kset);
}
+static int btf_defer_reg(struct module *owner, const struct btf_deferred_reg *tmpl);
+
static int __register_btf_kfunc_id_set(enum btf_kfunc_hook hook,
const struct btf_kfunc_id_set *kset)
{
+ struct btf_deferred_reg tmpl = {
+ .kind = BTF_DEFERRED_KFUNC_SET,
+ .kfunc = { .hook = hook, .kset = kset },
+ };
struct btf *btf;
int ret;
+ ret = btf_defer_reg(kset->owner, &tmpl);
+ if (ret)
+ return ret > 0 ? 0 : ret;
+
btf = btf_get_module_btf(kset->owner);
if (!btf)
return check_btf_kconfigs(kset->owner, "kfunc");
@@ -9649,9 +9872,17 @@ static int btf_dtor_kfuncs_add(struct btf *btf, const struct btf_id_dtor_kfunc *
int register_btf_id_dtor_kfuncs(const struct btf_id_dtor_kfunc *dtors, u32 add_cnt,
struct module *owner)
{
+ struct btf_deferred_reg tmpl = {
+ .kind = BTF_DEFERRED_DTOR_KFUNCS,
+ .dtor = { .dtors = dtors, .cnt = add_cnt },
+ };
struct btf *btf;
int ret;
+ ret = btf_defer_reg(owner, &tmpl);
+ if (ret)
+ return ret > 0 ? 0 : ret;
+
btf = btf_get_module_btf(owner);
if (!btf)
return check_btf_kconfigs(owner, "dtor kfuncs");
@@ -10321,9 +10552,17 @@ static int btf_struct_ops_add(struct btf *btf, struct bpf_struct_ops *st_ops)
int __register_bpf_struct_ops(struct bpf_struct_ops *st_ops)
{
+ struct btf_deferred_reg tmpl = {
+ .kind = BTF_DEFERRED_STRUCT_OPS,
+ .st_ops = st_ops,
+ };
struct btf *btf;
int err;
+ err = btf_defer_reg(st_ops->owner, &tmpl);
+ if (err)
+ return err > 0 ? 0 : err;
+
btf = btf_get_module_btf(st_ops->owner);
if (!btf)
return check_btf_kconfigs(st_ops->owner, "struct_ops");
@@ -10335,8 +10574,183 @@ int __register_bpf_struct_ops(struct bpf_struct_ops *st_ops)
return err;
}
EXPORT_SYMBOL_GPL(__register_bpf_struct_ops);
+#else
+static int btf_struct_ops_add(struct btf *btf, struct bpf_struct_ops *st_ops)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+/*
+ * CONFIG_DEBUG_INFO_BTF=m: registrations made before the BTF they apply to
+ * is available. Registrations for vmlinux wait in btf_vmlinux_deferred_regs
+ * until btf_parse_vmlinux() applies them; registrations for a module wait in
+ * its struct btf_module until btf_parse_deferred_modules() does. Both lists
+ * are protected by btf_module_mutex.
+ */
+#ifdef BTF_MODULE_NOTIFIER
+static LIST_HEAD(btf_vmlinux_deferred_regs);
+/* Set when the vmlinux BTF is parsed; new registrations apply directly */
+static bool btf_vmlinux_regs_closed;
+
+/*
+ * Queue @tmpl if the BTF for @owner is not available yet. Returns 1 if the
+ * registration was queued and is to be considered done, 0 if the caller has
+ * to apply it, or -ENOMEM.
+ */
+static int btf_defer_reg(struct module *owner, const struct btf_deferred_reg *tmpl)
+{
+ struct list_head *head = NULL;
+ struct btf_deferred_reg *reg;
+ struct btf_module *btf_mod;
+
+ if (!IS_MODULE(CONFIG_DEBUG_INFO_BTF))
+ return 0;
+
+ guard(mutex)(&btf_module_mutex);
+ if (!owner) {
+ if (!btf_vmlinux_regs_closed)
+ head = &btf_vmlinux_deferred_regs;
+ } else {
+ list_for_each_entry(btf_mod, &btf_modules, list) {
+ if (btf_mod->module != owner)
+ continue;
+ if (!btf_mod->btf)
+ head = &btf_mod->deferred_regs;
+ break;
+ }
+ }
+ if (!head)
+ return 0;
+
+ reg = kmemdup(tmpl, sizeof(*reg), GFP_KERNEL);
+ if (!reg)
+ return -ENOMEM;
+ /*
+ * kfunc id sets and struct_ops are static data of their owner, but
+ * the dtor arrays are commonly built on the stack of the initcall.
+ */
+ if (reg->kind == BTF_DEFERRED_DTOR_KFUNCS) {
+ reg->dtor.dtors = kmemdup_array(tmpl->dtor.dtors, tmpl->dtor.cnt,
+ sizeof(*tmpl->dtor.dtors), GFP_KERNEL);
+ if (!reg->dtor.dtors) {
+ kfree(reg);
+ return -ENOMEM;
+ }
+ }
+ list_add_tail(®->list, head);
+ return 1;
+}
+
+static void btf_free_deferred_reg(struct btf_deferred_reg *reg)
+{
+ if (reg->kind == BTF_DEFERRED_DTOR_KFUNCS)
+ kfree(reg->dtor.dtors);
+ kfree(reg);
+}
+
+static const char *btf_deferred_reg_name(const struct btf_deferred_reg *reg)
+{
+ switch (reg->kind) {
+ case BTF_DEFERRED_KFUNC_SET: return "kfunc set";
+ case BTF_DEFERRED_DTOR_KFUNCS: return "dtor kfuncs";
+ case BTF_DEFERRED_STRUCT_OPS: return "struct_ops";
+ }
+ return "?";
+}
+
+static int btf_apply_deferred_reg(struct btf *btf, const struct btf_deferred_reg *reg)
+{
+ switch (reg->kind) {
+ case BTF_DEFERRED_KFUNC_SET:
+ return btf_kfunc_id_set_add(btf, reg->kfunc.hook, reg->kfunc.kset);
+ case BTF_DEFERRED_DTOR_KFUNCS:
+ return btf_dtor_kfuncs_add(btf, reg->dtor.dtors, reg->dtor.cnt);
+ case BTF_DEFERRED_STRUCT_OPS:
+ return btf_struct_ops_add(btf, reg->st_ops);
+ }
+ return -EINVAL;
+}
+
+#if IS_MODULE(CONFIG_DEBUG_INFO_BTF)
+/* Apply and free the registrations in @regs; each is pinned to its BTF and module. */
+static void btf_apply_deferred_regs(struct list_head *regs)
+{
+ struct btf_deferred_reg *reg, *tmp;
+ int err;
+
+ list_for_each_entry_safe(reg, tmp, regs, list) {
+ err = btf_apply_deferred_reg(reg->btf, reg);
+ if (err)
+ pr_warn("failed to register deferred %s for module [%s] BTF: %d\n",
+ btf_deferred_reg_name(reg), reg->btf->name, err);
+ btf_put(reg->btf);
+ module_put(reg->module);
+ list_del(®->list);
+ btf_free_deferred_reg(reg);
+ }
+}
#endif
+static void btf_free_deferred_regs(struct list_head *regs)
+{
+ struct btf_deferred_reg *reg, *tmp;
+
+ list_for_each_entry_safe(reg, tmp, regs, list) {
+ list_del(®->list);
+ btf_free_deferred_reg(reg);
+ }
+}
+
+/*
+ * The vmlinux BTF has just been parsed; apply the registrations that waited
+ * for it. Runs under btf_vmlinux_lock, before @btf is published, so nothing
+ * can observe a vmlinux BTF without its kfuncs and struct_ops.
+ *
+ * Applying a registration can queue further ones: a struct_ops ->init()
+ * registers the kfuncs of its hook. Those must not go through
+ * bpf_get_btf_vmlinux() (we hold its lock), so the queue stays open until
+ * a pass applies nothing new, and only then are registrations applied directly.
+ */
+static void btf_apply_deferred_vmlinux_regs(struct btf *btf)
+{
+ struct btf_deferred_reg *reg, *tmp;
+ LIST_HEAD(regs);
+ int err;
+
+ if (!IS_MODULE(CONFIG_DEBUG_INFO_BTF))
+ return;
+
+ mutex_lock(&btf_module_mutex);
+ while (!list_empty(&btf_vmlinux_deferred_regs)) {
+ list_splice_init(&btf_vmlinux_deferred_regs, ®s);
+ mutex_unlock(&btf_module_mutex);
+
+ list_for_each_entry_safe(reg, tmp, ®s, list) {
+ err = btf_apply_deferred_reg(btf, reg);
+ if (err)
+ pr_warn("failed to register deferred %s for vmlinux BTF: %d\n",
+ btf_deferred_reg_name(reg), err);
+ list_del(®->list);
+ btf_free_deferred_reg(reg);
+ }
+
+ mutex_lock(&btf_module_mutex);
+ }
+ btf_vmlinux_regs_closed = true;
+ mutex_unlock(&btf_module_mutex);
+}
+#else
+static int btf_defer_reg(struct module *owner, const struct btf_deferred_reg *tmpl)
+{
+ return 0;
+}
+
+static void btf_apply_deferred_vmlinux_regs(struct btf *btf)
+{
+}
+#endif /* BTF_MODULE_NOTIFIER */
+
bool btf_param_match_suffix(const struct btf *btf,
const struct btf_param *arg,
const char *suffix)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index a7b73bc146a8..b6f094d5306a 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -21160,12 +21160,14 @@ int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 bt
/*
* Returns the parsed vmlinux BTF, NULL if the kernel has none, or an ERR_PTR
* if it is malformed. With CONFIG_DEBUG_INFO_BTF=m the BTF lives in the
- * btf_vmlinux module; the first caller loads it and parses it. May sleep.
+ * btf_vmlinux module; the first caller loads it, parses it and then registers
+ * the BTF of the modules that were loaded before it. May sleep.
*/
struct btf *bpf_get_btf_vmlinux(void)
{
/* Pairs with the smp_store_release() on the parse path below. */
struct btf *btf = smp_load_acquire(&btf_vmlinux);
+ bool parsed = false;
u32 size;
if (btf || !IS_ENABLED(CONFIG_DEBUG_INFO_BTF))
@@ -21190,8 +21192,13 @@ struct btf *bpf_get_btf_vmlinux(void)
* on the lockless fast path above.
*/
smp_store_release(&btf_vmlinux, btf);
+ parsed = true;
}
mutex_unlock(&btf_vmlinux_lock);
+
+ if (parsed && IS_MODULE(CONFIG_DEBUG_INFO_BTF))
+ btf_parse_deferred_modules();
+
return btf;
}
--
2.47.3
next prev parent reply other threads:[~2026-09-23 5:41 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-23 5:39 [PATCH bpf-next 0/6] bpf: make the vmlinux BTF an on-demand loadable module (CONFIG_DEBUG_INFO_BTF=m) to save ~5.4 MB memory Jay Wang
2026-09-23 5:39 ` [PATCH bpf-next 1/6] bpf: pass the vmlinux BTF to btf_parse_module() and let it adopt the data Jay Wang
2026-09-23 5:39 ` [PATCH bpf-next 2/6] bpf: split the kfunc, dtor kfunc and struct_ops registration bodies Jay Wang
2026-09-23 6:16 ` bot+bpf-ci
2026-09-23 5:39 ` [PATCH bpf-next 3/6] bpf: fetch the vmlinux BTF where kernel types enter a program Jay Wang
2026-09-23 6:28 ` bot+bpf-ci
2026-09-23 5:39 ` [PATCH bpf-next 4/6] bpf: take the vmlinux BTF from the btf_vmlinux module Jay Wang
2026-09-23 5:39 ` Jay Wang [this message]
2026-09-23 6:41 ` [PATCH bpf-next 5/6] bpf: defer registrations until the vmlinux BTF is available bot+bpf-ci
2026-09-23 5:39 ` [PATCH bpf-next 6/6] kbuild, bpf: allow building the vmlinux BTF as a module Jay Wang
2026-09-23 8:27 ` [PATCH bpf-next 0/6] bpf: make the vmlinux BTF an on-demand loadable module (CONFIG_DEBUG_INFO_BTF=m) to save ~5.4 MB memory Alan Maguire
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260923053948.30617-6-wanjay@amazon.com \
--to=wanjay@amazon.com \
--cc=abuehaze@amazon.com \
--cc=alan.maguire@oracle.com \
--cc=andrii@kernel.org \
--cc=arnd@arndb.de \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=doebel@amazon.de \
--cc=eddyz87@gmail.com \
--cc=jay.wang.upstream@gmail.com \
--cc=linux-kbuild@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-modules@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=mcgrof@kernel.org \
--cc=memxor@gmail.com \
--cc=nathan@kernel.org \
--cc=nsc@kernel.org \
--cc=petr.pavlu@suse.com \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®