mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jay Wang <wanjay@amazon.com>
To: <bpf@vger.kernel.org>, <ast@kernel.org>, <daniel@iogearbox.net>,
	<andrii@kernel.org>
Cc: <yonghong.song@linux.dev>, <catalin.marinas@arm.com>,
	<will@kernel.org>, <x86@kernel.org>,
	<dave.hansen@linux.intel.com>,
	<linux-arm-kernel@lists.infradead.org>,
	<linux-doc@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<abuehaze@amazon.com>, <doebel@amazon.de>,
	<jay.wang.upstream@gmail.com>
Subject: [PATCH bpf-next 1/1] bpf: btf: add btf=off boot parameter to disable and free vmlinux BTF
Date: Thu, 17 Sep 2026 00:02:01 +0000	[thread overview]
Message-ID: <20260917000201.25581-2-wanjay@amazon.com> (raw)
In-Reply-To: <20260917000201.25581-1-wanjay@amazon.com>

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.

Signed-off-by: Jay Wang <wanjay@amazon.com>
---
 .../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(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 33cd30996e47..9ab9638b2840 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -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.
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 79d90226fd5d..16c853d9640e 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -30,6 +30,8 @@
 #include <linux/mm_inline.h>
 #include <linux/pagewalk.h>
 #include <linux/stop_machine.h>
+#include <linux/btf.h>
+#include <linux/poison.h>
 
 #include <asm/barrier.h>
 #include <asm/cputype.h>
@@ -1054,6 +1056,39 @@ static void update_mapping_prot(phys_addr_t phys, unsigned long virt,
 	flush_tlb_kernel_range(virt, virt + size);
 }
 
+#if defined(CONFIG_DEBUG_INFO_BTF) && defined(CONFIG_BPF_SYSCALL)
+extern char __start_BTF[], __stop_BTF[];
+
+/*
+ * With btf=off the pages backing the vmlinux .BTF section are freed by
+ * mark_rodata_ro() and later reused through the linear alias of the
+ * kernel image.  Block mappings cannot be split live without BBML2,
+ * which not all CPUs provide, so map_mem() maps the page-aligned
+ * interior of the section as a separate region of the alias: mapping
+ * boundaries then coincide with its edges, and its permissions can be
+ * changed as a whole without ever splitting a block mapping.
+ *
+ * Returns true and the region's kernel image virtual bounds when the
+ * free is pending.  early_param() is parsed before map_mem(), so the
+ * decision is stable by the time it is first needed.  __stop_BTF is
+ * not necessarily page-aligned; a trailing partial page stays
+ * resident (and read-only).
+ */
+static bool btf_free_range(unsigned long *start, unsigned long *end)
+{
+	*start = PAGE_ALIGN((unsigned long)__start_BTF);
+	*end = (unsigned long)__stop_BTF & PAGE_MASK;
+
+	return btf_is_disabled() && *start < *end;
+}
+#else
+static bool btf_free_range(unsigned long *start, unsigned long *end)
+{
+	*start = *end = 0;
+	return false;
+}
+#endif
+
 static void __init __map_memblock(phys_addr_t start, phys_addr_t end,
 				  pgprot_t prot, int flags)
 {
@@ -1086,12 +1121,33 @@ static int arm64_hibernate_pm_notify(struct notifier_block *nb,
 
 void __init mark_linear_text_alias_ro(void)
 {
+	unsigned long btf_start, btf_end;
+
 	/*
 	 * Remove the write permissions from the linear alias of .text/.rodata
+	 *
+	 * With btf=off the alias consists of three regions (see map_mem());
+	 * remap it with the same boundaries so that every entry keeps its
+	 * granularity and only the permissions change.
 	 */
-	update_mapping_prot(__pa_symbol(_text), (unsigned long)lm_alias(_text),
-			    (unsigned long)__init_begin - (unsigned long)_text,
-			    PAGE_KERNEL_RO);
+	if (btf_free_range(&btf_start, &btf_end)) {
+		update_mapping_prot(__pa_symbol(_text),
+				    (unsigned long)lm_alias(_text),
+				    btf_start - (unsigned long)_text,
+				    PAGE_KERNEL_RO);
+		update_mapping_prot(__pa_symbol(btf_start),
+				    (unsigned long)lm_alias(btf_start),
+				    btf_end - btf_start, PAGE_KERNEL_RO);
+		update_mapping_prot(__pa_symbol(btf_end),
+				    (unsigned long)lm_alias(btf_end),
+				    (unsigned long)__init_begin - btf_end,
+				    PAGE_KERNEL_RO);
+	} else {
+		update_mapping_prot(__pa_symbol(_text),
+				    (unsigned long)lm_alias(_text),
+				    (unsigned long)__init_begin - (unsigned long)_text,
+				    PAGE_KERNEL_RO);
+	}
 
 	/*
 	 * Register a PM notifier to remap the linear alias of data/bss as
@@ -1185,6 +1241,7 @@ static void __init map_mem(void)
 	phys_addr_t init_end = __pa_symbol(__init_end);
 	phys_addr_t kernel_end = __pa_symbol(__bss_stop);
 	phys_addr_t start, end;
+	unsigned long btf_start, btf_end;
 	int flags = NO_EXEC_MAPPINGS;
 	u64 i;
 
@@ -1217,9 +1274,23 @@ static void __init map_mem(void)
 	 * removed later by mark_linear_text_alias_ro() above. This makes the
 	 * contents of the region accessible to subsystems such as hibernate,
 	 * but protects it from inadvertent modification or execution.
+	 *
+	 * With btf=off the .BTF section becomes a separate region of the
+	 * alias, so that mark_rodata_ro() can change its permissions and free
+	 * it as a whole without splitting any block mapping (see
+	 * btf_free_range()).
 	 */
-	__map_memblock(kernel_start, init_begin, pgprot_tagged(PAGE_KERNEL),
-		       flags);
+	if (btf_free_range(&btf_start, &btf_end)) {
+		__map_memblock(kernel_start, __pa_symbol(btf_start),
+			       pgprot_tagged(PAGE_KERNEL), flags);
+		__map_memblock(__pa_symbol(btf_start), __pa_symbol(btf_end),
+			       pgprot_tagged(PAGE_KERNEL), flags);
+		__map_memblock(__pa_symbol(btf_end), init_begin,
+			       pgprot_tagged(PAGE_KERNEL), flags);
+	} else {
+		__map_memblock(kernel_start, init_begin,
+			       pgprot_tagged(PAGE_KERNEL), flags);
+	}
 
 	/* Map the kernel data/bss so it can be remapped later */
 	__map_memblock(init_end, kernel_end, pgprot_tagged(PAGE_KERNEL),
@@ -1254,6 +1325,7 @@ static void __init map_mem(void)
 void mark_rodata_ro(void)
 {
 	unsigned long section_size;
+	unsigned long btf_start, btf_end;
 
 	/*
 	 * mark .rodata as read only. Use __init_begin rather than __end_rodata
@@ -1270,6 +1342,32 @@ void mark_rodata_ro(void)
 
 	/* Map the kernel data/bss as invalid in the linear map */
 	mark_linear_data_alias_valid(false);
+
+	/*
+	 * 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 returned to the page allocator.
+	 *
+	 * The pages are freed, poisoned and later reused through their
+	 * linear alias, which map_mem() mapped as a separate region and
+	 * mark_linear_text_alias_ro() made read-only: flip the whole
+	 * region back to PAGE_KERNEL - a permission-only change - and
+	 * hand the pages over, mirroring free_initmem().  The kernel
+	 * image mapping of the section (made read-only just above) is
+	 * left in place: it cannot be written through, and removing it
+	 * would require splitting live block mappings.
+	 */
+	if (btf_free_range(&btf_start, &btf_end)) {
+		void *lm_start = lm_alias((void *)btf_start);
+		void *lm_end = lm_alias((void *)btf_end);
+
+		update_mapping_prot(__pa_symbol(btf_start),
+				    (unsigned long)lm_start,
+				    btf_end - btf_start, PAGE_KERNEL);
+		free_reserved_area(lm_start, lm_end, POISON_FREE_INITMEM,
+				   "unused BTF (btf=off)");
+	}
 }
 
 static void __init declare_vma(struct vm_struct *vma,
diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c
index 70e682180291..83a3b478202d 100644
--- a/arch/x86/mm/init_64.c
+++ b/arch/x86/mm/init_64.c
@@ -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
 }
 
 /*
diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c
index 4652487b5572..932e85dad394 100644
--- a/arch/x86/mm/pat/set_memory.c
+++ b/arch/x86/mm/pat/set_memory.c
@@ -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;
 }
diff --git a/include/linux/btf.h b/include/linux/btf.h
index 89d5a5c4f117..dd23d9b567e4 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -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)
 
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 9f33e95d5741..bc9b2e57eb23 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -6072,6 +6072,46 @@ extern char __start_BTF[];
 extern char __stop_BTF[];
 extern struct btf *btf_vmlinux;
 
+#ifdef CONFIG_DEBUG_INFO_BTF
+/*
+ * Boot-time opt out of vmlinux and module BTF.
+ *
+ * "btf=off" makes the kernel behave as if CONFIG_DEBUG_INFO_BTF were not
+ * set: vmlinux BTF is never parsed, /sys/kernel/btf is not created (so
+ * the .BTF section can never be read or mapped), and module BTF is not
+ * registered.  Since nothing can then reference the .BTF section, the
+ * architecture frees the pages backing it at free_initmem() time,
+ * recovering its memory.
+ */
+static bool btf_disabled __ro_after_init;
+
+static int __init btf_param(char *str)
+{
+	if (str && !strcmp(str, "off")) {
+		/*
+		 * Only architectures that implement freeing of the .BTF
+		 * section honour btf=off: x86-64 (mark_rodata_ro() in
+		 * arch/x86/mm/init_64.c) and arm64 (mark_rodata_ro() in
+		 * arch/arm64/mm/mmu.c).  On any other architecture
+		 * btf_disabled must never be set - disabling BTF without
+		 * freeing the section would lose the functionality while
+		 * recovering no memory.
+		 */
+		if (IS_ENABLED(CONFIG_X86_64) || IS_ENABLED(CONFIG_ARM64))
+			btf_disabled = true;
+		else
+			pr_warn("btf=off is not supported on this architecture, ignoring\n");
+	}
+	return 0;
+}
+early_param("btf", btf_param);
+
+bool btf_is_disabled(void)
+{
+	return btf_disabled;
+}
+#endif
+
 #define BPF_MAP_TYPE(_id, _ops)
 #define BPF_LINK_TYPE(_id, _name)
 static union {
@@ -8530,7 +8570,12 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
 	struct btf *btf;
 	int err = 0;
 
-	if (mod->btf_data_size == 0 ||
+	/*
+	 * btf=off: module BTF must not be parsed or registered; parsing
+	 * would fail against the missing vmlinux BTF and (without
+	 * CONFIG_MODULE_ALLOW_BTF_MISMATCH) veto the module load.
+	 */
+	if (btf_is_disabled() || mod->btf_data_size == 0 ||
 	    (op != MODULE_STATE_COMING && op != MODULE_STATE_LIVE &&
 	     op != MODULE_STATE_GOING))
 		goto out;
@@ -8711,6 +8756,10 @@ struct btf *btf_get_module_btf(const struct module *module)
 
 static int check_btf_kconfigs(const struct module *module, const char *feature)
 {
+	/* btf=off: behave as if CONFIG_DEBUG_INFO_BTF were not set */
+	if (btf_is_disabled())
+		return 0;
+
 	if (!module && IS_ENABLED(CONFIG_DEBUG_INFO_BTF)) {
 		pr_err("missing vmlinux BTF, cannot register %s\n", feature);
 		return -ENOENT;
diff --git a/kernel/bpf/sysfs_btf.c b/kernel/bpf/sysfs_btf.c
index 9cbe15ce3540..bab6f8db6cdd 100644
--- a/kernel/bpf/sysfs_btf.c
+++ b/kernel/bpf/sysfs_btf.c
@@ -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;
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 72a3f5998dd2..d61479b681f9 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -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) {
-- 
2.47.3


  reply	other threads:[~2026-09-17  0:02 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-17  0:02 [PATCH bpf-next 0/1] bpf: btf: make vmlinux BTF disposable at boot to save ~5 MiB Jay Wang
2026-09-17  0:02 ` Jay Wang [this message]
2026-09-17  1:56   ` [PATCH bpf-next 1/1] bpf: btf: add btf=off boot parameter to disable and free vmlinux BTF Alexei Starovoitov
2026-09-17  4:07     ` Wang, Jay
2026-09-17  7:10       ` 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=20260917000201.25581-2-wanjay@amazon.com \
    --to=wanjay@amazon.com \
    --cc=abuehaze@amazon.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=catalin.marinas@arm.com \
    --cc=daniel@iogearbox.net \
    --cc=dave.hansen@linux.intel.com \
    --cc=doebel@amazon.de \
    --cc=jay.wang.upstream@gmail.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=will@kernel.org \
    --cc=x86@kernel.org \
    --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®