qemu-devel.nongnu.org archive mirror
 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).