From: Luis Machado <luis.machado@linaro.org>
To: Andrew Burgess <andrew.burgess@embecosm.com>,
gdb-patches@sourceware.org, binutils@sourceware.org
Cc: Fredrik Hederstierna <fredrik@hederstierna.com>
Subject: Re: [PATCHv2 5/9] gdb/riscv: introduce bare metal core dump support
Date: Mon, 1 Feb 2021 11:05:51 -0300 [thread overview]
Message-ID: <d58b8601-bfd4-98bb-a9f5-fedc1bc8b905@linaro.org> (raw)
In-Reply-To: <49e988fa56f44853a06a3de0de7ae0e512db265a.1611172468.git.andrew.burgess@embecosm.com>
Andrew,
On 1/20/21 5:23 PM, Andrew Burgess wrote:
> The commit message below includes a description of the core file
> format. Though this description could exist in the commit message, I
> don't imagine this is really the right place to document something
> like this.
Thanks for writing this down.
>
> For RISC-V I imagine the correct place would be here:
>
> https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md
>
> And I have a commit for this repository that replicates the
> description below. However, I wanted to post this to the binutils/gdb
> list first, and see if this description was good enough for all
> stakeholders.
>
> If people here are happy with this commit and the documentation, then
> I would image creating a pull request against the RISC-V ABI document,
> and then if there's no negative feedback, merge this patch at that
> point.
>
> All feedback, on docs or implementation, is welcome,
>
> Thanks,
> Andrew
>
>
>
>
>
> ----
>
> This commit adds the ability for bare metal RISC-V target to generate
> core files from within GDB.
>
> The intended use case is that a user will connect to a remote bare
> metal target, debug up to some error condition, then generate a core
> file in the normal way using:
>
> (gdb) generate-core-file
>
> This core file can then be used to revisit the state of the remote
> target without having to reconnect to the remote target.
>
> The core file creation code is split between two new files. In
> none-tdep.c is code designed to support the architecture agnostic
> parts of a bare metal core dump.
>
> In riscv-none-tdep.c are the RISC-V specific parts, this is where the
> regset and regcache_map_entry structures are defined that control how
> registers are laid out in the core file.
>
> Currently for RISC-V only the x-regs and f-regs (if present) are
> written out. In future commits I plan to add support for writing out
> the RISC-V CSRs.
>
> The cores dump format is based around generating an ELF containing
cores -> core
> sections for the writable regions of memory that a user could be
> using. Which regions are dumped rely on GDB's existing common core
> dumping code, GDB will attempt to figure out the stack and heap as
> well as copying out writable data sections as identified by the
> original ELF.
>
> Register information is added to the core dump using notes, just as it
> is for Linux of FreeBSD core dumps. The note types used consist of
> the 3 basic types you would expect in a OS based core dump,
> NT_PRPSINFO, NT_PRSTATUS, NT_FPREGSET.
>
> The layout of these notes differs slightly (due to field sizes)
> between RV32 and RV64. Below I describe the data layout for each
> note. In all case, all padding fields should be set to zero.
case -> cases
>
> Note NT_PRPSINFO is optional. Its data layout is:
>
> struct prpsinfo32_t /* For RV32. */
> {
> uint8_t padding[32];
> char fname[16];
> char psargs[80];
> }
>
> struct prpsinfo64_t /* For RV64. */
> {
> uint8_t padding[40];
> char fname[16];
> char psargs[80];
> }
>
> Field 'fname' - null terminated string consisting of the basename of
> (up to the fist 15 characters of) the executable. Any additional
> space should be set to zero. If there's no executable name then
> this field can be set to all zero.
>
> Field 'psargs' - a null terminated string up to 80 characters in
> length. Any additional space should be filled with zero. This
> field contains the full executable path and any arguments passed
> to the executable. If there's nothing sensible to write in this
> field then fill it with zero.
>
> Note NT_PRSTATUS is required, its data layout is:
>
> struct prstatus32_t /* For RV32. */
> {
> uint8_t padding_1[12];
> uint16_t sig;
> uint8_t padding_2[10];
> uint32_t thread_id;
> uint8_t padding_3[44];
> uint32_t x_regs[32];
> uint8_t padding_4[4];
> }
>
> struct prstatus64_t /* For RV64. */
> {
> uint8_t padding_1[12];
> uint16_t sig;
> uint8_t padding_2[18];
> uint32_t thread_id;
> uint8_t padding_3[76];
> uint64_t x_regs[32];
> uint8_t padding_4[4];
> }
>
> Field 'sig' - the signal that stopped this thread. Its implementation
Its -> It's
> defined what this field actually means. Within GDB this will be
> the signal number that the remote target reports as the stop
> reason for this thread.
>
> Field 'thread_is' - the thread id for this thread, its implementation
its -> it's
> defined what this field actually means. Within GDB this will be
> thread thread-id that is assigned to each remote thread.
>
> Field 'x_regs' - at index 0 we store the program counter, and at
> indices 1 to 31 we store x-registers 1 to 31. x-register 0 is not
> stored, its value is always zero anyway.
>
> Note NT_FPREGSET is optional, its data layout is:
>
> fpregset32_t /* For targets with 'F' extension. */
> {
> uint32_t f_regs[32];
> uint32_t fcsr;
> }
>
> fpregset64_t /* For targets with 'D' extension . */
> {
> uint64_t f_regs[32];
> uint32_t fcsr;
> }
>
> Field 'f_regs' - stores f-registers 0 to 31.
>
> Field 'fcsr' - stores the fcsr CSR register, and is always 4-bytes.
>
> The rules for ordering the notes is the same as for Linux. The
> NT_PRSTATUS note must come before any other notes about additional
> register sets. And for multi-threaded targets all registers for a
> single thread should be grouped together. This is because only
> NT_PRSTATUS includes a thread-id, all additional register notes after
> a NT_PRSTATUS are assumed to belong to the same thread until a
> different NT_PRSTATUS is seen.
I think the above documentation is a good start, and we can expand it
with more information.
I'm wondering if we should document this in the GDB manual. Then again,
it may be a bit too technical for the user manual.
Another idea is to document arch-specific bits in arch-specific files
and the generic parts in the none-tdep.c file?
>
> gdb/ChangeLog:
>
> * Makefile.in (ALL_TARGET_OBS): Add riscv-none-tdep.o.
> (ALLDEPFILES): Add riscv-none-tdep.c.
> * configure.tgt (riscv*-*-*): Include riscv-none-tdep.c.
> * none-tdep.c: New file.
> * none-tdep.h: New file.
> * riscv-none-tdep.c: New file.
> ---
> gdb/ChangeLog | 10 ++++
> gdb/Makefile.in | 4 ++
> gdb/configure.tgt | 2 +-
> gdb/none-tdep.c | 119 ++++++++++++++++++++++++++++++++++++++++++
> gdb/none-tdep.h | 30 +++++++++++
> gdb/riscv-none-tdep.c | 108 ++++++++++++++++++++++++++++++++++++++
> 6 files changed, 272 insertions(+), 1 deletion(-)
> create mode 100644 gdb/none-tdep.c
> create mode 100644 gdb/none-tdep.h
> create mode 100644 gdb/riscv-none-tdep.c
>
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index 9267bea7beb..5f88b6a78cf 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -807,6 +807,7 @@ ALL_TARGET_OBS = \
> ravenscar-thread.o \
> riscv-fbsd-tdep.o \
> riscv-linux-tdep.o \
> + riscv-none-tdep.o \
> riscv-ravenscar-thread.o \
> riscv-tdep.o \
> rl78-tdep.o \
> @@ -1100,6 +1101,7 @@ COMMON_SFILES = \
> minsyms.c \
> mipsread.c \
> namespace.c \
> + none-tdep.c \
> objc-lang.c \
> objfiles.c \
> observable.c \
> @@ -1360,6 +1362,7 @@ HFILES_NO_SRCDIR = \
> netbsd-tdep.h \
> nds32-tdep.h \
> nios2-tdep.h \
> + none-tdep.h \
> nto-tdep.h \
> objc-lang.h \
> objfiles.h \
> @@ -2271,6 +2274,7 @@ ALLDEPFILES = \
> riscv-fbsd-tdep.c \
> riscv-linux-nat.c \
> riscv-linux-tdep.c \
> + riscv-none-tdep.c \
> riscv-ravenscar-thread.c \
> riscv-tdep.c \
> rl78-tdep.c \
> diff --git a/gdb/configure.tgt b/gdb/configure.tgt
> index 6e039838748..ad88ddd9302 100644
> --- a/gdb/configure.tgt
> +++ b/gdb/configure.tgt
> @@ -85,7 +85,7 @@ ia64*-*-*)
> ;;
>
> riscv*-*-*)
> - cpu_obs="riscv-tdep.o arch/riscv.o \
> + cpu_obs="riscv-tdep.o riscv-none-tdep.o arch/riscv.o \
> ravenscar-thread.o riscv-ravenscar-thread.o";;
>
> x86_64-*-*)
> diff --git a/gdb/none-tdep.c b/gdb/none-tdep.c
> new file mode 100644
> index 00000000000..8ec2407ad45
> --- /dev/null
> +++ b/gdb/none-tdep.c
> @@ -0,0 +1,119 @@
> +/* Target-dependent code for none, architecture independent.
> +
> + Copyright (C) 2020 Free Software Foundation, Inc.
2020~2021 now I think.
> +
> + 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/>. */
> +
> +#include "defs.h"
> +#include "none-tdep.h"
> +#include "regset.h"
> +#include "elf-bfd.h" /* for elfcore_write_* */
> +#include "inferior.h"
> +#include "regcache.h"
> +#include "gdbarch.h"
> +#include "gcore.h"
> +
> +/* Build the note section for a corefile, and return it in a malloc
> + buffer. Currently this just dumps all available registers for each
Spurious whitespace in "just dumps"
> + thread. */
> +
> +static gdb::unique_xmalloc_ptr<char>
> +none_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
> +{
> + gdb::unique_xmalloc_ptr<char> note_data;
> +
> + /* Add note information about the executable and its arguments. */
> + std::string fname;
> + std::string psargs;
> + static const size_t fname_len = 16;
> + static const size_t psargs_len = 80;
> + if (get_exec_file (0))
> + {
> + const char *exe = get_exec_file (0);
> + fname = lbasename (exe);
> + psargs = std::string (exe);
> +
> + const char *infargs = get_inferior_args ();
> + if (infargs != nullptr)
> + psargs += " " + std::string (infargs);
> +
> + /* All existing targets that handling writing out prpsinfo expect the
handling -> handle
> + fname and psargs strings to be at least 16 and 80 characters long
> + respectively, including a null terminator at the end. Resize to
> + the expected length minus one to ensure there is a null within the
> + required length. */
> + fname.resize (fname_len - 1);
> + psargs.resize (psargs_len - 1);
> + }
> +
> + /* Resize the buffers up to their required lengths. This will fill any
> + remaining space with the null character. */
> + fname.resize (fname_len);
> + psargs.resize (psargs_len);
> +
> + /* Now write out the prpsinfo structure. */
> + note_data.reset (elfcore_write_prpsinfo (obfd, note_data.release (),
> + note_size, fname.c_str (),
> + psargs.c_str ()));
> + if (note_data == nullptr)
> + return nullptr;
> +
> + /* Thread register information. */
> + try
> + {
> + update_thread_list ();
> + }
> + catch (const gdb_exception_error &e)
> + {
> + exception_print (gdb_stderr, e);
> + }
> +
> + /* Like the Linux kernel, prefer dumping the signalled thread first.
> + "First thread" is what tools use to infer the signalled thread. */
> + thread_info *signalled_thr = gcore_find_signalled_thread ();
> +
> + /* All threads are reported as having been stopped by the same signal
> + that stopped SIGNALLED_THR. */
> + gdb_signal stop_signal;
> + if (signalled_thr != nullptr)
> + stop_signal = signalled_thr->suspend.stop_signal;
> + else
> + stop_signal = GDB_SIGNAL_0;
> +
> + if (signalled_thr != nullptr)
> + gcore_build_thread_register_notes (gdbarch, signalled_thr,
> + stop_signal, obfd, ¬e_data,
> + note_size);
> + for (thread_info *thr : current_inferior ()->non_exited_threads ())
> + {
> + if (thr == signalled_thr)
> + continue;
> +
> + gcore_build_thread_register_notes (gdbarch, thr, stop_signal, obfd,
> + ¬e_data, note_size);
> + }
> +
> + return note_data;
> +}
> +
> +/* See none-tdep.h. */
> +
> +void
> +none_init_abi (struct gdbarch *gdbarch)
> +{
> + /* Default core file support. */
> + set_gdbarch_make_corefile_notes (gdbarch, none_make_corefile_notes);
> +}
> diff --git a/gdb/none-tdep.h b/gdb/none-tdep.h
> new file mode 100644
> index 00000000000..46dcfe219ef
> --- /dev/null
> +++ b/gdb/none-tdep.h
> @@ -0,0 +1,30 @@
> +/* Architecture independent code for ABI 'none' (bare-metal).
> +
> + Copyright (C) 2021 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/>. */
> +
> +#ifndef NONE_TDEP_H
> +#define NONE_TDEP_H
> +
> +struct gdbarch;
> +
> +/* Initialize support for cross-architecture features applicable for the
> + GDB_OSABI_NONE ABI, that is bare-metal targets. */
> +
> +void none_init_abi (struct gdbarch *gdbarch);
> +
> +#endif /* NONE_TDEP_H */
> diff --git a/gdb/riscv-none-tdep.c b/gdb/riscv-none-tdep.c
> new file mode 100644
> index 00000000000..e06ee0981fe
> --- /dev/null
> +++ b/gdb/riscv-none-tdep.c
> @@ -0,0 +1,108 @@
> +/* Copyright (C) 2020 Free Software Foundation, Inc.
2020~2021 now.
> +
> + 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/>. */
> +
> +/* This file contain code that is specific for bare-metal RISC-V targets. */
> +
> +#include "defs.h"
> +#include "arch-utils.h"
> +#include "regcache.h"
> +#include "riscv-tdep.h"
> +#include "elf-bfd.h"
> +#include "regset.h"
> +#include "none-tdep.h"
> +
> +/* Define the general register mapping. This follows the same format as
> + the RISC-V linux corefile. The linux kernel puts the PC at offset 0,
> + gdb puts it at offset 32. Register x0 is always 0 and can be ignored.
> + Registers x1 to x31 are in the same place. */
> +
> +static const struct regcache_map_entry riscv_gregmap[] =
> +{
> + { 1, RISCV_PC_REGNUM, 0 },
> + { 31, RISCV_RA_REGNUM, 0 }, /* x1 to x31 */
> + { 0 }
> +};
> +
> +/* Define the FP register mapping. This follows the same format as the
> + RISC-V linux corefile. The kernel puts the 32 FP regs first, and then
> + FCSR. */
> +
> +static const struct regcache_map_entry riscv_fregmap[] =
> +{
> + { 32, RISCV_FIRST_FP_REGNUM, 0 },
> + { 1, RISCV_CSR_FCSR_REGNUM, 4 }, /* Always stored as 4-bytes. */
> + { 0 }
> +};
> +
> +/* Define the general register regset. */
> +
> +static const struct regset riscv_gregset =
> +{
> + riscv_gregmap, riscv_supply_regset, regcache_collect_regset
> +};
> +
> +/* Define the FP register regset. */
> +
> +static const struct regset riscv_fregset =
> +{
> + riscv_fregmap, riscv_supply_regset, regcache_collect_regset
> +};
> +
> +/* Implement the "iterate_over_regset_sections" gdbarch method. */
> +
> +static void
> +riscv_iterate_over_regset_sections (struct gdbarch *gdbarch,
> + iterate_over_regset_sections_cb *cb,
> + void *cb_data,
> + const struct regcache *regcache)
> +{
> + /* Write out the GPRs. */
> + int sz = 32 * riscv_isa_xlen (gdbarch);
> + cb (".reg", sz, sz, &riscv_gregset, NULL, cb_data);
> +
> + /* Write out the FPRs, but only if present. */
> + if (riscv_isa_flen (gdbarch) > 0)
> + {
> + sz = (32 * riscv_isa_flen (gdbarch)
> + + register_size (gdbarch, RISCV_CSR_FCSR_REGNUM));
> + cb (".reg2", sz, sz, &riscv_fregset, NULL, cb_data);
> + }
> +}
> +
> +/* Initialize RISC-V bare-metal ABI info. */
> +
> +static void
> +riscv_none_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
> +{
> + none_init_abi (gdbarch);
> +
> + /* Iterate over registers for reading and writing bare metal RISC-V core
> + files. */
> + set_gdbarch_iterate_over_regset_sections
> + (gdbarch, riscv_iterate_over_regset_sections);
> +
> +}
> +
> +/* Initialize RISC-V bare-metal target support. */
> +
> +void _initialize_riscv_none_tdep ();
> +void
> +_initialize_riscv_none_tdep ()
> +{
> + gdbarch_register_osabi (bfd_arch_riscv, 0, GDB_OSABI_NONE,
> + riscv_none_init_abi);
> +}
>
Otherwise this looks good to me.
next prev parent reply other threads:[~2021-02-01 14:05 UTC|newest]
Thread overview: 57+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-01-20 20:23 [PATCHv2 0/9] Bare-metal core dumps for RISC-V Andrew Burgess
2021-01-20 20:23 ` [PATCHv2 1/9] gdb: unify parts of the Linux and FreeBSD core dumping code Andrew Burgess
2021-01-22 12:01 ` Strasuns, Mihails
2021-01-22 18:50 ` Tom Tromey
2021-02-01 11:56 ` Andrew Burgess
2021-02-09 21:52 ` Andrew Burgess
2021-01-20 20:23 ` [PATCHv2 2/9] bfd/binutils: support for gdb target descriptions in the core file Andrew Burgess
2021-01-22 10:47 ` Strasuns, Mihails
2021-01-22 19:30 ` Andrew Burgess
2021-01-25 10:11 ` Strasuns, Mihails
2021-01-25 11:20 ` Andrew Burgess
2021-02-01 12:05 ` PING: " Andrew Burgess
2021-02-01 15:10 ` Strasuns, Mihails
2021-02-01 13:29 ` Luis Machado
2021-02-10 20:45 ` Jim Wilson
2021-01-20 20:23 ` [PATCHv2 3/9] gdb: write target description into " Andrew Burgess
2021-01-22 19:15 ` Tom Tromey
2021-02-01 13:37 ` Luis Machado
2021-01-20 20:23 ` [PATCHv2 4/9] bfd/riscv: prepare to handle bare metal core dump creation Andrew Burgess
2021-02-01 12:03 ` PING: " Andrew Burgess
2021-02-01 13:48 ` Luis Machado
2021-02-01 14:44 ` Andrew Burgess
2021-02-10 20:57 ` Jim Wilson
2021-01-20 20:23 ` [PATCHv2 5/9] gdb/riscv: introduce bare metal core dump support Andrew Burgess
2021-02-01 14:05 ` Luis Machado [this message]
2021-02-03 3:04 ` Palmer Dabbelt
2021-01-20 20:23 ` [PATCHv2 6/9] bfd/binutils: add support for RISC-V CSRs in core files Andrew Burgess
2021-02-01 12:00 ` Andrew Burgess
2021-02-01 14:08 ` Luis Machado
2021-02-10 21:00 ` Jim Wilson
2021-01-20 20:23 ` [PATCHv2 7/9] gdb/riscv: make riscv target description names global Andrew Burgess
2021-02-01 14:22 ` Luis Machado
2021-01-20 20:23 ` [PATCHv2 8/9] gdb/riscv: write CSRs into baremetal core dumps Andrew Burgess
2021-02-01 14:33 ` Luis Machado
2021-01-20 20:23 ` [PATCHv2 9/9] gdb/arm: add support for bare-metal " Andrew Burgess
2021-02-01 14:51 ` Luis Machado
2021-01-22 19:28 ` [PATCHv2 0/9] Bare-metal core dumps for RISC-V Tom Tromey
2021-02-15 17:29 ` [PATCHv3 " Andrew Burgess
2021-02-15 17:29 ` [PATCHv3 1/9] gdb: unify parts of the Linux and FreeBSD core dumping code Andrew Burgess
2021-02-15 22:56 ` Lancelot SIX
2021-02-16 16:55 ` Andrew Burgess
2021-02-15 17:29 ` [PATCHv3 2/9] bfd/binutils: support for gdb target descriptions in the core file Andrew Burgess
2021-02-15 17:29 ` [PATCHv3 3/9] gdb: write target description into " Andrew Burgess
2021-02-15 17:29 ` [PATCHv3 4/9] bfd/riscv: prepare to handle bare metal core dump creation Andrew Burgess
2021-02-15 17:29 ` [PATCHv3 5/9] gdb/riscv: introduce bare metal core dump support Andrew Burgess
2021-02-15 17:29 ` [PATCHv3 6/9] bfd/binutils: add support for RISC-V CSRs in core files Andrew Burgess
2021-02-15 17:29 ` [PATCHv3 7/9] gdb/riscv: make riscv target description names global Andrew Burgess
2021-02-15 17:29 ` [PATCHv3 8/9] gdb/riscv: write CSRs into baremetal core dumps Andrew Burgess
2021-02-15 17:29 ` [PATCHv3 9/9] gdb/arm: add support for bare-metal " Andrew Burgess
2021-05-13 13:42 ` Andrew Burgess
2021-05-13 13:51 ` Luis Machado
2021-05-13 13:56 ` Andrew Burgess
2021-05-15 13:52 ` SV: " sarah@hederstierna.com
2021-06-01 9:00 ` Andrew Burgess
2021-03-01 10:32 ` [PATCHv3 0/9] Bare-metal core dumps for RISC-V Andrew Burgess
2021-03-01 14:45 ` Nick Clifton
2021-03-05 17:35 ` Andrew Burgess
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=d58b8601-bfd4-98bb-a9f5-fedc1bc8b905@linaro.org \
--to=luis.machado@linaro.org \
--cc=andrew.burgess@embecosm.com \
--cc=binutils@sourceware.org \
--cc=fredrik@hederstierna.com \
--cc=gdb-patches@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).