From: Luis Machado <luis.machado@linaro.org>
To: David Spickett <david.spickett@linaro.org>
Cc: gdb-patches@sourceware.org, alan.hayward@arm.com,
John Baldwin <jhb@freebsd.org>,
Catalin Marinas <catalin.marinas@arm.com>
Subject: Re: [PATCH] [AArch64] MTE corefile support
Date: Wed, 19 May 2021 08:11:59 -0300 [thread overview]
Message-ID: <fa40d366-87e6-1d42-2604-f3aa13b3374e@linaro.org> (raw)
In-Reply-To: <CACBW-2L3RPAK+oE9Thjn4+ShebLSdBs5BpmOTWbGNV6V2HQ7fA@mail.gmail.com>
On 5/19/21 7:01 AM, David Spickett wrote:
>> +/* Maximum number of tags to request. */
>> +#define MAX_TAGS_TO_TRANSFER 1024
>
> Is this 1024 derived from something? Random guess the max note size
> minus the size of the note header.
No. Just a fixed number so we can transfer tags in chunks as opposed to
transferring a lot of them at once. ptrace won't let us do it in one go
anyway.
>
>> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
>> + hook. Decode a memory tag note and return the request tags. */
>> +
>> +static gdb::byte_vector
>> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
>> + gdb::array_view <const gdb_byte> note,
>> + CORE_ADDR address, size_t length)
>
> Would be worth commenting that this assumes that address is within the
> range of the note given. (I see later that it's only called if that's
> true)
I'll add a comment.
>
> On Tue, 18 May 2021 at 21:20, Luis Machado <luis.machado@linaro.org> wrote:
>>
>> Teach GDB how to dump memory tags when using the gcore command and how
>> to read them back from a core file generated via gcore or the kernel.
>>
>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
>> own NT_MEMTAG note. A section named ".memtag" is created for each of those
>> when reading the core file back.
>>
>> Dumping memory tags
>> -
>>
>> When using the gcore command to dump a core file, GDB will go through the maps
>> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
>> passed to an arch-specific gdbarch hook that generates a vector of blobs of
>> memory tag data that are blindly put into a NT_MEMTAG note.
>>
>> The vector is used because we may have, in the future, multiple tag types for
>> a particular memory range.
>>
>> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
>> like so:
>>
>> struct tag_dump_header
>> {
>> uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>> uint64_t start_vma;
>> uint64_t end_vma;
>> };
>>
>> struct tag_dump_mte
>> {
>> uint16_t granule_byte_size;
>> uint16_t tag_bit_size;
>> uint16_t __unused;
>> };
>>
>> The only bits meant to be generic are the tag_dump_format, start_vma and
>> end_vma fields.
>>
>> The format-specific data is supposed to be opaque and only useful for the
>> arch-specific code.
>>
>> We can extend the format in the future to make room for other memory tag
>> layouts.
>>
>> Reading memory tags
>> -
>>
>> When reading a core file that contains NT_MEMTAG entries, GDB will use
>> a different approach to check for tagged memory range. Rather than looking
>> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
>> memory range.
>>
>> When reading tags, GDB will now use the core target's implementation of
>> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
>> is fed into an arch-specific hook that will decode the memory tag format and
>> return a vector of tags.
>>
>> I've added a test to exercise writing and reading of memory tags in core
>> files.
>>
>> gdb/ChangeLog:
>>
>> YYYY-MM-DD Luis Machado <luis.machado@linaro.org>
>>
>> * aarch64-linux-tdep.c: Include elf/common.h.
>> (MAX_TAGS_TO_TRANSFER): New constant.
>> (aarch64_linux_create_memtag_notes_from_range): New function.
>> (aarch64_linux_decode_memtag_note): Likewise.
>> (aarch64_linux_init_abi): Register new core file hooks.
>> * arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
>> (struct tag_dump_header): New struct.
>> (struct tag_dump_mte): New struct.
>> (MEMTAG_NOTE_HEADER_SIZE): New constant.
>> * corelow.c (core_target) <supports_memory_tagging, fetch_memtags>: New
>> method overrides.
>> * gdbarch.c: Regenerate.
>> * gdbarch.h: Likewise.
>> * gdbarch.sh (create_memtag_notes_from_range): New hook.
>> (decode_memtag_note): Likewise.
>> * linux-tdep.c (linux_address_in_memtag_page): Renamed to...
>> (linux_process_address_in_memtag_page): ... this.
>> (linux_core_file_address_in_memtag_page): New function.
>> (linux_address_in_memtag_page): Likewise.
>> (linux_make_memtag_corefile_notes): Likewise.
>> (linux_make_corefile_notes): Handle memory tag notes.
>> * NEWS: Mention core file support for memory tagging.
>>
>> gdb/doc/ChangeLog:
>>
>> YYYY-MM-DD Luis Machado <luis.machado@linaro.org>
>>
>> * gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
>> for memory tagging in core files.
>>
>> gdb/testsuite/ChangeLog:
>>
>> YYYY-MM-DD Luis Machado <luis.machado@linaro.org>
>>
>> * gdb.arch/aarch64-mte-gcore.c: New file.
>> * gdb.arch/aarch64-mte-gcore.exp: New file.
>> ---
>> gdb/NEWS | 4 +
>> gdb/aarch64-linux-tdep.c | 179 +++++++++++++++++++
>> gdb/arch/aarch64-mte-linux.h | 29 +++
>> gdb/corelow.c | 88 +++++++++
>> gdb/doc/gdb.texinfo | 4 +
>> gdb/gdbarch.c | 64 +++++++
>> gdb/gdbarch.h | 16 ++
>> gdb/gdbarch.sh | 6 +
>> gdb/linux-tdep.c | 141 ++++++++++++++-
>> gdb/testsuite/gdb.arch/aarch64-mte-gcore.c | 93 ++++++++++
>> gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 115 ++++++++++++
>> 11 files changed, 736 insertions(+), 3 deletions(-)
>> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index ab678acec8b..58b9f739d4f 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -3,6 +3,10 @@
>>
>> *** Changes since GDB 10
>>
>> +* GDB now supports dumping memory tag data for AArch64 MTE. It also supports
>> + reading memory tag data for AArch64 MTE from core files generated by
>> + the gcore command or the Linux kernel.
>> +
>> * GDB now supports general memory tagging functionality if the underlying
>> architecture supports the proper primitives and hooks. Currently this is
>> enabled only for AArch64 MTE.
>> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
>> index e9761ed2189..663d0e1a215 100644
>> --- a/gdb/aarch64-linux-tdep.c
>> +++ b/gdb/aarch64-linux-tdep.c
>> @@ -53,6 +53,8 @@
>>
>> #include "gdbsupport/selftest.h"
>>
>> +#include "elf/common.h"
>> +
>> /* Signal frame handling.
>>
>> +------------+ ^
>> @@ -1779,6 +1781,172 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>> }
>> }
>>
>> +/* Maximum number of tags to request. */
>> +#define MAX_TAGS_TO_TRANSFER 1024
>> +
>> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
>> + gdbarch hook. Create core file notes for memory tags. */
>> +
>> +static std::vector<gdb::byte_vector>
>> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
>> + CORE_ADDR start_address,
>> + CORE_ADDR end_address)
>> +{
>> + /* We only handle MTE tags for now. */
>> +
>> + /* Figure out how many tags we need to store in this memory range. */
>> + int granules = aarch64_mte_get_tag_granules (start_address,
>> + end_address - start_address,
>> + AARCH64_MTE_GRANULE_SIZE);
>> +
>> + /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
>> + at the moment). */
>> + std::vector<gdb::byte_vector> notes (1);
>> +
>> + /* If there are no tag granules to fetch, just return. */
>> + if (granules == 0)
>> + return notes;
>> +
>> + /* Adjust the MTE note size to hold the header + tags. */
>> + notes[0].resize (MEMTAG_NOTE_HEADER_SIZE + granules);
>> +
>> + CORE_ADDR address = start_address;
>> + /* Vector of tags. */
>> + gdb::byte_vector tags;
>> +
>> + while (granules > 0)
>> + {
>> + /* Transfer tags in chunks. */
>> + gdb::byte_vector tags_read;
>> + size_t xfer_len
>> + = (granules >= MAX_TAGS_TO_TRANSFER)?
>> + MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
>> + granules * AARCH64_MTE_GRANULE_SIZE;
>> +
>> + if (!target_fetch_memtags (address, xfer_len, tags_read,
>> + static_cast<int> (memtag_type::allocation)))
>> + {
>> + warning (_("Failed to read MTE tags from memory range [%s,%s]."),
>> + phex_nz (start_address, sizeof (start_address)),
>> + phex_nz (end_address, sizeof (end_address)));
>> + notes.resize (0);
>> + return notes;
>> + }
>> +
>> + /* Transfer over the tags that have been read. */
>> + tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
>> +
>> + /* Adjust the remaining granules and starting address. */
>> + granules -= tags_read.size ();
>> + address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
>> + }
>> +
>> + /* Create the header. */
>> + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>> + gdb_byte *buf = notes[0].data ();
>> +
>> + /* Generic header. */
>> + /* Tag dump format */
>> + store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>> + NT_MEMTAG_TYPE_AARCH_MTE);
>> + buf += sizeof (uint16_t);
>> +
>> + /* Start address */
>> + store_unsigned_integer (buf, sizeof (uint64_t), byte_order, start_address);
>> + buf += sizeof (uint64_t);
>> +
>> + /* End address */
>> + store_unsigned_integer (buf, sizeof (uint64_t), byte_order, end_address);
>> + buf += sizeof (uint64_t);
>> +
>> + /* MTE-specific header. */
>> + /* Granule byte size */
>> + store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>> + AARCH64_MTE_GRANULE_SIZE);
>> + buf += sizeof (uint16_t);
>> +
>> + /* Tag bit size */
>> + store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>> + AARCH64_MTE_TAG_BIT_SIZE);
>> + buf += sizeof (uint16_t);
>> +
>> + /* Unused value */
>> + store_unsigned_integer (buf, sizeof (uint16_t), byte_order, 0);
>> +
>> + /* Store the tags. */
>> + memcpy (notes[0].data () + MEMTAG_NOTE_HEADER_SIZE, tags.data (),
>> + tags.size ());
>> +
>> + return notes;
>> +}
>> +
>> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
>> + hook. Decode a memory tag note and return the request tags. */
>> +
>> +static gdb::byte_vector
>> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
>> + gdb::array_view <const gdb_byte> note,
>> + CORE_ADDR address, size_t length)
>> +{
>> + gdb::byte_vector tags;
>> +
>> + /* Read the generic header. */
>> + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>> + struct tag_dump_header header;
>> + const gdb_byte *buf = note.data ();
>> +
>> + header.format
>> + = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
>> + buf += sizeof (uint16_t);
>> +
>> + header.start_vma
>> + = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
>> + buf += sizeof (uint64_t);
>> +
>> + header.end_vma
>> + = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
>> + buf += sizeof (uint64_t);
>> +
>> + /* Sanity check */
>> + if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
>> + {
>> + warning (_("Unexpected memory tag note format (%x).\n"), header.format);
>> + return tags;
>> + }
>> +
>> + /* Calculate how many granules we need to skip to get to the granule of
>> + ADDRESS. Align both the start address and the requested address
>> + so it is easier to get the number of granules to skip. This way we
>> + don't need to consider cases where ADDRESS falls in the middle of a
>> + tag granule range. */
>> + CORE_ADDR aligned_start_address
>> + = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
>> + CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
>> +
>> + int skipped_granules
>> + = aarch64_mte_get_tag_granules (aligned_start_address,
>> + aligned_address - aligned_start_address,
>> + AARCH64_MTE_GRANULE_SIZE);
>> +
>> + /* The amount of memory tag granules we need to fetch. */
>> + int granules
>> + = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
>> +
>> + /* If there are no tag granules to decode, just return. */
>> + if (granules == 0)
>> + return tags;
>> +
>> + /* Point to the block of data that contains the first granule we are
>> + interested in. */
>> + const gdb_byte *tags_data = note.data () + sizeof (header) + skipped_granules;
>> +
>> + /* Read the tag granules. */
>> + for (unsigned int i = 0; i < granules; i++)
>> + tags.push_back (tags_data[i]);
>> +
>> + return tags;
>> +}
>> +
>> static void
>> aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>> {
>> @@ -1862,6 +2030,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>
>> set_gdbarch_report_signal_info (gdbarch,
>> aarch64_linux_report_signal_info);
>> +
>> + /* Core file helpers. */
>> +
>> + /* Core file helper to create memory tag notes for a particular range of
>> + addresses. */
>> + set_gdbarch_create_memtag_notes_from_range (gdbarch,
>> + aarch64_linux_create_memtag_notes_from_range);
>> +
>> + /* Core file helper to decode a memory tag note. */
>> + set_gdbarch_decode_memtag_note (gdbarch,
>> + aarch64_linux_decode_memtag_note);
>> }
>>
>> /* Initialize the aarch64_linux_record_tdep. */
>> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
>> index 2aa97eb861a..a4a26628847 100644
>> --- a/gdb/arch/aarch64-mte-linux.h
>> +++ b/gdb/arch/aarch64-mte-linux.h
>> @@ -32,6 +32,7 @@
>>
>> /* We have one tag per 16 bytes of memory. */
>> #define AARCH64_MTE_GRANULE_SIZE 16
>> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>> #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>> #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>>
>> @@ -71,4 +72,32 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>> It is always possible to get the logical tag. */
>> extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>
>> +/* Headers for the NT_MEMTAG notes. */
>> +
>> +/* Generic NT_MEMTAG header. */
>> +struct tag_dump_header
>> +{
>> + /* Tag format. */
>> + uint16_t format;
>> + /* Start address of the tagged range. */
>> + uint64_t start_vma;
>> + /* End address of the tagged range. */
>> + uint64_t end_vma;
>> +};
>> +
>> +/* MTE-specific NT_MEMTAG header. */
>> +struct tag_dump_mte
>> +{
>> + /* Size of the tag granule in bytes. */
>> + uint16_t granule_byte_size;
>> + /* Size of the tag in bits. */
>> + uint16_t tag_bit_size;
>> + /* Reserved field for the future. */
>> + uint16_t __unused;
>> +};
>> +
>> +/* Memory tag note header size. Includes both the generic and the
>> + arch-specific parts. */
>> +#define MEMTAG_NOTE_HEADER_SIZE (2 + 8 + 8 + 2 + 2 + 2)
>> +
>> #endif /* ARCH_AARCH64_LINUX_H */
>> diff --git a/gdb/corelow.c b/gdb/corelow.c
>> index 452b4dd4f9a..33d16b7220a 100644
>> --- a/gdb/corelow.c
>> +++ b/gdb/corelow.c
>> @@ -100,6 +100,13 @@ class core_target final : public process_stratum_target
>>
>> bool info_proc (const char *, enum info_proc_what) override;
>>
>> + bool supports_memory_tagging () override;
>> +
>> + /* Core file implementation of fetch_memtags. Fetch the memory tags from
>> + core file notes. */
>> + bool fetch_memtags (CORE_ADDR address, size_t len,
>> + gdb::byte_vector &tags, int type) override;
>> +
>> /* A few helpers. */
>>
>> /* Getter, see variable definition. */
>> @@ -1115,6 +1122,87 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>> return true;
>> }
>>
>> +/* Implementation of the "supports_memory_tagging" target_ops method. */
>> +
>> +bool
>> +core_target::supports_memory_tagging ()
>> +{
>> + /* Look for memory tag notes. If they exist, that means this core file
>> + supports memory tagging. */
>> + if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
>> + return false;
>> +
>> + return true;
>> +}
>> +
>> +/* Implementation of the "fetch_memtags" target_ops method. */
>> +
>> +bool
>> +core_target::fetch_memtags (CORE_ADDR address, size_t len,
>> + gdb::byte_vector &tags, int type)
>> +{
>> + struct gdbarch *gdbarch = target_gdbarch ();
>> +
>> + /* Make sure we have a way to decode the memory tag notes. */
>> + if (!gdbarch_decode_memtag_note_p (gdbarch))
>> + warning (_("gdbarch_decode_memtag_note not implemented for this "
>> + "architecture."));
>> +
>> + asection *section
>> + = bfd_get_section_by_name (core_bfd, ".memtag");
>> +
>> + /* Remove the top byte for the memory range check. */
>> + address = address_significant (gdbarch, address);
>> +
>> + /* Go through all the memtag sections and figure out if ADDRESS
>> + falls within one of the memory ranges that contain tags. */
>> + while (section != nullptr)
>> + {
>> + size_t note_size = bfd_section_size (section);
>> +
>> + /* If the note is smaller than the size of the header, this core note
>> + is malformed. */
>> + if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
>> + {
>> + warning (_("malformed core note - too short for header"));
>> + return false;
>> + }
>> +
>> + gdb::byte_vector note (note_size);
>> +
>> + /* Fetch the contents of this particular memtag note. */
>> + if (!bfd_get_section_contents (core_bfd, section,
>> + note.data (), 0, note_size))
>> + {
>> + warning (_("could not get core note contents."));
>> + return false;
>> + }
>> +
>> + /* Read the generic header of the note. Thos contains the format,
>> + start address and end address. */
>> + uint64_t start_address
>> + = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
>> + uint64_t end_address
>> + = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
>> + + sizeof (uint64_t));
>> +
>> + /* Is the address within [start_address, end_address)? */
>> + if (address >= start_address
>> + && address < end_address)
>> + {
>> + /* Decode the memory tag note and return the tags. */
>> + tags = gdbarch_decode_memtag_note (gdbarch, note, address, len);
>> + return true;
>> + }
>> +
>> + /* The requested address lies outside this particular memtag note. Keep
>> + looking and get the next section. */
>> + section = bfd_get_next_section_by_name (core_bfd, section);
>> + }
>> +
>> + return false;
>> +}
>> +
>> /* Get a pointer to the current core target. If not connected to a
>> core target, return NULL. */
>>
>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>> index 56f37eb2288..738cb3b56eb 100644
>> --- a/gdb/doc/gdb.texinfo
>> +++ b/gdb/doc/gdb.texinfo
>> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
>> option @code{PR_SET_TAGGED_ADDR_CTRL}. For further information, see the
>> documentation in the Linux kernel.
>>
>> +@value{GDBN} supports dumping memory tag data to core files through the
>> +@command{gcore} command and reading memory tag data from core files generated
>> +by the @command{gcore} command or the Linux kernel.
>> +
>> @node i386
>> @subsection x86 Architecture-specific Issues
>>
>> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
>> index 208cf4b5aaa..de384da2e9a 100644
>> --- a/gdb/gdbarch.c
>> +++ b/gdb/gdbarch.c
>> @@ -283,6 +283,8 @@ struct gdbarch
>> gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>> gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>> gdbarch_find_memory_regions_ftype *find_memory_regions;
>> + gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
>> + gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>> gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>> gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>> gdbarch_core_pid_to_str_ftype *core_pid_to_str;
>> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>> /* Skip verify of iterate_over_regset_sections, has predicate. */
>> /* Skip verify of make_corefile_notes, has predicate. */
>> /* Skip verify of find_memory_regions, has predicate. */
>> + /* Skip verify of create_memtag_notes_from_range, has predicate. */
>> + /* Skip verify of decode_memtag_note, has predicate. */
>> /* Skip verify of core_xfer_shared_libraries, has predicate. */
>> /* Skip verify of core_xfer_shared_libraries_aix, has predicate. */
>> /* Skip verify of core_pid_to_str, has predicate. */
>> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>> fprintf_unfiltered (file,
>> "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>> host_address_to_string (gdbarch->core_xfer_siginfo));
>> + fprintf_unfiltered (file,
>> + "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
>> + gdbarch_create_memtag_notes_from_range_p (gdbarch));
>> + fprintf_unfiltered (file,
>> + "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
>> + host_address_to_string (gdbarch->create_memtag_notes_from_range));
>> + fprintf_unfiltered (file,
>> + "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
>> + gdbarch_decode_memtag_note_p (gdbarch));
>> + fprintf_unfiltered (file,
>> + "gdbarch_dump: decode_memtag_note = <%s>\n",
>> + host_address_to_string (gdbarch->decode_memtag_note));
>> fprintf_unfiltered (file,
>> "gdbarch_dump: decr_pc_after_break = %s\n",
>> core_addr_to_string_nz (gdbarch->decr_pc_after_break));
>> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>> gdbarch->find_memory_regions = find_memory_regions;
>> }
>>
>> +bool
>> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
>> +{
>> + gdb_assert (gdbarch != NULL);
>> + return gdbarch->create_memtag_notes_from_range != NULL;
>> +}
>> +
>> +std::vector<gdb::byte_vector>
>> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
>> +{
>> + gdb_assert (gdbarch != NULL);
>> + gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
>> + if (gdbarch_debug >= 2)
>> + fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
>> + return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
>> +}
>> +
>> +void
>> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
>> + gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
>> +{
>> + gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
>> +}
>> +
>> +bool
>> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
>> +{
>> + gdb_assert (gdbarch != NULL);
>> + return gdbarch->decode_memtag_note != NULL;
>> +}
>> +
>> +gdb::byte_vector
>> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
>> +{
>> + gdb_assert (gdbarch != NULL);
>> + gdb_assert (gdbarch->decode_memtag_note != NULL);
>> + if (gdbarch_debug >= 2)
>> + fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
>> + return gdbarch->decode_memtag_note (gdbarch, note, address, length);
>> +}
>> +
>> +void
>> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
>> + gdbarch_decode_memtag_note_ftype decode_memtag_note)
>> +{
>> + gdbarch->decode_memtag_note = decode_memtag_note;
>> +}
>> +
>> bool
>> gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>> {
>> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
>> index 7157e5596fd..80e244624de 100644
>> --- a/gdb/gdbarch.h
>> +++ b/gdb/gdbarch.h
>> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>> extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>> extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>>
>> +/* Create memory tag core file notes given a range of addresses. */
>> +
>> +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
>> +
>> +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
>> +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
>> +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
>> +
>> +/* Decode a memory tag note and return the tags that it contains. */
>> +
>> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
>> +
>> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
>> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
>> +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
>> +
>> /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>> core file into buffer READBUF with length LEN. Return the number of bytes read
>> (zero indicates failure).
>> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
>> index 43e51341f97..7c5eed0780c 100755
>> --- a/gdb/gdbarch.sh
>> +++ b/gdb/gdbarch.sh
>> @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
>> # Find core file memory regions
>> M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
>>
>> +# Create memory tag core file notes given a range of addresses.
>> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
>> +
>> +# Decode a memory tag note and return the tags that it contains.
>> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
>> +
>> # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>> # core file into buffer READBUF with length LEN. Return the number of bytes read
>> # (zero indicates failure).
>> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
>> index 927e69bf1e1..f4ff57dec87 100644
>> --- a/gdb/linux-tdep.c
>> +++ b/gdb/linux-tdep.c
>> @@ -1438,10 +1438,11 @@ parse_smaps_data (const char *data,
>> return smaps;
>> }
>>
>> -/* See linux-tdep.h. */
>> +/* Helper that checks if an address is in a memory tag page for a live
>> + process. */
>>
>> -bool
>> -linux_address_in_memtag_page (CORE_ADDR address)
>> +static bool
>> +linux_process_address_in_memtag_page (CORE_ADDR address)
>> {
>> if (current_inferior ()->fake_pid_p)
>> return false;
>> @@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
>> return false;
>> }
>>
>> +/* Helper that checks if an address is in a memory tag page for a core file
>> + process. */
>> +
>> +static bool
>> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
>> +{
>> + if (core_bfd == nullptr)
>> + return false;
>> +
>> + asection *section
>> + = bfd_get_section_by_name (core_bfd, ".memtag");
>> +
>> + /* Go through all the memtag sections and figure out if ADDRESS
>> + falls within one of the memory ranges that contain tags. */
>> + while (section != nullptr)
>> + {
>> + size_t note_size = bfd_section_size (section);
>> +
>> + /* If the note is smaller than the size of the header, this core note
>> + is malformed. */
>> + if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
>> + {
>> + warning (_("malformed core note - too short for header"));
>> + return false;
>> + }
>> +
>> + gdb::byte_vector note (note_size);
>> +
>> + /* Fetch the contents of this particular memtag note. */
>> + if (!bfd_get_section_contents (core_bfd, section,
>> + note.data (), 0, note_size))
>> + {
>> + warning (_("could not get core note contents."));
>> + return false;
>> + }
>> +
>> + /* Read the generic header of the note. Those contain the format,
>> + start address and end address. */
>> + uint64_t start_address
>> + = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
>> + uint64_t end_address
>> + = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
>> + + sizeof (uint64_t));
>> +
>> + /* Is the address within [start_address, end_address)? */
>> + if (address >= start_address
>> + && address < end_address)
>> + return true;
>> +
>> + /* The requested address lies outside this particular memtag note. Keep
>> + looking and get the next section. */
>> + section = bfd_get_next_section_by_name (core_bfd, section);
>> + }
>> +
>> + return false;
>> +}
>> +
>> +/* See linux-tdep.h. */
>> +
>> +bool
>> +linux_address_in_memtag_page (CORE_ADDR address)
>> +{
>> + if (!target_has_execution ())
>> + return linux_core_file_address_in_memtag_page (address);
>> +
>> + return linux_process_address_in_memtag_page (address);
>> +}
>> +
>> +/* For each memory map entry that has memory tagging enabled, create a new
>> + core file note that contains all of its memory tags. Save the data to
>> + NOTE_DATA and update NOTE_SIZE accordingly. */
>> +
>> +static void
>> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
>> + gdb::unique_xmalloc_ptr<char> ¬e_data,
>> + int *note_size)
>> +{
>> + if (current_inferior ()->fake_pid_p)
>> + return;
>> +
>> + /* If the architecture doesn't have a hook to return memory tag notes,
>> + there is nothing left to do. */
>> + if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
>> + return;
>> +
>> + pid_t pid = current_inferior ()->pid;
>> +
>> + std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
>> +
>> + gdb::unique_xmalloc_ptr<char> data
>> + = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
>> +
>> + if (data == nullptr)
>> + return;
>> +
>> + std::vector<struct smaps_data> smaps;
>> +
>> + /* Parse the contents of smaps into a vector. */
>> + smaps = parse_smaps_data (data.get (), smaps_file);
>> +
>> + for (const smaps_data &map : smaps)
>> + {
>> + /* Does this mapping have memory tagging enabled? If so, save the
>> + memory tags to the core file note. */
>> + if (map.vmflags.memory_tagging == 0)
>> + continue;
>> +
>> + /* Ask the architecture to create (one or more) NT_MEMTAG notes for
>> + this particular memory range, including the header.
>> +
>> + If the notes are too big, we may need to break up the transfer
>> + into smaller chunks.
>> +
>> + If the architecture returns an empty vector, that means there are
>> + no memory tag notes to write. */
>> + std::vector<gdb::byte_vector> memory_tag_notes;
>> + memory_tag_notes
>> + = gdbarch_create_memtag_notes_from_range (gdbarch,
>> + map.start_address,
>> + map.end_address);
>> + /* Write notes to the core file. */
>> + for (gdb::byte_vector note : memory_tag_notes)
>> + {
>> + note_data.reset (elfcore_write_note (obfd, note_data.release (),
>> + note_size, "CORE",
>> + NT_MEMTAG, note.data (),
>> + note.size ()));
>> + }
>> + }
>> +}
>> +
>> /* List memory regions in the inferior for a corefile. */
>>
>> static int
>> @@ -2051,6 +2183,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
>> return NULL;
>> }
>>
>> + /* Dump the memory tags, if any. */
>> + linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
>> +
>> /* File mappings. */
>> linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
>>
>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> new file mode 100644
>> index 00000000000..b20ebcff424
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> @@ -0,0 +1,93 @@
>> +/* This test program is part of GDB, the GNU debugger.
>> +
>> + Copyright 2021 Free Software Foundation, Inc.
>> +
>> + 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/>. */
>> +
>> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers. */
>> +
>> +/* This test was based on the documentation for the AArch64 Memory Tagging
>> + Extension from the Linux Kernel, found in the sources in
>> + Documentation/arm64/memory-tagging-extension.rst. */
>> +
>> +#include <errno.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +#include <sys/auxv.h>
>> +#include <sys/mman.h>
>> +#include <sys/prctl.h>
>> +
>> +/* From arch/arm64/include/uapi/asm/hwcap.h */
>> +#define HWCAP2_MTE (1 << 18)
>> +
>> +/* From arch/arm64/include/uapi/asm/mman.h */
>> +#define PROT_MTE 0x20
>> +
>> +/* From include/uapi/linux/prctl.h */
>> +#define PR_SET_TAGGED_ADDR_CTRL 55
>> +#define PR_GET_TAGGED_ADDR_CTRL 56
>> +#define PR_TAGGED_ADDR_ENABLE (1UL << 0)
>> +#define PR_MTE_TCF_SHIFT 1
>> +#define PR_MTE_TCF_SYNC (1UL << PR_MTE_TCF_SHIFT)
>> +#define PR_MTE_TAG_SHIFT 3
>> +
>> +void
>> +access_memory (unsigned char *tagged_ptr)
>> +{
>> + tagged_ptr[0] = 'a';
>> +}
>> +
>> +int
>> +main (int argc, char **argv)
>> +{
>> + unsigned char *tagged_ptr;
>> + unsigned long page_sz = sysconf (_SC_PAGESIZE);
>> + unsigned long hwcap2 = getauxval(AT_HWCAP2);
>> +
>> + /* Bail out if MTE is not supported. */
>> + if (!(hwcap2 & HWCAP2_MTE))
>> + return 1;
>> +
>> + /* Enable the tagged address ABI, synchronous MTE tag check faults and
>> + allow all non-zero tags in the randomly generated set. */
>> + if (prctl (PR_SET_TAGGED_ADDR_CTRL,
>> + PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
>> + | (0xfffe << PR_MTE_TAG_SHIFT),
>> + 0, 0, 0))
>> + {
>> + perror ("prctl () failed");
>> + return 1;
>> + }
>> +
>> + /* Create a mapping that will have PROT_MTE set. */
>> + tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
>> + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
>> + if (tagged_ptr == MAP_FAILED)
>> + {
>> + perror ("mmap () failed");
>> + return 1;
>> + }
>> +
>> + /* Enable MTE on the above anonymous mmap. */
>> + if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
>> + {
>> + perror ("mprotect () failed");
>> + return 1;
>> + }
>> +
>> + access_memory (tagged_ptr);
>> +
>> + return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> new file mode 100644
>> index 00000000000..bb529a8b369
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> @@ -0,0 +1,115 @@
>> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
>> +#
>> +# 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 is part of the gdb testsuite.
>> +
>> +# Test generating and reading a core file with MTE memory tags.
>> +
>> +if {![is_aarch64_target]} {
>> + verbose "Skipping ${gdb_test_file_name}."
>> + return
>> +}
>> +
>> +standard_testfile
>> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
>> + return -1
>> +}
>> +
>> +if ![runto_main] {
>> + untested "could not run to main"
>> + return -1
>> +}
>> +
>> +# Targets that don't support memory tagging should not execute the
>> +# runtime memory tagging tests.
>> +if {![supports_memtag]} {
>> + unsupported "memory tagging unsupported"
>> + return -1
>> +}
>> +
>> +gdb_breakpoint "access_memory"
>> +
>> +if [gdb_continue "access_memory"] {
>> + return -1
>> +}
>> +
>> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
>> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> + set index [expr [expr 15 - $i] * 16]
>> + set tag [format "%02x" $i]
>> + gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
>> + $atag_msg \
>> + "set memory tag of &tagged_ptr\[$index\] to $tag"
>> +}
>> +
>> +# Run until a crash and confirm GDB displays memory tag violation
>> +# information.
>> +gdb_test "continue" \
>> + [multi_line \
>> + "Program received signal SIGSEGV, Segmentation fault" \
>> + "Memory tag violation while accessing address $hex" \
>> + "Allocation tag $hex" \
>> + "Logical tag $hex\." \
>> + "$hex in access_memory \\(.*\\) at .*" \
>> + ".*tagged_ptr\\\[0\\\] = 'a';"] \
>> + "display tag violation information for live process"
>> +
>> +# Generate the core file.
>> +set core_filename [standard_output_file "$testfile.core"]
>> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
>> +
>> +if { !$core_generated } {
>> + return -1
>> +}
>> +
>> +clean_restart
>> +
>> +# Load the program file.
>> +set program_filename [standard_output_file $testfile]
>> +set program_loaded [gdb_file_cmd $program_filename]
>> +
>> +if { $program_loaded } {
>> + return -1
>> +}
>> +
>> +# Load the core file and make sure we see the tag violation fault
>> +# information.
>> +gdb_test "core $core_filename" \
>> + [multi_line \
>> + "Core was generated by.*\." \
>> + "Program terminated with signal SIGSEGV, Segmentation fault" \
>> + "Memory tag violation while accessing address $hex" \
>> + "Allocation tag 0xf" \
>> + "Logical tag 0x0\." \
>> + "#0.*$hex in access_memory \\(.*\\) at .*" \
>> + ".*tagged_ptr\\\[0\\\] = 'a';"] \
>> + "core file shows tag violation information"
>> +
>> +# Make sure we have the tag_ctl register.
>> +gdb_test "info register tag_ctl" \
>> + "tag_ctl.*$hex.*${::decimal}" \
>> + "tag_ctl is available"
>> +
>> +# Check if the tag granules have the expected values. If they do, that
>> +# means the core file saved the tags properly and GDB has read them
>> +# correctly.
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> + set index [expr [expr 15 - $i] * 16]
>> + set tag [format "%x" $i]
>> + gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
>> + "= 0x$tag" \
>> + "memory tag of &tagged_ptr\[$index\] is correct"
>> +}
>> --
>> 2.25.1
>>
next prev parent reply other threads:[~2021-05-19 11:12 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-05-18 20:20 Luis Machado
2021-05-19 10:01 ` David Spickett
2021-05-19 11:11 ` Luis Machado [this message]
2021-05-19 12:13 ` Eli Zaretskii
2021-05-21 15:12 ` Alan Hayward
2021-05-21 15:30 ` Luis Machado
2021-05-21 17:20 ` John Baldwin
2021-05-24 13:41 ` Luis Machado
2021-05-24 8:07 ` Alan Hayward
2021-05-24 12:45 ` Luis Machado
2021-05-26 14:08 ` [PATCH,v2] " Luis Machado
2021-05-29 3:14 ` Simon Marchi
2021-05-31 14:12 ` Luis Machado
2021-05-31 14:49 ` Simon Marchi
2021-05-31 14:56 ` Luis Machado
2021-05-31 14:15 ` [PATCH,v3][AArch64] " Luis Machado
2021-05-31 16:44 ` [PATCH,v4][AArch64] " Luis Machado
2021-06-01 17:45 ` [PATCH,v5][AArch64] " Luis Machado
2021-06-15 14:10 ` [Ping][PATCH,v5][AArch64] " Luis Machado
2021-06-24 14:00 ` [PATCH,v5][AArch64] " Alan Hayward
2021-06-24 14:37 ` Luis Machado
2021-06-24 15:18 ` Alan Hayward
2021-07-01 13:50 ` [PING][PATCH,v5][AArch64] " Luis Machado
2021-07-11 14:22 ` Joel Brobecker
2021-07-14 13:07 ` Catalin Marinas
2021-07-29 2:26 ` Simon Marchi
2021-07-29 16:03 ` John Baldwin
2021-07-29 18:10 ` Catalin Marinas
2021-07-29 18:20 ` Simon Marchi
2021-08-01 15:44 ` Joel Brobecker
2021-08-02 12:06 ` Luis Machado
2021-07-19 19:05 ` Luis Machado
2021-07-27 16:10 ` Luis Machado
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=fa40d366-87e6-1d42-2604-f3aa13b3374e@linaro.org \
--to=luis.machado@linaro.org \
--cc=alan.hayward@arm.com \
--cc=catalin.marinas@arm.com \
--cc=david.spickett@linaro.org \
--cc=gdb-patches@sourceware.org \
--cc=jhb@freebsd.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).