public inbox for archer-commits@sourceware.org
help / color / mirror / Atom feed
From: scox@sourceware.org
To: archer-commits@sourceware.org
Subject: [SCM]  scox/strace: Add gdb remote protocol packet dumper.
Date: Fri, 04 Nov 2016 02:38:00 -0000	[thread overview]
Message-ID: <20161104023837.40402.qmail@sourceware.org> (raw)

The branch, scox/strace has been updated
       via  2020670cfec28db64144a510f0bac8a32b99a8f6 (commit)
       via  add4dc17e0b724b65e0b00a2ab20223191ee5c9f (commit)
      from  cf6f2ca1c40cfcbd5078edf12bf2ed887e8a7bfe (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email.

- Log -----------------------------------------------------------------
commit 2020670cfec28db64144a510f0bac8a32b99a8f6
Author: Stan Cox <scox@redhat.com>
Date:   Thu Nov 3 22:32:49 2016 -0400

    Add gdb remote protocol packet dumper.

commit add4dc17e0b724b65e0b00a2ab20223191ee5c9f
Author: Stan Cox <scox@redhat.com>
Date:   Sun Oct 23 19:54:18 2016 -0400

    Add gdb.server/strace-threads.c

-----------------------------------------------------------------------

Summary of changes:
 gdb/contrib/gdbrp-dump.py                 |  676 +++++++++++++++++++++++++++++
 gdb/gdbserver/linux-low.c                 |    2 +
 gdb/gdbserver/server.c                    |   68 +++-
 gdb/gdbserver/server.h                    |    4 +
 gdb/testsuite/ChangeLog                   |    1 +
 gdb/testsuite/gdb.server/multi-client.exp |   29 +-
 gdb/testsuite/gdb.server/strace-threads.c |   86 ++++
 7 files changed, 847 insertions(+), 19 deletions(-)
 create mode 100755 gdb/contrib/gdbrp-dump.py
 create mode 100644 gdb/testsuite/gdb.server/strace-threads.c

First 500 lines of diff:
diff --git a/gdb/contrib/gdbrp-dump.py b/gdb/contrib/gdbrp-dump.py
new file mode 100755
index 0000000..f2d1d46
--- /dev/null
+++ b/gdb/contrib/gdbrp-dump.py
@@ -0,0 +1,676 @@
+#! /usr/bin/env python
+
+# Copyright (C) 2016 Free Software Foundation, Inc.
+#
+# This file is part of GDB.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Pretty print gdb remote protocol packets created from either gdb or gdbserver
+# logging.  An optional proc map can be given to allow pc interpretation.
+# e.g. gdbserver --debug --remote-debug --multi :65432 |& tee /tmp/server
+#      gdb -iex 'target extended-remote localhost:65432' -iex 'set debug remote'
+#	   -iex 'b main' -iex run -iex 'set logging file /tmp/proc-map' 
+#	   -iex 'set logging on' -iex 'info proc mappings' -iex 'set logging off'
+#	   |& tee /tmp/client
+#      gdbrp-dump.py --proc-map /tmp/proc-map /tmp/server
+#      gdbrp-dump.py --proc-map /tmp/proc-map /tmp/client
+# Note: Register display could be generalized but is currently x8664 specific
+
+import sys
+import binascii
+import chardet
+import re
+from commands import getstatusoutput
+
+executable = ""
+prefix = "      #"
+red_shift = '\033[0;33m'
+want_lineno = True
+hexchars = (['a', 'c', 'b', 'e', 'd', 'f', '1', '0', '3', '2', '5', '4', '7', '6', '9', '8'])
+addr_map = list()
+verbose = False
+multi_client = False
+
+# Convert arg substring starting at s ending at e to big endian
+
+def big_endian(arg, s, e):
+    if e == 0:
+        i = len(arg)
+    else:
+        i = e
+    be_str = ""
+    while i > s:
+        be_str += arg[i-2:i-1]
+        be_str += arg[i-1:i]
+        i -= 2
+    return be_str
+
+def signal(sig):
+    signals = {3:"SIGQUIT", 4:"SIGILL", 5:"SIGTRAP", 17:"SIGCHLD", 18:"SIGCONT", 19:"SIGSTOP"}
+    try:
+        return signals[sig]
+    except KeyError:
+        return sig
+
+def register(reg):
+    registers = {"00":"rax", "01":"rdx", "02":"rcx", "03":"rbx", "04":"rsi", "05":"rdi", "06":"rbp", "07":"rsp", \
+    "08":"r8", "09":"r9", "0a":"r10", "0b":"r11", "0c":"r12", "0d":"r13", "0e":"r14", "0f":"r15", "10":"rip"}
+    try:
+        return registers[reg]
+    except KeyError:
+        return reg
+
+# Display the pc.  First try addr2line, second try proc map, third try objdump
+
+def print_rip(regval, eol):
+    global executable
+    base_addr = 0
+    base_obj = ""
+
+    if len(regval) == 0:
+        return
+    address = int(str('0x' + regval), 16)
+    if len(executable) > 0:
+        # Is this an address in the executable?
+        status, text = getstatusoutput('addr2line -afips -e ' + executable + ' 0x' + regval)
+        if text.find("??") == -1:
+            print 'rip = ' + text,
+        elif len(addr_map) > 0:
+            # Is this an address in the proc map?
+            for addr in addr_map:
+                if int(addr[0], 16) > address:
+                    break
+                base_addr = int(addr[0], 16)
+                base_obj = addr[1]
+
+            offset = address - base_addr
+            status, text = getstatusoutput('addr2line -fips -e ' + base_obj + ' ' + str(hex(offset)))
+            if text.find("??") == -1:
+                print "rip =", regval, text,
+            # addr2line does not know about the address
+            elif len(regval) > 0:
+                status, text = getstatusoutput('objdump --no-show-raw-insn -d ' + executable + \
+                                               ' | awk "/>:/ {s=\$2} /' + regval.lstrip('0') + ':/ {print s,\$0}"')
+                if len(text) > 0:
+                    print "rip =", text,
+                else: print "rip =", regval,
+            else: print "rip =", regval,
+        else: print "rip =", regval,
+    if eol:
+        print
+
+
+def print_register(regno, field, begin, end):
+    regval = big_endian(field, begin, end)
+    if register(regno) == "rip":
+        print_rip(regval, False)
+    else:
+        print register(regno), "=", regval,
+
+
+def dump_registers(arg):
+    print prefix,
+    print_register("00", arg, 0, 16)
+    print_register("03", arg, 16, 32)
+    print_register("02", arg, 32, 48)
+    print_register("01", arg, 48, 64)
+    print "\n", prefix,
+    print_register("04", arg, 64, 80)
+    print_register("05", arg, 80, 96)
+    print_register("06", arg, 96, 112)
+    print_register("07", arg, 112, 128)
+    print "\n", prefix,
+    print_register("10", arg, 256, 272)
+    print
+
+def dump_memory(arg):
+    i = 0
+    hash_ch = arg.rfind("#")
+    arg = arg[0:hash_ch]
+    print prefix,
+    while i < len(arg):
+        print big_endian(arg, i, i + 16),
+        i += 16
+        if i > (16 * 50):
+            print "..."
+            break
+        if ((i / 16) % 5) == 0:
+            print
+            print prefix,
+    print
+
+# Dump the packet that is sent from the client
+
+def dump_client_packet(line):
+    global executable
+
+    # [ packet id, brief description, packet layout ]
+    # The packet layout keywords are expanded below
+    packets = [
+        ["$q", "general query", ["name params"]],
+        ["$vAttach", "attach", ["pid"]],
+        ["$vCont?", 'query resume actions', ["action:thread-id"]],
+        ["$vCont", 'resume', ["action", "thread-id"]],
+        ["$vKill", "kill", ["pid"]],
+        ["$vRun", "run", ["symbol-name", "argument"]],
+        ["$vStopped", "stop notify", [""]],
+        ["$g", "read registers", "registers"],
+        ["$G", "write registers", ["registers"]],
+        ["$Hc", "set thread for continue", ["thread-id"]],
+        ["$Hg", "set thread for general", ["thread-id"]],
+        ["$z", "remove breakpoint", ["type", "addr", "bytes"]],
+        ["$Z", "add breakpoint", ["type", "addr", "bytes"]],
+        ["$m", "read memory", ['addr', 'length']],
+        ["$M", "write memory", ['addr', 'length:XX']],
+        ["$p", "read a register", ["n"]],
+        ["$P", "set a register", ['n', 'value']],
+        ["$Q", "general query", ["name params"]],
+        ["$X", "binary download", ["addr", "length"]],
+        ["$vFile:open", 'open', ["symbol-name", "offset", "length"]],
+        ["$vFile:pread", 'read', ["fd", "count", "offset"]],
+        ["$vFile", 'hostio-close-packet', ["operation:parameter"]],
+        ["$vFile:readlink: filename", "", [""]],
+        ["$qfThreadInfo", "thread info", [""]],
+        ["$qSearch:memory:", "search memory", ["address;length;search-pattern"]],
+        ["$qSupported", "Supported", ["gdbfeatures"]],
+        ["$qThreadExtraInfo", "query thread extra info", ["thread-id"]],
+        ["$qXfer:auxv:read::", "query auxv", ["offset", "length"]],
+        ["$qXfer:btrace:read:", "query btrace", ["annex:offset,length"]],
+        ["qXfer:exec-file:read:", "query execfile", ["symbol-name"]],
+        ["$qXfer:features:read:", "Check features", ["feature", "length"]],
+        ["$qXfer:libraries:read:", "query features", ["annex:offset", "length"]],
+        ["$qXfer:libraries-svr4:read:", "query libraries", ["annex:offset", "length"]],
+        ["$qXfer:memory-map:read::", "query memory map", ["offset", "length"]],
+        ["$qXfer:sdata:read::", "query sdata", ["offset", "length"]],
+        ["$qXfer:siginfo:read::", "query siginfo", ["offset", "length"]],
+        ["$qXfer:spu:read:", "query spu", ["annex:offset", "length"]],
+        ["$qXfer:threads:read::", "query threads", ["offset", "length"]],
+        ["$qXfer:traceframe-info:read::", "query traceframe", ["offset", "length"]],
+        ["$qXfer:uib:read:", "query uib", ["pc:offset", "length"]],
+        ["$qXfer:fdpic:read:", "query fdpic", ["annex:offset", "length"]],
+        ["$qXfer:osdata:read::", "query osdata", ["offset", "length"]],
+        ["$qXfer:object:write:", "query object", ["annex:offset:data"]],
+        ["$qXfer:siginfo:write::", "query siginfo", ["offset:data"]],
+        ["$qXfer:spu:write:", "query spu", ["annex:offset:data"]],
+        ["$qXfer:object:", "query object", ["operation:"]],
+        ["$qAttached:", "query attached", ["pid"]],
+        ["$qRelocInsn", ["query reloc insn"]],
+        ["$qTP:tp:", "query tp", ["addr"]],
+        ["$qTBuffer:", "query TBuffer", ["offset", "len"]],
+        ["$qRelocInsn:", "query relocate instrjr", ["from;to"]],
+        ["$vFlashErase:addr", ["length"]],
+        ["$vFlashDone", "", [""]],
+        ["$QAllow:op:val", "", [""]],
+        ["$qCRC:addr,length", "", [""]],
+        ["$QDisableRandomization:value", "", [""]],
+        ["$qsThreadInfo", "threadinfo", [""]],
+        ["$qGetTLSAddr:", "get thread local storage address", ["thread-id", "offset", "lm"]],
+        ["$qGetTIBAddr:", "get thread info block address", ["thread-id"]],
+        ["$qP mode thread-id", "", [""]],
+        ["$QNonStop:0", "enter nonstop mode", [""]],
+        ["$QPassSignals", "pass signals", ["signal"]],
+        ["$QProgramSignals:", "program signals", ["signal"]],
+        ["$qRcmd", "remote monitor command", ["symbol-name"]],
+        ["$qSearch:memory:address;length;search-pattern", "", [""]],
+        ["$QStartNoAckMode", "packet acknowledgment", [""]],
+        ["$BreakpointCommands", "", [""]],
+        ["$qSymbol::", "symbol lookup", ["symbol-name"]],
+        ["$qThreadExtraInfo,thread-id", "", [""]],
+        ["$qXfer:object:read:annex:offset,length", "", [""]],
+        ["$qAttached", "remote attach state", ["pid"]],
+        ["$QTDP", 'create new tracepoint', [":n:addr:ena:step:pass[:Fflen][:Xlen,bytes]"]],
+        ["$QTDPsrc", "specify tracepoint source", [":n:addr:type:start:slen:bytes"]],
+        ["$QTDV", "tracepoint XXX", [":n:value"]],
+        ["$QTFrame", "tracepoint XXX", [":n"]],
+        ["$qTMinFTPILen", "tracepoint XXX", [""]],
+        ["$QTEnable", "tracepoint XXX", [":n:addr"]],
+        ["$QTDisable", "tracepoint XXX", [":n:addr"]],
+        ["$QTro", "tracepoint XXX", [":start1,end1:start2,end2:"]],
+        ["$QTDisconnected", "tracepoint XXX", [":value"]],
+        ["$qTP", "tracepoint XXX", [":tp:addr"]],
+        ["$qTV", "tracepoint XXX", [":var"]],
+        ["$qTSTMat", "tracepoint XXX", [":address"]],
+        ["$QTSave", "tracepoint XXX", [":filename"]],
+        ["$qTBuffer", "tracepoint XXX", [":offset", "len"]],
+        ["$QTBuffer", "tracepoint XXX", [":size:size"]],
+        ["$QTNotes", "tracepoint XXX", [":type:text;type:text"]],
+        ["$Fcall-id,parameter", "", [""]],
+        ["$Fcall-id,parameter", "", [""]],
+        ["$qGetTIBAddr:", "", ["thread-id"]],
+        ["$qGetTLSAddr:", "", ["thread-id,offset", "lm"]],
+        ["$qTStatus", "trace status", [""]],
+        ["$qTSTMat:", "", ["address"]],
+        ["$qTBuffer", "", [""]],
+        ["$qOffsets", "get section offsets", [""]],
+        ["$QTStart", "", [""]],
+        ["$QTinit", "", [""]],
+        ["$QTStop", "", [""]],
+        ["$qXfer:", "", ["object:read:annex:offset", "length"]],
+        ["$qTSTMat", "", [""]],
+        ["$qTfSTM", "", [""]],
+        ["$qTsSTM", "", [""]],
+        ["$query", "", [""]],
+        ["$quit", "", [" expression"]],
+        ["$Stop", "", [""]],
+        ["$qCRC:", "checksum", ["addr", "length"]],
+        ["$qTsV", "read tracepoint state variables", [""]],
+        ["$qTsP", "read tracepoint state variables", [""]],
+        ["$qTfV", "read tracepoint state variables", [""]],
+        ["$qTfP", "read tracepoint state variables", [""]],
+        ["$qTV:", "", ["var"]],
+        ["$qP", "thread info (do not use)", [" mode thread-id"]],
+        ["$qM", "", [" count done argthread thread"]],
+        ["$qL", "thread info (do not use)", [" startflag threadcount nextthread"]],
+        ["$qC", "query thread id", [""]],
+        ["$bc", "backward continue", [""]],
+        ["$Z0, ", "software breakpoint", ["addr", "bytes"]],
+        ["$Z1, ", "hardware breakpoint", ["addr", "bytes"]],
+        ["$Z2, ", "write watchpoint", ["addr", "bytes"]],
+        ["$Z3, ", "read watchpoint", ["addr", "bytes"]],
+        ["$Z4, ", "access watchpoint", ["addr", "bytes"]],
+        ["$z0, ", "remove software breakpoint", ["addr", "bytes"]],
+        ["$z1, ", "remove hardware breakpoint", ["addr", "bytes"]],
+        ["$z2, ", "remove write watchpoint", ["addr", "bytes"]],
+        ["$z3, ", "remove read watchpoint", ["addr", "bytes"]],
+        ["$z4, ", "remove access watchpoint", ["addr", "bytes"]],
+        ["$E ", "error", ["NN"]],
+        ["$A", "argv", ["arglen:", ":argnum:", ":arg"]],
+        ["$b", "baud", ["baud"]],
+        ["$B", "breakpoint (use z,Z)", ["addr", "mode"]],
+        ["$c", "continue", ["addr"]],
+        ["$C", "continue with signal", ["signal;addr"]],
+        ["$D", "detach", ["pid"]],
+        ["$F", "file protocol", [" RC,EE,CF;XX"]],
+        ["$i", "step 1 clock cycle", ["addr", "nnn"]],
+        ["$R", "restart", ["XX"]],
+        ["$s", "single step", ["addr"]],
+        ["$S", "step with signal", ["sig;addr"]],
+        ["$t", "search backwards", ["addr:PP", "MM"]],
+        ["$T", "is thread alive", ["thread-id"]],
+        ["$!", "enable extended mode", ""],
+        ["$?", "indicate the reason for halting", ""],
+        ["$d", "toggle debug flag", ""],
+        ["$I", "signal then cycle step", ""],
+        ["$k", "kill request", ""],
+        ["$r", "read (do not use)", ""],
+        ["$4", "", ""],
+        ["$5", "", ""],
+    ]
+
+    request = ''
+    for p in packets:
+        lidx = line.find(p[0])
+        if lidx >= 0:
+            print prefix,
+            if len(p[1]) > 0:
+                print p[1], ':',
+
+            # split "ab:cde,fg;h#ijk" to "ab" "cde "fg" "h"
+            hash_ch = line.find("#")
+            if hash_ch >= 0:
+                line = line[0:hash_ch]
+            args = [s1 for s1 in \
+                     line[lidx+len(p[0]):].split(',') if s1 != '']
+            args1 = [s1.split(";") for s1 in args if s1 != '']
+            args = [item for sublist in args1 if len(sublist) > 0 \
+                    for item in sublist if len(item) > 0]
+            args1 = [s1.split(":") for s1 in args if s1 != '']
+            args = [item for sublist in args1 if len(sublist) > 0 \
+                    for item in sublist if len(item) > 0]
+            args1 = [s1.split("=") for s1 in args if s1 != '']
+            args = [item for sublist in args1 if len(sublist) > 0 \
+                    for item in sublist if len(item) > 0]
+
+            request = p[0][1:]
+            argidx = 0
+            for a in p[2]:
+                if argidx == len(args):
+                    break
+                arg = args[argidx]
+                if a == "action":
+                    print a, "=",
+                    if arg[0] == "c":
+                        print "continue",
+                    elif arg[0][0] == "C":
+                        print "continue with signal", int(arg[1:], 16) if len(arg[1:]) > 0 else "",
+                    elif arg[0] == "s":
+                        print "step",
+                    elif arg[0][0] == "S":
+                        print "step with signal", int(arg[1:], 16) if len(arg[1:]) > 0 else "",
+                    elif arg[0][0] == "r":
+                        argidx += 1
+                        print "range step %s-%s" % (arg[1:], args[argidx]),
+                elif a == "thread-id":
+                    pid = arg.split(".")
+                    if len(pid) > 1:
+                        print "pid =",
+                        if pid[0][0:1] == "p":
+                            print pid[0][0:],
+                        elif pid[0] == "-1":
+                            print "*",
+                        else:
+                            print "pid =", int("0x" + pid[0][0:], 16),
+                        ti = 1
+                    else:
+                        ti = 0
+                    print "tid =",
+                    if pid[ti] == "-1":
+                        print "*",
+                    else:
+                        print pid[ti],
+                elif a == "symbol-name":
+                    if arg.find("/") > 0:
+                        arg = arg[0:arg.find("/")].rstrip()
+                    else:
+                        arg = arg.rstrip()
+                    if len(arg) > 1:
+                        idx = len(arg)
+                        if idx % 2 > 0:
+                            idx -= 3
+                        if request == "vRun":
+                            executable = binascii.unhexlify(args[argidx][:idx])
+                        print binascii.unhexlify(args[argidx][:idx]),
+                elif a == "registers":
+                    dump_registers(arg)
+                else:
+                    if len(args[argidx]) > 32:
+                        arg = args[argidx][0:32] + "..."
+                    else:
+                        if arg.find("...") > 0:
+                            arg = arg[0:arg.find("...")]
+                    if arg.find("/") > 0:
+                        arg = arg[0:arg.find("/")]
+                    print a, "=", arg,
+                argidx = argidx + 1
+            print
+            break
+    return request
+
+
+# Dump the gdbserver return packet
+
+def dump_server_packet(line, last_request):
+    global executable
+    line = decode_packet(line)
+    if line.find("qSymbol") == -1:
+        l = ''
+        try:
+            l = line.decode('ascii')
+        except UnicodeDecodeError:
+            True
+        if len(executable) == 0 and last_request == "Xfer:exec-file:read:":
+            executable = l[1:]
+        if len(l) > 50:
+            print prefix + " server: " + l[0:50] + "..."
+        else:
+            print prefix + " server: " + l
+    dsp_prefix = "        #"
+    args = [s1 for s1 in \
+            line.split(';') if s1 != '']
+    if last_request == "g":
+        if any((args[0][0] == c) for c in hexchars):
+            start = 0
+        else:
+            start = 1
+        dump_registers(decode_packet(args[0][start:]))
+    elif last_request == "m":
+        dump_memory(decode_packet(args[0][1:]))
+    if len(args) == 0:
+        return
+    if args[0][0] == 'T' or args[0][0] == 'S':
+        if args[0][0] == 'S':
+            args[0] = args[0][5:]
+        print dsp_prefix, "packet reply: signal=", signal(int(args[0][1:3], 16)),
+        subargs = args[0].split(':')
+        print
+        print dsp_prefix,
+        try:
+            if args[0][3:5] == "sy":
+                status, text = getstatusoutput('grep " ' + str(int(subargs[1], 16)) + '$" /usr/include/asm/unistd_64.h')
+                print text[8:],
+            elif args[0][3:5] == "wa":
+                print "watch =", subargs[1],
+            else:
+                print_register(args[0][3:5], subargs[1], 0, 0)
+            print
+        except IndexError:
+            pass
+
+        first = True
+        for a in args:
+            if a[0] == "#":
+                break
+            if first:
+                first = False
+                continue
+            subargs = a.split(':')
+            if subargs[0] == "thread":
+                print dsp_prefix, "Thread:", subargs[1],
+            else:
+                print dsp_prefix,
+                print_register(a[0:2], subargs[1], 0, 0)
+                print
+    elif args[0][0] == 'W':
+        print dsp_prefix, "Program exited: code=" + args[0][1:3]
+        print
+    elif args[0].find('qSymbol:') == 0:
+        idx = args[0].find("*")
+        if idx > 0:
+            idx -= 1
+            suffix = "*"
+        else:
+            idx = args[0].find("#")
+            if idx == -1:
+                idx = len(args[0])
+            suffix = ""
+        x = 0
+        print args[0][8:idx]
+        print dsp_prefix, " server:", binascii.unhexlify(args[0][8:idx]) + suffix
+
+# Depending on verbosity, we may or may not wish to print a line
+
+def print_this_line(aline):
+    global verbose
+    global multi_client
+
+    if aline.find("readahead cache") >= 0:
+        return False
+    if aline.find("getpkt") >= 0 or aline.find("putpkt") >= 0 or aline.find("Sending packet") >= 0:
+        return True
+    if multi_client:
+        if re.match("^ *[*]*[0-9][0-9]* LWP", aline, flags=0):
+            return True


hooks/post-receive
--
Repository for Project Archer.


                 reply	other threads:[~2016-11-04  2:38 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=20161104023837.40402.qmail@sourceware.org \
    --to=scox@sourceware.org \
    --cc=archer-commits@sourceware.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 read-only IMAP folder(s) and NNTP newsgroup(s).