Skip to contentSkip to conversation

bpf: btf: make vmlinux BTF disposable at boot to save ~5 MiB

PATCH In review Jay Wang 3 participants 6 messages Sep 17 – Sep 23

Subsystems (6)
Documentation x86 BPF arm64 +2 more subsystems
lore
Share & tools
lore

Jay Wang

author
#0 lore raw

cover bpf: btf: make vmlinux BTF disposable at boot to save ~5 MiB

Based on and tested against mainline commit 9b87fdc9af2f ("Merge tag 'sched_ext-for-7.3-rc3-fixes' of
git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext").

This patch adds a "btf=off" boot parameter that disables the vmlinux BTF and frees the pages backing it, recovering ~5 MiB. The reasons for making it a boot-time choice are below.

A distribution ships one kernel binary to every user, so each CONFIG_* choice applies to all of them. CONFIG_DEBUG_INFO_BTF is one where that
hurts: the vmlinux .BTF section stays in rodata for the whole boot, 5.2 MiB on x86-64 and 5.3 MiB on arm64 with a distribution config. On a 1 GiB VM, a common cloud instance size, that is a visible part of total memory. Off takes BTF away from the users who need it; on charges the users who never touch it.

Letting the user decide at boot follows the existing CONFIG_SECURITY_SELINUX_BOOTPARAM, which does the same for selinux=0. With btf=off the kernel behaves as if CONFIG_DEBUG_INFO_BTF were not set, so nothing can reach the section and its pages are freed. BPF features needing kernel type information then fail program load cleanly, exactly as on a config-off kernel. By default the parameter is not set and nothing changes.

The freeing implementation is architecture-specific, and btf=off is currently supported on x86-64 and arm64.

Testing: x86-64 and arm64 boot with and without btf=off. With btf=off, /sys/kernel/btf is absent and ~5 MiB is returned to the page allocator.

Jay Wang (1):
bpf: btf: add btf=off boot parameter to disable and free vmlinux BTF

 .../admin-guide/kernel-parameters.txt         |  14 +++
 arch/arm64/mm/mmu.c                           | 108 +++++++++++++++++-
 arch/x86/mm/init_64.c                         |  30 +++++
 arch/x86/mm/pat/set_memory.c                  |  28 ++++-
 include/linux/btf.h                           |   9 ++
 kernel/bpf/btf.c                              |  51 ++++++++-
 kernel/bpf/sysfs_btf.c                        |   7 ++
 kernel/bpf/verifier.c                         |   2 +-
 8 files changed, 241 insertions(+), 8 deletions(-)

base-commit: 9b87fdc9af2fbfcdb5c24a64139685ef80f6573f

Signature · 1 line
2.47.3
author
#1 lore raw

PATCH 1/1 bpf: btf: add btf=off boot parameter to disable and free vmlinux BTF

CONFIG_DEBUG_INFO_BTF puts the kernel's BTF type information in the vmlinux .BTF section, which stays in rodata for the whole boot: 5.2 MiB on x86-64 and 5.3 MiB on arm64 with a distribution config. On a 1 GiB VM, a common cloud instance size, that is a visible part of total memory.

The only way to drop it today is to build with CONFIG_DEBUG_INFO_BTF=n, which a distribution cannot do: it ships one binary to every user, so the choice applies to all of them. Off takes BTF away from the users who need it (CO-RE programs, fentry/fexit, kfunc calls, struct_ops, bpftrace); on charges the users who never touch it. Whether BTF is used depends on the workload, not on the build.

Add a "btf=off" early boot parameter, similar to the existing "selinux=0" parameter, so users who do not want BTF can turn it off at boot; the section is then freed to save memory.

By default btf=off is not set and the kernel behaves as before: BTF stays resident and read-only.

With btf=off the kernel behaves as if CONFIG_DEBUG_INFO_BTF were not set:

- bpf_get_btf_vmlinux() never parses the section and returns NULL, the same handled state as a config-off build; all call sites already tolerate NULL.
- /sys/kernel/btf is not created, so the section cannot be read or mapped from userspace.
- btf_module_notify() skips module BTF parsing. This is mandatory:
parsing would fail against the missing vmlinux BTF and, without CONFIG_MODULE_ALLOW_BTF_MISMATCH, veto every module load.
- check_btf_kconfigs() returns success, so boot-time kfunc, dtor-kfunc and struct_ops registration stays silent as it does with the config off.

Nothing can then reference the section, so the architecture frees it.

On x86-64, mark_rodata_ro() hands the range to free_kernel_image_pages(), alongside the existing text/rodata and rodata/data gap frees. The range is excluded from the rodata pinning in protect_rodata(), or the RW flip in free_init_pages() is silently refused and the poison write faults.

On arm64 the pages are freed and reused through the linear alias of the kernel image, which is block-mapped and cannot be split live without BBML2. map_mem() therefore maps the page-aligned interior of the section as a separate region of the alias and mark_linear_text_alias_ro() keeps those boundaries, so mark_rodata_ro() can flip the whole region back to PAGE_KERNEL - permissions only, legal at any granularity - and free it with free_reserved_area(), which also drops it from memblock.reserved as free_initmem() does. No memblock_free() pairing: memblock_free() itself releases to the buddy allocator once it is up, so the pair would double-free. The image mapping stays read-only in place; removing it would require splitting block mappings.

__start_BTF is PAGE_SIZE-aligned by the linker script; __stop_BTF is not, so a trailing partial page stays resident and read-only.

btf=off is currently supported on x86-64 and arm64 only. On other architectures it is ignored with a warning, rather than disabling BTF without recovering any memory.

---
 .../admin-guide/kernel-parameters.txt         |  14 +++
 arch/arm64/mm/mmu.c                           | 108 +++++++++++++++++-
 arch/x86/mm/init_64.c                         |  30 +++++
 arch/x86/mm/pat/set_memory.c                  |  28 ++++-
 include/linux/btf.h                           |   9 ++
 kernel/bpf/btf.c                              |  51 ++++++++-
 kernel/bpf/sysfs_btf.c                        |   7 ++
 kernel/bpf/verifier.c                         |   2 +-
 8 files changed, 241 insertions(+), 8 deletions(-)
Documentation/admin-guide/kernel-parameters.txt +14 -0
index 33cd30996e47..9ab9638b2840 100644
@@ -669,6 +669,20 @@ Kernel parameters
 
 			See Documentation/admin-guide/bootconfig.rst
 
+	btf=		[KNL,EARLY]
+			Format: { off }
+			Disable use of the kernel's BTF type information
+			(CONFIG_DEBUG_INFO_BTF), making the kernel behave
+			as if it were built without it: /sys/kernel/btf is
+			not created, module BTF is not registered, and BPF
+			features that require kernel type information
+			(CO-RE, fentry/fexit, kfunc calls, struct_ops) are
+			unavailable.  The memory backing the .BTF section
+			is freed after init, reducing the kernel's
+			resident memory footprint.  Only supported on
+			x86-64 and arm64; other architectures ignore this
+			parameter.
+
 	bttv.card=	[HW,V4L] bttv (bt848 + bt878 based grabber cards)
 	bttv.radio=	Most important insmod options are available as
 			kernel args too.
arch/arm64/mm/mmu.c +103 -5

154 lines not on this page · raw message

arch/x86/mm/init_64.c +30 -0
index 70e682180291..83a3b478202d 100644
@@ -33,6 +33,7 @@
 #include <linux/nmi.h>
 #include <linux/gfp.h>
 #include <linux/kcore.h>
+#include <linux/btf.h>
 
 #include <asm/processor.h>
 #include <asm/bios_ebda.h>
@@ -1400,6 +1401,35 @@ void mark_rodata_ro(void)
 				(void *)text_end, (void *)rodata_start);
 	free_kernel_image_pages("unused kernel image (rodata/data gap)",
 				(void *)rodata_end, (void *)_sdata);
+
+#if defined(CONFIG_DEBUG_INFO_BTF) && defined(CONFIG_BPF_SYSCALL)
+	/*
+	 * With btf=off nothing has parsed or exported the vmlinux BTF:
+	 * no sysfs attribute exists (so no mmap can exist) and no BTF
+	 * pointer has been handed out, so the pages backing the .BTF
+	 * section can be freed like the gaps above.
+	 *
+	 * .BTF lies inside __start_rodata..__end_rodata, which
+	 * protect_rodata() pins read-only now that
+	 * kernel_set_to_readonly is set.  The freed page range is
+	 * excluded from that pinning (see protect_rodata() in
+	 * arch/x86/mm/pat/set_memory.c); without the exclusion the RW
+	 * flip in free_init_pages() is silently refused and the poison
+	 * write faults.  __start_BTF is PAGE_SIZE-aligned by the linker
+	 * script; __stop_BTF is not, so the trailing partial page is
+	 * left in place (and stays protected rodata).
+	 */
+	if (btf_is_disabled()) {
+		extern char __start_BTF[], __stop_BTF[];
+		unsigned long btf_start = PAGE_ALIGN((unsigned long)__start_BTF);
+		unsigned long btf_end = (unsigned long)__stop_BTF & PAGE_MASK;
+
+		if (btf_start < btf_end)
+			free_kernel_image_pages("unused BTF (btf=off)",
+						(void *)btf_start,
+						(void *)btf_end);
+	}
+#endif
 }
 
 /*
arch/x86/mm/pat/set_memory.c +27 -1
index 4652487b5572..932e85dad394 100644
@@ -21,6 +21,7 @@
 #include <linux/kernel.h>
 #include <linux/cc_platform.h>
 #include <linux/set_memory.h>
+#include <linux/btf.h>
 #include <linux/memregion.h>
 #include <linux/cleanup.h>
 
@@ -547,7 +548,32 @@ static pgprotval_t protect_rodata(unsigned long spfn, unsigned long epfn)
 	 */
 	epfn_ro = PFN_DOWN(__pa_symbol(__end_rodata)) - 1;
 
-	if (kernel_set_to_readonly && overlaps(spfn, epfn, spfn_ro, epfn_ro))
+	if (!kernel_set_to_readonly)
+		return 0;
+
+#if defined(CONFIG_DEBUG_INFO_BTF) && defined(CONFIG_BPF_SYSCALL)
+	/*
+	 * btf=off: the pages backing the .BTF section are freed to the
+	 * page allocator at the end of mark_rodata_ro() and are then
+	 * ordinary pages that must not be pinned read-only - both for
+	 * the freeing itself (free_reserved_area() writes the poison
+	 * pattern through the direct map) and for any later CPA call on
+	 * whatever gets allocated from them.  Only the fully-freed page
+	 * range is excluded; a trailing partial page stays protected
+	 * rodata.
+	 */
+	if (btf_is_disabled()) {
+		extern char __start_BTF[], __stop_BTF[];
+		unsigned long spfn_btf = PFN_UP(__pa_symbol(__start_BTF));
+		unsigned long epfn_btf = PFN_DOWN(__pa_symbol(__stop_BTF));
+
+		if (spfn_btf < epfn_btf &&
+		    spfn >= spfn_btf && epfn <= epfn_btf - 1)
+			return 0;
+	}
+#endif
+
+	if (overlaps(spfn, epfn, spfn_ro, epfn_ro))
 		return _PAGE_RW;
 	return 0;
 }
include/linux/btf.h +9 -0
index 89d5a5c4f117..dd23d9b567e4 100644
@@ -11,6 +11,15 @@
 #include <uapi/linux/btf.h>
 #include <uapi/linux/bpf.h>
 
+#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_DEBUG_INFO_BTF)
+bool btf_is_disabled(void);
+#else
+static inline bool btf_is_disabled(void)
+{
+	return false;
+}
+#endif
+
 #define BTF_TYPE_EMIT(type) ((void)(type *)0)
 #define BTF_TYPE_EMIT_ENUM(enum_val) ((void)enum_val)
 
kernel/bpf/btf.c +50 -1

69 lines not on this page · raw message

kernel/bpf/sysfs_btf.c +7 -0
index 9cbe15ce3540..bab6f8db6cdd 100644
@@ -53,6 +53,13 @@ struct kobject *btf_kobj;
 
 static int __init btf_vmlinux_init(void)
 {
+	/*
+	 * btf=off: never expose the .BTF section; its backing pages are
+	 * freed at free_initmem() time.
+	 */
+	if (btf_is_disabled())
+		return 0;
+
 	bin_attr_btf_vmlinux.private = __start_BTF;
 	bin_attr_btf_vmlinux.size = __stop_BTF - __start_BTF;
 
kernel/bpf/verifier.c +1 -1
index 72a3f5998dd2..d61479b681f9 100644
@@ -20619,7 +20619,7 @@ 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);
 
-	if (!btf && IS_ENABLED(CONFIG_DEBUG_INFO_BTF)) {
+	if (!btf && IS_ENABLED(CONFIG_DEBUG_INFO_BTF) && !btf_is_disabled()) {
 		mutex_lock(&btf_vmlinux_lock);
 		btf = btf_vmlinux;
 		if (!btf) {
Signature · 1 line
2.47.3
#2 lore raw

Re: [PATCH bpf-next 1/1] bpf: btf: add btf=off boot parameter to disable and free vmlinux BTF

Quoted 4 lines · Jay Wang

Add a "btf=off" early boot parameter, similar to the existing "selinux=0" parameter, so users who do not want BTF can turn it off at boot; the section is then freed to save memory.

tbh I'm not excited about btf=off. BTF is not debug info anymore. sched_ext, bpf-lsm (systemd uses it), hid-bpf, most of bcc/bpftrace quietly stop working, and we'll be the ones triaging "libbpf: failed to find valid kernel BTF" reports from people who copy-pasted a "save 5MB" tuning guide.
This needs more thought.

Quoted 7 lines
 .../admin-guide/kernel-parameters.txt         |  14 +++
 arch/arm64/mm/mmu.c                           | 108 +++++++++++++++++-
 arch/x86/mm/init_64.c                         |  30 +++++
 arch/x86/mm/pat/set_memory.c                  |  28 ++++-
 include/linux/btf.h                           |   9 ++
 kernel/bpf/btf.c                              |  51 ++++++++-

kernel/bpf/sysfs_btf.c

                       |   7 ++
Quoted 1 line
 kernel/bpf/verifier.c                         |   2 +-

This doesn't apply to bpf-next.
Consider using Alan's approach and support CONFIG_DEBUG_INFO_BTF=m

pw-bot: cr

author
#3 lore raw

Re: [PATCH bpf-next 1/1] bpf: btf: add btf=off boot parameter to disable and free vmlinux BTF

Quoted 1 line

Consider using Alan's approach and support CONFIG_DEBUG_INFO_BTF=m

Thanks for the suggestion. We’ll explore how we can make CONFIG_DEBUG_INFO_BTF=m supportable and may post a v2 patch later.

Best,
Jay

On 9/16/26, 6:56 PM, "Alexei Starovoitov" <[email protected] <mailto:[email protected]>> wrote:

CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you can confirm the sender and know the content is safe.

Quoted 4 lines · On Thu, Sep 17, 2026 at 12:02 AM Jay Wang <[email protected]…

Add a "btf=off" early boot parameter, similar to the existing "selinux=0" parameter, so users who do not want BTF can turn it off at boot; the section is then freed to save memory.

tbh I'm not excited about btf=off. BTF is not debug info anymore. sched_ext, bpf-lsm (systemd uses it), hid-bpf, most of bcc/bpftrace quietly stop working, and we'll be the ones triaging "libbpf: failed to find valid kernel BTF" reports from people who copy-pasted a "save 5MB" tuning guide.
This needs more thought.

Quoted 7 lines

.../admin-guide/kernel-parameters.txt | 14 +++
arch/arm64/mm/mmu.c | 108 +++++++++++++++++-
arch/x86/mm/init_64.c | 30 +++++
arch/x86/mm/pat/set_memory.c | 28 ++++-
include/linux/btf.h | 9 ++
kernel/bpf/btf.c | 51 ++++++++-
kernel/bpf/sysfs_btf.c

| 7 ++

Quoted 1 line

kernel/bpf/verifier.c | 2 +-

This doesn't apply to bpf-next.
Consider using Alan's approach and support CONFIG_DEBUG_INFO_BTF=m

pw-bot: cr

#4 lore raw

Re: [PATCH bpf-next 1/1] bpf: btf: add btf=off boot parameter to disable and free vmlinux BTF

Quoted 7 lines · Jay
Quoted 1 line

Consider using Alan's approach and support CONFIG_DEBUG_INFO_BTF=m

Thanks for the suggestion. We’ll explore how we can make CONFIG_DEBUG_INFO_BTF=m supportable and may post a v2 patch later.

Best,
Jay

See some of the machinery in [1] for hints on how to do this. Ideally we would want the first user of vmlinux to force the module to load, /sys/kernel/btf/vmlinux to behave similarly as before etc. The approach there does this for vmlinux.inline so adapting something like that is likely the way to go.

The challenging part I suspect will be how to preserve your goal - avoiding memory allocation - while supporting module BTF. Specifically for cases where vmlinux BTF has not been used yet and modules load. Since you'd need to preserve the module BTF for later use on load (due to the nature of the ELF section it is in), I'm not sure that part would be feasible, unless you wanted to just support BTF for modules loaded after vmlinux BTF was.

On-demand vmlinux BTF availability would still be a win.

[1] https://lore.kernel.org/bpf/[email protected]/

Quoted 44 lines

On 9/16/26, 6:56 PM, "Alexei Starovoitov" <[email protected] <mailto:[email protected]>> wrote:

CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you can confirm the sender and know the content is safe.

Quoted 4 lines · On Thu, Sep 17, 2026 at 12:02 AM Jay Wang <[email protected]…

Add a "btf=off" early boot parameter, similar to the existing "selinux=0" parameter, so users who do not want BTF can turn it off at boot; the section is then freed to save memory.

tbh I'm not excited about btf=off. BTF is not debug info anymore. sched_ext, bpf-lsm (systemd uses it), hid-bpf, most of bcc/bpftrace quietly stop working, and we'll be the ones triaging "libbpf: failed to find valid kernel BTF" reports from people who copy-pasted a "save 5MB" tuning guide.
This needs more thought.

Quoted 7 lines

.../admin-guide/kernel-parameters.txt | 14 +++
arch/arm64/mm/mmu.c | 108 +++++++++++++++++-
arch/x86/mm/init_64.c | 30 +++++
arch/x86/mm/pat/set_memory.c | 28 ++++-
include/linux/btf.h | 9 ++
kernel/bpf/btf.c | 51 ++++++++-
kernel/bpf/sysfs_btf.c

| 7 ++

Quoted 1 line

kernel/bpf/verifier.c | 2 +-

This doesn't apply to bpf-next.
Consider using Alan's approach and support CONFIG_DEBUG_INFO_BTF=m

pw-bot: cr

author
#5 lore raw

Re: [PATCH bpf-next 1/1] bpf: btf: add btf=off boot parameter to disable and free vmlinux BTF

Quoted 1 line

The challenging part I suspect will be how to preserve your goal -

avoiding memory allocation - while supporting module BTF. Specifically for cases where vmlinux BTF has not been used yet and modules load. Since you'd need to preserve the module BTF for later use on load (due to the nature of the ELF section it is in), I'm not sure that part would be feasible, unless you wanted to just support BTF for modules loaded after vmlinux BTF

Thanks for pointing this out, Alan. I have finished CONFIG_DEBUG_INFO_BTF=m, including module BTF for modules loaded before the vmlinux BTF: https://lore.kernel.org/bpf/[email protected]/ cc:Alexei

On 9/17/26, 12:12 AM, "Alan Maguire" <[email protected] <mailto:[email protected]>> wrote:

CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you can confirm the sender and know the content is safe.

Quoted 7 lines · Jay
Quoted 1 line

Consider using Alan's approach and support CONFIG_DEBUG_INFO_BTF=m

Thanks for the suggestion. We’ll explore how we can make CONFIG_DEBUG_INFO_BTF=m supportable and may post a v2 patch later.

Best,
Jay

See some of the machinery in [1] for hints on how to do this. Ideally we would want the first user of vmlinux to force the module to load, /sys/kernel/btf/vmlinux to behave similarly as before etc. The approach there does this for vmlinux.inline so adapting something like that is likely the way to go.

The challenging part I suspect will be how to preserve your goal - avoiding memory allocation - while supporting module BTF. Specifically for cases where vmlinux BTF has not been used yet and modules load. Since you'd need to preserve the module BTF for later use on load (due to the nature of the ELF section it is in), I'm not sure that part would be feasible, unless you wanted to just support BTF for modules loaded after vmlinux BTF was.

On-demand vmlinux BTF availability would still be a win.

[1] https://lore.kernel.org/bpf/[email protected] <mailto:[email protected]>/

Quoted 44 lines · On 9/16/26, 6:56 PM, "Alexei Starovoitov" <[email protected]…

CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you can confirm the sender and know the content is safe.

Quoted 4 lines · On Thu, Sep 17, 2026 at 12:02 AM Jay Wang <[email protected]…

Add a "btf=off" early boot parameter, similar to the existing "selinux=0" parameter, so users who do not want BTF can turn it off at boot; the section is then freed to save memory.

tbh I'm not excited about btf=off. BTF is not debug info anymore. sched_ext, bpf-lsm (systemd uses it), hid-bpf, most of bcc/bpftrace quietly stop working, and we'll be the ones triaging "libbpf: failed to find valid kernel BTF" reports from people who copy-pasted a "save 5MB" tuning guide.
This needs more thought.

Quoted 7 lines

.../admin-guide/kernel-parameters.txt | 14 +++
arch/arm64/mm/mmu.c | 108 +++++++++++++++++-
arch/x86/mm/init_64.c | 30 +++++
arch/x86/mm/pat/set_memory.c | 28 ++++-
include/linux/btf.h | 9 ++
kernel/bpf/btf.c | 51 ++++++++-
kernel/bpf/sysfs_btf.c

| 7 ++

Quoted 1 line

kernel/bpf/verifier.c | 2 +-

This doesn't apply to bpf-next.
Consider using Alan's approach and support CONFIG_DEBUG_INFO_BTF=m

pw-bot: cr