All of lore.kernel.org
 help / color / mirror / Atom feed
From: Richard Henderson <richard.henderson@linaro.org>
To: qemu-devel@nongnu.org
Subject: [PATCH v3 59/66] accel/tcg: Handle SIGBUS in handle_cpu_signal
Date: Wed, 18 Aug 2021 09:19:13 -1000	[thread overview]
Message-ID: <20210818191920.390759-60-richard.henderson@linaro.org> (raw)
In-Reply-To: <20210818191920.390759-1-richard.henderson@linaro.org>

We've been registering host SIGBUS, but then treating it
exactly like SIGSEGV.

Handle BUS_ADRALN via cpu_unaligned_access, but allow other
SIGBUS si_codes to continue into the host-to-guest signal
coversion code in host_signal_handler.  Unwind the guest
state so that we report the correct guest PC for the fault.

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
---
 accel/tcg/user-exec.c  | 188 +++++++++++++++++++++++++++--------------
 linux-user/signal.c    |  36 +++++---
 accel/tcg/trace-events |   3 +-
 3 files changed, 152 insertions(+), 75 deletions(-)

diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c
index 1c9486c76d..c7ee5accb3 100644
--- a/accel/tcg/user-exec.c
+++ b/accel/tcg/user-exec.c
@@ -57,17 +57,123 @@ static void QEMU_NORETURN cpu_exit_tb_from_sighandler(CPUState *cpu,
     cpu_loop_exit_noexc(cpu);
 }
 
-/* 'pc' is the host PC at which the exception was raised. 'address' is
-   the effective address of the memory exception. 'is_write' is 1 if a
-   write caused the exception and otherwise 0'. 'old_set' is the
-   signal set which should be restored */
-static inline int handle_cpu_signal(uintptr_t pc, siginfo_t *info,
-                                    int is_write, sigset_t *old_set)
+static bool handle_cpu_sigsegv(CPUState *cpu, uintptr_t pc, uintptr_t addr,
+                               int si_code, MMUAccessType access_type,
+                               sigset_t *old_set)
+{
+    trace_sigsegv(pc, addr, access_type, *(unsigned long *)old_set);
+
+    /* XXX: locking issue */
+    /*
+     * Note that it is important that we don't call page_unprotect() unless
+     * this is really a "write to nonwriteable page" fault, because
+     * page_unprotect() assumes that if it is called for an access to
+     * a page that's writeable this means we had two threads racing and
+     * another thread got there first and already made the page writeable;
+     * so we will retry the access. If we were to call page_unprotect()
+     * for some other kind of fault that should really be passed to the
+     * guest, we'd end up in an infinite loop of retrying the faulting
+     * access.
+     */
+    if (access_type == MMU_DATA_STORE
+        && si_code == SEGV_ACCERR
+        && h2g_valid(addr)) {
+        switch (page_unprotect(h2g_nocheck(addr), pc)) {
+        case 0:
+            /*
+             * Fault not caused by a page marked unwritable to protect
+             * cached translations, must be the guest binary's problem.
+             */
+            break;
+        case 1:
+            /*
+             * Fault caused by protection of cached translation; TBs
+             * invalidated, so resume execution.  Retain helper_retaddr
+             * for a possible second fault.
+             */
+            return true;
+        case 2:
+            /*
+             * Fault caused by protection of cached translation, and the
+             * currently executing TB was modified and must be exited
+             * immediately.  Clear helper_retaddr for next execution.
+             */
+            clear_helper_retaddr();
+            cpu_exit_tb_from_sighandler(cpu, old_set);
+            /* NORETURN */
+        default:
+            g_assert_not_reached();
+        }
+    }
+
+    /*
+     * Convert forcefully to guest address space, invalid addresses
+     * are still valid segv ones.
+     */
+    addr = h2g_nocheck(addr);
+
+    /*
+     * There is no way the target can handle this other than raising
+     * an exception.  Undo signal and retaddr state prior to longjmp.
+     */
+    sigprocmask(SIG_SETMASK, old_set, NULL);
+    clear_helper_retaddr();
+
+    CPUClass *cc = CPU_GET_CLASS(cpu);
+    cc->tcg_ops->tlb_fill(cpu, addr, 0, access_type, MMU_USER_IDX, false, pc);
+    g_assert_not_reached();
+}
+
+static bool handle_cpu_sigbus(CPUState *cpu, uintptr_t pc, uintptr_t addr,
+                              int si_code, MMUAccessType access_type,
+                              sigset_t *old_set)
+{
+    trace_sigbus(pc, addr, access_type, *(unsigned long *)old_set);
+
+    if (si_code == BUS_ADRALN) {
+        /*
+         * We're expecting host alignment faults to correspond
+         * directly to guest alignment faults, and thus the host
+         * address must correspond to a guest address.
+         */
+        addr = h2g(addr);
+
+        /* Undo signal and retaddr state prior to longjmp. */
+        sigprocmask(SIG_SETMASK, old_set, NULL);
+        clear_helper_retaddr();
+
+        cpu_unaligned_access(cpu, addr, access_type, MMU_USER_IDX, pc);
+    }
+
+    /*
+     * Otherwise this is probably BUS_ADRERR or suchlike, which
+     * can be easily triggered by the guest playing with mmap
+     * and accessing past the end of the file.
+     *
+     * Use PC to unwind to the current guest address and then
+     * return false to pass on the host signal to the guest.
+     */
+    cpu_restore_state(cpu, pc, true);
+    return false;
+}
+
+/**
+ * handle_cpu_signal:
+ * @pc: the host PC at which the exception was raised,
+ * @address: the effective address of the memory exception,
+ * @is_write: true if a write caused the exception,
+ * @old_set: signal set which should be restored.
+ *
+ * Return true if the signal has been handled by the vcpu,
+ * false if the signal should be sent on to the guest.
+ */
+static bool handle_cpu_signal(uintptr_t pc, siginfo_t *info,
+                              bool is_write, sigset_t *old_set)
 {
     CPUState *cpu = current_cpu;
-    CPUClass *cc;
-    unsigned long address = (unsigned long)info->si_addr;
+    uintptr_t addr = (uintptr_t)info->si_addr;
     MMUAccessType access_type = is_write ? MMU_DATA_STORE : MMU_DATA_LOAD;
+    int code;
 
     switch (helper_retaddr) {
     default:
@@ -119,7 +225,8 @@ static inline int handle_cpu_signal(uintptr_t pc, siginfo_t *info,
         break;
     }
 
-    /* For synchronous signals we expect to be coming from the vCPU
+    /*
+     * For synchronous signals we expect to be coming from the vCPU
      * thread (so current_cpu should be valid) and either from running
      * code or during translation which can fault as we cross pages.
      *
@@ -132,62 +239,15 @@ static inline int handle_cpu_signal(uintptr_t pc, siginfo_t *info,
         abort();
     }
 
-    trace_sigsegv(pc, address, is_write, *(unsigned long *)old_set);
-
-    /* XXX: locking issue */
-    /* Note that it is important that we don't call page_unprotect() unless
-     * this is really a "write to nonwriteable page" fault, because
-     * page_unprotect() assumes that if it is called for an access to
-     * a page that's writeable this means we had two threads racing and
-     * another thread got there first and already made the page writeable;
-     * so we will retry the access. If we were to call page_unprotect()
-     * for some other kind of fault that should really be passed to the
-     * guest, we'd end up in an infinite loop of retrying the faulting
-     * access.
-     */
-    if (is_write && info->si_signo == SIGSEGV && info->si_code == SEGV_ACCERR &&
-        h2g_valid(address)) {
-        switch (page_unprotect(h2g(address), pc)) {
-        case 0:
-            /* Fault not caused by a page marked unwritable to protect
-             * cached translations, must be the guest binary's problem.
-             */
-            break;
-        case 1:
-            /* Fault caused by protection of cached translation; TBs
-             * invalidated, so resume execution.  Retain helper_retaddr
-             * for a possible second fault.
-             */
-            return 1;
-        case 2:
-            /* Fault caused by protection of cached translation, and the
-             * currently executing TB was modified and must be exited
-             * immediately.  Clear helper_retaddr for next execution.
-             */
-            clear_helper_retaddr();
-            cpu_exit_tb_from_sighandler(cpu, old_set);
-            /* NORETURN */
-
-        default:
-            g_assert_not_reached();
-        }
+    code = info->si_code;
+    switch (info->si_signo) {
+    case SIGSEGV:
+        return handle_cpu_sigsegv(cpu, pc, addr, code, access_type, old_set);
+    case SIGBUS:
+        return handle_cpu_sigbus(cpu, pc, addr, code, access_type, old_set);
+    default:
+        g_assert_not_reached();
     }
-
-    /* Convert forcefully to guest address space, invalid addresses
-       are still valid segv ones */
-    address = h2g_nocheck(address);
-
-    /*
-     * There is no way the target can handle this other than raising
-     * an exception.  Undo signal and retaddr state prior to longjmp.
-     */
-    sigprocmask(SIG_SETMASK, old_set, NULL);
-    clear_helper_retaddr();
-
-    cc = CPU_GET_CLASS(cpu);
-    cc->tcg_ops->tlb_fill(cpu, address, 0, access_type,
-                          MMU_USER_IDX, false, pc);
-    g_assert_not_reached();
 }
 
 static int probe_access_internal(CPUArchState *env, target_ulong addr,
diff --git a/linux-user/signal.c b/linux-user/signal.c
index a8faea6f09..99b456a781 100644
--- a/linux-user/signal.c
+++ b/linux-user/signal.c
@@ -747,27 +747,37 @@ static inline void rewind_if_in_safe_syscall(void *puc)
 static void host_signal_handler(int host_signum, siginfo_t *info,
                                 void *puc)
 {
-    CPUArchState *env = thread_cpu->env_ptr;
-    CPUState *cpu = env_cpu(env);
+    CPUState *cpu = thread_cpu;
+    CPUArchState *env = cpu->env_ptr;
     TaskState *ts = cpu->opaque;
-
     int sig;
     target_siginfo_t tinfo;
     ucontext_t *uc = puc;
     struct emulated_sigtable *k;
+    bool must_exit = false;
 
-    /* the CPU emulator uses some host signals to detect exceptions,
-       we forward to it some signals */
+    /*
+     * The CPU emulator uses some host signals to detect exceptions,
+     * we forward to it some signals.
+     */
     if ((host_signum == SIGSEGV || host_signum == SIGBUS)
         && info->si_code > 0) {
-        if (cpu_signal_handler(host_signum, info, puc))
+        if (cpu_signal_handler(host_signum, info, puc)) {
             return;
+        }
+        /*
+         * E.g. SIGBUS, without BUS_ADRALN, which we want to pass on.
+         * We have unwound the TB to PC, so must use cpu_loop_exit below.
+         */
+        must_exit = true;
     }
 
     /* get target signal number */
     sig = host_to_target_signal(host_signum);
-    if (sig < 1 || sig > TARGET_NSIG)
+    if (sig < 1 || sig > TARGET_NSIG) {
+        assert(!must_exit);
         return;
+    }
     trace_user_host_signal(env, host_signum, sig);
 
     rewind_if_in_safe_syscall(puc);
@@ -778,7 +788,8 @@ static void host_signal_handler(int host_signum, siginfo_t *info,
     k->pending = sig;
     ts->signal_pending = 1;
 
-    /* Block host signals until target signal handler entered. We
+    /*
+     * Block host signals until target signal handler entered. We
      * can't block SIGSEGV or SIGBUS while we're executing guest
      * code in case the guest code provokes one in the window between
      * now and it getting out to the main loop. Signals will be
@@ -796,8 +807,13 @@ static void host_signal_handler(int host_signum, siginfo_t *info,
     sigdelset(&uc->uc_sigmask, SIGSEGV);
     sigdelset(&uc->uc_sigmask, SIGBUS);
 
-    /* interrupt the virtual CPU as soon as possible */
-    cpu_exit(thread_cpu);
+    /* Interrupt the virtual CPU as soon as possible. */
+    if (must_exit) {
+        cpu->exception_index = EXCP_INTERRUPT;
+        cpu_loop_exit(cpu);
+    } else {
+        cpu_exit(cpu);
+    }
 }
 
 /* do_sigaltstack() returns target values and errnos. */
diff --git a/accel/tcg/trace-events b/accel/tcg/trace-events
index d54416f2ee..009fbdc124 100644
--- a/accel/tcg/trace-events
+++ b/accel/tcg/trace-events
@@ -10,4 +10,5 @@ exec_tb_exit(void *last_tb, unsigned int flags) "tb:%p flags=0x%x"
 translate_block(void *tb, uintptr_t pc, const void *tb_code) "tb:%p, pc:0x%"PRIxPTR", tb_code:%p"
 
 # user-exec.c
-sigsegv(uintptr_t pc, uintptr_t addr, int is_write, unsigned long old_set) "pc:0x%"PRIxPTR", addr:0x%"PRIxPTR", w:%d, oldset:0x%lx"
+sigsegv(uintptr_t pc, uintptr_t addr, int access_type, unsigned long old_set) "pc:0x%"PRIxPTR", addr:0x%"PRIxPTR", at:%d, oldset:0x%lx"
+sigbus(uintptr_t pc, uintptr_t addr, int access_type, unsigned long old_set) "pc:0x%"PRIxPTR", addr:0x%"PRIxPTR", at:%d, oldset:0x%lx"
-- 
2.25.1



  parent reply	other threads:[~2021-08-18 20:13 UTC|newest]

Thread overview: 108+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-08-18 19:18 [PATCH v3 00/66] Unaligned access for user-only Richard Henderson
2021-08-18 19:18 ` [PATCH v3 01/66] util: Suppress -Wstringop-overflow in qemu_thread_start Richard Henderson
2021-08-19 15:13   ` Peter Maydell
2021-08-18 19:18 ` [PATCH v3 02/66] hw/core: Make do_unaligned_access noreturn Richard Henderson
2021-08-19  6:15   ` Alistair Francis
2021-08-18 19:18 ` [PATCH v3 03/66] hw/core: Make do_unaligned_access available to user-only Richard Henderson
2021-08-18 19:18 ` [PATCH v3 04/66] target/alpha: Implement do_unaligned_access for user-only Richard Henderson
2021-08-18 19:18 ` [PATCH v3 05/66] target/arm: " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 06/66] target/hppa: " Richard Henderson
2021-08-19 15:32   ` Peter Maydell
2021-08-18 19:18 ` [PATCH v3 07/66] target/microblaze: Do not set MO_ALIGN " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 08/66] target/mips: Implement do_unaligned_access " Richard Henderson
2021-08-19 15:34   ` Peter Maydell
2021-08-18 19:18 ` [PATCH v3 09/66] target/ppc: Move SPR_DSISR setting to powerpc_excp Richard Henderson
2021-08-19 15:39   ` Peter Maydell
2021-08-19 19:13     ` Richard Henderson
2021-08-18 19:18 ` [PATCH v3 10/66] target/ppc: Set fault address in ppc_cpu_do_unaligned_access Richard Henderson
2021-08-19 15:41   ` Peter Maydell
2021-08-18 19:18 ` [PATCH v3 11/66] target/ppc: Implement do_unaligned_access for user-only Richard Henderson
2021-08-19 15:44   ` Peter Maydell
2021-08-18 19:18 ` [PATCH v3 12/66] target/riscv: " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 13/66] target/s390x: " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 14/66] target/sh4: Set fault address in superh_cpu_do_unaligned_access Richard Henderson
2021-08-18 19:18 ` [PATCH v3 15/66] target/sh4: Implement do_unaligned_access for user-only Richard Henderson
2021-08-19 15:46   ` Peter Maydell
2021-08-19 19:21     ` Richard Henderson
2021-08-18 19:18 ` [PATCH v3 16/66] target/sparc: Remove DEBUG_UNALIGNED Richard Henderson
2021-08-18 19:18 ` [PATCH v3 17/66] target/sparc: Split out build_sfsr Richard Henderson
2021-08-18 19:18 ` [PATCH v3 18/66] target/sparc: Set fault address in sparc_cpu_do_unaligned_access Richard Henderson
2021-08-18 19:18 ` [PATCH v3 19/66] target/sparc: Implement do_unaligned_access for user-only Richard Henderson
2021-08-18 19:18 ` [PATCH v3 20/66] target/xtensa: " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 21/66] accel/tcg: Report unaligned atomics " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 22/66] accel/tcg: Drop signness in tracing in cputlb.c Richard Henderson
2021-08-18 21:14   ` Philippe Mathieu-Daudé
2021-08-18 19:18 ` [PATCH v3 23/66] tcg: Expand MO_SIZE to 3 bits Richard Henderson
2021-08-19  6:17   ` Alistair Francis
2021-08-18 19:18 ` [PATCH v3 24/66] tcg: Rename TCGMemOpIdx to MemOpIdx Richard Henderson
2021-08-19  6:17   ` Alistair Francis
2021-08-18 19:18 ` [PATCH v3 25/66] tcg: Split out MemOpIdx to exec/memopidx.h Richard Henderson
2021-08-18 19:18 ` [PATCH v3 26/66] trace/mem: Pass MemOpIdx to trace_mem_get_info Richard Henderson
2021-08-19 15:49   ` Peter Maydell
2021-08-18 19:18 ` [PATCH v3 27/66] accel/tcg: Pass MemOpIdx to atomic_trace_*_post Richard Henderson
2021-08-18 19:18 ` [PATCH v3 28/66] plugins: Reorg arguments to qemu_plugin_vcpu_mem_cb Richard Henderson
2021-08-30 21:42   ` Philippe Mathieu-Daudé
2021-08-18 19:18 ` [PATCH v3 29/66] trace: Split guest_mem_before Richard Henderson
2021-08-18 19:18 ` [PATCH v3 30/66] target/arm: Use MO_128 for 16 byte atomics Richard Henderson
2021-08-18 19:18 ` [PATCH v3 31/66] target/i386: " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 32/66] target/ppc: " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 33/66] target/s390x: " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 34/66] target/hexagon: Implement cpu_mmu_index Richard Henderson
2021-08-18 19:18 ` [PATCH v3 35/66] accel/tcg: Add cpu_{ld,st}*_mmu interfaces Richard Henderson
2021-08-19 15:57   ` Peter Maydell
2021-08-18 19:18 ` [PATCH v3 36/66] accel/tcg: Move cpu_atomic decls to exec/cpu_ldst.h Richard Henderson
2021-08-18 19:18 ` [PATCH v3 37/66] target/mips: Use cpu_*_data_ra for msa load/store Richard Henderson
2021-08-18 19:18 ` [PATCH v3 38/66] target/mips: Use 8-byte memory ops " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 39/66] target/s390x: Use cpu_*_mmu instead of helper_*_mmu Richard Henderson
2021-08-18 19:18 ` [PATCH v3 40/66] target/sparc: " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 41/66] target/arm: " Richard Henderson
2021-08-18 19:18 ` [PATCH v3 42/66] tcg: Move helper_*_mmu decls to tcg/tcg-ldst.h Richard Henderson
2021-08-18 19:18 ` [PATCH v3 43/66] tcg: Add helper_unaligned_{ld, st} for user-only sigbus Richard Henderson
2021-08-19 15:58   ` Peter Maydell
2021-08-18 19:18 ` [PATCH v3 44/66] tcg/i386: Support raising sigbus for user-only Richard Henderson
2021-08-19 16:02   ` Peter Maydell
2021-08-18 19:18 ` [PATCH v3 45/66] tests/tcg/multiarch: Add sigbus.c Richard Henderson
2021-08-19 16:04   ` Peter Maydell
2021-08-18 19:19 ` [PATCH v3 46/66] linux-user: Split out do_prctl and subroutines Richard Henderson
2021-08-19 16:06   ` Peter Maydell
2021-08-19 19:30     ` Richard Henderson
2021-08-18 19:19 ` [PATCH v3 47/66] linux-user: Disable more prctl subcodes Richard Henderson
2021-08-18 19:19 ` [PATCH v3 48/66] hw/core/cpu: Re-sort the non-pointers to the end of CPUClass Richard Henderson
2021-08-18 21:17   ` Philippe Mathieu-Daudé
2021-08-18 19:19 ` [PATCH v3 49/66] linux-user: Add code for PR_GET/SET_UNALIGN Richard Henderson
2021-08-18 19:19 ` [PATCH v3 50/66] hw/core/cpu: Move cpu properties to cpu-sysemu.c Richard Henderson
2021-08-19 15:26   ` Peter Maydell
2021-08-19 16:52     ` Eduardo Habkost
2021-08-18 19:19 ` [PATCH v3 51/66] hw/core/cpu: Add prctl-unalign-sigbus property for user-only Richard Henderson
2021-08-18 19:19 ` [PATCH v3 52/66] target/alpha: Reorg fp memory operations Richard Henderson
2021-08-18 21:21   ` Philippe Mathieu-Daudé
2021-08-18 19:19 ` [PATCH v3 53/66] target/alpha: Reorg integer " Richard Henderson
2021-08-20  9:29   ` Peter Maydell
2021-08-18 19:19 ` [PATCH v3 54/66] target/alpha: Implement prctl_unalign_sigbus Richard Henderson
2021-08-18 19:19 ` [PATCH v3 55/66] target/hppa: " Richard Henderson
2021-08-18 19:19 ` [PATCH v3 56/66] target/sh4: " Richard Henderson
2021-08-18 19:19 ` [PATCH v3 57/66] accel/tcg/user-exec: Convert DEBUG_SIGNAL to tracepoint Richard Henderson
2021-08-18 21:22   ` Philippe Mathieu-Daudé
2021-08-18 19:19 ` [PATCH v3 58/66] include/exec: Move cpu_signal_handler declaration Richard Henderson
2021-08-18 21:23   ` Philippe Mathieu-Daudé
2021-08-19  6:18   ` Alistair Francis
2021-08-18 19:19 ` Richard Henderson [this message]
2021-08-20  9:34   ` [PATCH v3 59/66] accel/tcg: Handle SIGBUS in handle_cpu_signal Peter Maydell
2021-08-22  7:48     ` Richard Henderson
2021-08-18 19:19 ` [PATCH v3 60/66] tcg/aarch64: Support raising sigbus for user-only Richard Henderson
2021-08-20  9:46   ` Peter Maydell
2021-08-18 19:19 ` [PATCH v3 61/66] tcg/ppc: " Richard Henderson
2021-08-20 10:11   ` Peter Maydell
2021-08-18 19:19 ` [PATCH v3 62/66] tcg/s390: " Richard Henderson
2021-08-20 10:12   ` Peter Maydell
2021-08-18 19:19 ` [PATCH v3 63/66] tcg/tci: " Richard Henderson
2021-08-20 10:14   ` Peter Maydell
2021-08-22  7:59     ` Richard Henderson
2021-08-22 12:32       ` Peter Maydell
2021-08-22 17:09         ` Richard Henderson
2021-08-18 19:19 ` [PATCH v3 64/66] tcg: Canonicalize alignment flags in MemOp Richard Henderson
2021-08-18 21:24   ` Philippe Mathieu-Daudé
2021-08-18 19:19 ` [PATCH v3 65/66] tcg/riscv: Support raising sigbus for user-only Richard Henderson
2021-08-18 19:19 ` [PATCH v3 66/66] tcg/riscv: Remove add with zero on user-only memory access Richard Henderson
2021-08-30 21:29   ` Philippe Mathieu-Daudé
2021-08-30 22:38   ` Alistair Francis

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=20210818191920.390759-60-richard.henderson@linaro.org \
    --to=richard.henderson@linaro.org \
    --cc=qemu-devel@nongnu.org \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.