From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-qv1-xf35.google.com (mail-qv1-xf35.google.com [IPv6:2607:f8b0:4864:20::f35]) by sourceware.org (Postfix) with ESMTPS id 99D043857C5F for ; Mon, 31 May 2021 16:44:28 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org 99D043857C5F Received: by mail-qv1-xf35.google.com with SMTP id eb9so5796838qvb.6 for ; Mon, 31 May 2021 09:44:28 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=+7lFYsFndsxYBDZADEv72mjd8GR+/szbm6cS18P2W7Y=; b=X/Frg7XCHgaGoU/IxbCUttLYcqbgKBhGX6ZxMXfPTow274iwmnA7gxTJGPV/v8IJCp i/0FCtYqYQjURTaC6yCGLHWJxzAZLIWEInVj8d8iYrhtApj9gi8XA7J0L8oHhOXjuuVv lRw3f5jCDV+AdE2NrQzNpAszuJtYGZRdGgxi7fMLrcVdR56Xv+8iBxAypMnf9t6jOeeO +mcRHsMd855c0sQItcKi/wQMestIXd3y3SftD+6hONQ2VjRxC0Es55ZrERe/1i37D9i8 FKg8feNtAv+lTleBqUCkMSNArrFbwsLPPATFh9efTsnhyDcl/J0a+ezOtbn4xF/gOhWf y1Mg== X-Gm-Message-State: AOAM533B04tt5Pz6KQfgHgI1PRQWTSKG9kZ1wFPUT6CXWXayKxEsywP/ g6CgzV5eWtphOtv3jCwdhMuSDIE29yiauA== X-Google-Smtp-Source: ABdhPJwm2Hg+L77J9BCwBneAqcGagiHOuRCLAE0XqJyh5dsbZFaHvTXtwQJlCLzIppcIVuqq8frmwA== X-Received: by 2002:a0c:f792:: with SMTP id s18mr17819183qvn.46.1622479467761; Mon, 31 May 2021 09:44:27 -0700 (PDT) Received: from localhost.localdomain ([2804:7f0:4841:5c9d:d181:e3a7:9333:1f86]) by smtp.gmail.com with ESMTPSA id 15sm1257408qkj.62.2021.05.31.09.44.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 May 2021 09:44:27 -0700 (PDT) From: Luis Machado To: gdb-patches@sourceware.org, simon.marchi@polymtl.ca Cc: alan.hayward@arm.com, jhb@FreeBSD.org, david.spickett@linaro.org, catalin.marinas@arm.com Subject: [PATCH,v4][AArch64] MTE corefile support Date: Mon, 31 May 2021 13:44:20 -0300 Message-Id: <20210531164420.4081735-1-luis.machado@linaro.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210518202047.3492211-1-luis.machado@linaro.org> References: <20210518202047.3492211-1-luis.machado@linaro.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-12.1 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 31 May 2021 16:44:36 -0000 Updates on v4: - Calculate sizes based on individual struct field sizes. Updates on v3: - Addressed review comments. - New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c. - Updated code documentation. - Removed code duplication. Updates on v2: - Reworked core_target::fetch_memtags to handle cases where address + len runs over the NT_MEMTAG note. - Turned a few magic numbers into constants. There is an unfortunate duplication of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is no file generic enough that gets included by both corelow and linux-tdep. - More sanity checks to make sure the note format is correct. - Documented aarch64_linux_decode_memtag_note a little more. --- 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//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//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//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 * Makefile.in (COMMON_SFILES): Add memtag.c. * NEWS: Mention core file support for memory tagging. * aarch64-linux-tdep.c: Include elf/common.h. Include gdbsupport/memtag.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. (NT_MEMTAG_TOTAL_HEADER_SIZE): New constant. * arch/aarch64-mte-linux.h (tag_dump_mte): New struct. (AARCH64_MTE_TAG_BIT_SIZE): New constant. * corelow.c: Include gdbsupport/memtag.h and memtag.h. (core_target) : 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: Include gdbsupport/memtag.h and memtag.h. (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. * memtag.c: New file. * memtag.h: New file. gdb/doc/ChangeLog: YYYY-MM-DD Luis Machado * gdb.texinfo (AArch64 Memory Tagging Extension): Mention support for memory tagging in core files. gdb/testsuite/ChangeLog: YYYY-MM-DD Luis Machado * gdb.arch/aarch64-mte-gcore.c: New file. * gdb.arch/aarch64-mte-gcore.exp: New file. gdbsupport/ChangeLog: YYYY-MM-DD Luis Machado * memtag.h: New file. --- gdb/Makefile.in | 1 + gdb/NEWS | 4 + gdb/aarch64-linux-tdep.c | 221 +++++++++++++++++++ gdb/arch/aarch64-mte-linux.h | 17 ++ gdb/corelow.c | 63 ++++++ gdb/doc/gdb.texinfo | 4 + gdb/gdbarch.c | 64 ++++++ gdb/gdbarch.h | 16 ++ gdb/gdbarch.sh | 6 + gdb/linux-tdep.c | 97 +++++++- gdb/memtag.c | 88 ++++++++ gdb/memtag.h | 46 ++++ gdb/testsuite/gdb.arch/aarch64-mte-gcore.c | 93 ++++++++ gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++ gdbsupport/memtag.h | 39 ++++ 15 files changed, 867 insertions(+), 3 deletions(-) create mode 100644 gdb/memtag.c create mode 100644 gdb/memtag.h create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp create mode 100644 gdbsupport/memtag.h diff --git a/gdb/Makefile.in b/gdb/Makefile.in index bb6c5dfa784..89149c17808 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -1099,6 +1099,7 @@ COMMON_SFILES = \ memattr.c \ memory-map.c \ memrange.c \ + memtag.c \ minidebug.c \ minsyms.c \ mipsread.c \ 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..b4a8d09d5e2 100644 --- a/gdb/aarch64-linux-tdep.c +++ b/gdb/aarch64-linux-tdep.c @@ -52,6 +52,9 @@ #include "value.h" #include "gdbsupport/selftest.h" +#include "gdbsupport/memtag.h" + +#include "elf/common.h" /* Signal frame handling. @@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch, } } +/* Memory tag note header size. Includes both the generic and the + arch-specific parts. */ +#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \ + + NT_MEMTAG_MTE_HEADER_SIZE) + +/* 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 +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. */ + size_t 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 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 (NT_MEMTAG_TOTAL_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 (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 (tag_dump_header::format), + byte_order, NT_MEMTAG_TYPE_AARCH_MTE); + buf += sizeof (tag_dump_header::format); + + /* Start address */ + store_unsigned_integer (buf, sizeof (tag_dump_header::start_vma), + byte_order, start_address); + buf += sizeof (tag_dump_header::start_vma); + + /* End address */ + store_unsigned_integer (buf, sizeof (tag_dump_header::end_vma), + byte_order, end_address); + buf += sizeof (tag_dump_header::end_vma); + + /* MTE-specific header. */ + /* Granule byte size */ + store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size), + byte_order, AARCH64_MTE_GRANULE_SIZE); + buf += sizeof (tag_dump_mte::granule_byte_size); + + /* Tag bit size */ + store_unsigned_integer (buf, sizeof (tag_dump_mte::tag_bit_size), + byte_order, AARCH64_MTE_TAG_BIT_SIZE); + buf += sizeof (tag_dump_mte::tag_bit_size); + + /* Unused value */ + store_unsigned_integer (buf, sizeof (tag_dump_mte::__unused), byte_order, 0); + + /* Store the tags. */ + memcpy (notes[0].data () + NT_MEMTAG_TOTAL_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 requested tags. + + The note is guaranteed to cover the [ADDRESS, ADDRESS + length) + range. */ + +static gdb::byte_vector +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch, + gdb::array_view note, + CORE_ADDR address, size_t length) +{ + gdb::byte_vector tags; + + /* Sanity check. */ + if (note.size () < NT_MEMTAG_TOTAL_HEADER_SIZE) + { + warning (_("Malformed core note - too short for MTE header.\n" + "Expected %s bytes but got %s bytes."), + pulongest (NT_MEMTAG_TOTAL_HEADER_SIZE), + pulongest (note.size ())); + return tags; + } + + /* The amount of memory tag granules we need to fetch. */ + size_t 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; + + /* Read the generic header. */ + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); + const gdb_byte *buf = note.data (); + + unsigned int format + = extract_unsigned_integer (buf, sizeof (tag_dump_header::format), + byte_order); + buf += sizeof (tag_dump_header::format); + + CORE_ADDR start_vma + = extract_unsigned_integer (buf, sizeof (tag_dump_header::start_vma), + byte_order); + buf += sizeof (tag_dump_header::start_vma); + + CORE_ADDR end_vma + = extract_unsigned_integer (buf, sizeof (tag_dump_header::end_vma), + byte_order); + buf += sizeof (tag_dump_header::end_vma); + + /* Validate that ADDRESS + LENGTH doesn't fall outside of this note's + range of addresses. */ + gdb_assert (address + length < end_vma); + + /* Is the tag header format correct for this note? */ + if (format != NT_MEMTAG_TYPE_AARCH_MTE) + { + warning (_("Unexpected memory tag note format.\n" + "Expected %lx but got %x."), NT_MEMTAG_MTE_HEADER_SIZE, + format); + return tags; + } + + size_t expected_tag_bytes = (end_vma - start_vma)/AARCH64_MTE_GRANULE_SIZE; + + /* Does the number of tag bytes in this note match the expected number + of tag bytes the note says it has? */ + if (note.size () < (NT_MEMTAG_TOTAL_HEADER_SIZE + expected_tag_bytes)) + { + warning (_("Unexpected tag data size.\n" + "Expected %s but got %s."), pulongest (expected_tag_bytes), + pulongest (note.size () - NT_MEMTAG_TOTAL_HEADER_SIZE)); + 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 (start_vma, AARCH64_MTE_GRANULE_SIZE); + CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE); + + size_t skipped_granules + = aarch64_mte_get_tag_granules (aligned_start_address, + aligned_address - aligned_start_address, + AARCH64_MTE_GRANULE_SIZE); + + /* Point to the block of data that contains the first granule we are + interested in. */ + const gdb::array_view tags_data + = note.slice (NT_MEMTAG_TOTAL_HEADER_SIZE + skipped_granules, granules); + + /* Read the tag granules. */ + for (size_t 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 +2072,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..7da9de4aefb 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,20 @@ 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); +/* NT_MEMTAG header for MTE tags. */ +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; +}; + +/* Size of the MTE header for a NT_MEMTAG note. */ +#define NT_MEMTAG_MTE_HEADER_SIZE (sizeof (tag_dump_mte::granule_byte_size) \ + + sizeof (tag_dump_mte::tag_bit_size) \ + + sizeof (tag_dump_mte::__unused)) + #endif /* ARCH_AARCH64_LINUX_H */ diff --git a/gdb/corelow.c b/gdb/corelow.c index 452b4dd4f9a..24c5bf29a11 100644 --- a/gdb/corelow.c +++ b/gdb/corelow.c @@ -51,6 +51,8 @@ #include "gdbcmd.h" #include "xml-tdesc.h" #include "observable.h" +#include "gdbsupport/memtag.h" +#include "memtag.h" #ifndef O_LARGEFILE #define O_LARGEFILE 0 @@ -100,6 +102,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 +1124,60 @@ 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. */ + + return (bfd_get_section_by_name (core_bfd, ".memtag") != nullptr); +} + +/* 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)) + error (_("gdbarch_decode_memtag_note not implemented for this " + "architecture.")); + + memtag_note_info info; + info.memtag_section = nullptr; + + while (get_next_core_memtag_section (core_bfd, info.memtag_section, + address, info)) + { + size_t adjusted_length + = (address + len < info.end_address)? len : (info.end_address - address); + + /* Decode the memory tag note and return the tags. */ + gdb::byte_vector tags_read + = gdbarch_decode_memtag_note (gdbarch, info.note, address, + adjusted_length); + + /* Transfer over the tags that have been read. */ + tags.insert (tags.end (), tags_read.begin (), tags_read.end ()); + + /* ADDRESS + LEN may cross the boundaries of a particular NT_MEMTAG + note. Check if we need to fetch tags from a different section. */ + if (address + len < info.end_address) + return true; + + /* There are more tags to fetch. Update ADDRESS and LEN. */ + len -= (info.end_address - address); + address = info.end_address; + } + + 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 90d827a50e7..1b001e6cacb 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 +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 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 (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address); +extern std::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 note, CORE_ADDR address, size_t length); +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view 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;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;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 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..6192cc4421b 100644 --- a/gdb/linux-tdep.c +++ b/gdb/linux-tdep.c @@ -41,6 +41,8 @@ #include "gdbsupport/gdb_optional.h" #include "gcore.h" #include "gcore-elf.h" +#include "gdbsupport/memtag.h" +#include "memtag.h" #include @@ -1438,10 +1440,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 +1476,91 @@ 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; + + memtag_note_info info; + return get_next_core_memtag_section (core_bfd, nullptr, address, info); +} + +/* 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 ¬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 data + = target_fileio_read_stralloc (NULL, smaps_file.c_str ()); + + if (data == nullptr) + return; + + /* Parse the contents of smaps into a vector. */ + std::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 memory_tag_notes + = gdbarch_create_memtag_notes_from_range (gdbarch, + map.start_address, + map.end_address); + /* Write notes to the core file. */ + for (const gdb::byte_vector ¬e : 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 +2139,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/memtag.c b/gdb/memtag.c new file mode 100644 index 00000000000..4d92ecde84a --- /dev/null +++ b/gdb/memtag.c @@ -0,0 +1,88 @@ +/* GDB generic memory tagging functions. + + 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 . */ + +#include "defs.h" +#include "memtag.h" +#include "gdbsupport/memtag.h" +#include "bfd.h" + +/* Helper function to walk through NT_MEMTAG notes in a core file. + + Return a pointer to a .memtag section containing ADDRESS or nullptr + of none are found. + + If SECTION is provided, search from that section onwards. */ + +bool +get_next_core_memtag_section (bfd *abfd, asection *section, + CORE_ADDR address, memtag_note_info &info) +{ + /* If SECTION is nullptr, start a fresh lookup. */ + if (section == nullptr) + section = bfd_get_section_by_name (abfd, ".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 < NT_MEMTAG_GENERIC_HEADER_SIZE) + { + warning (_("Malformed core note - too short for NT_MEMTAG generic " + "header.\n" + "Expected %s bytes but got %s bytes."), + pulongest (NT_MEMTAG_GENERIC_HEADER_SIZE), + pulongest (note_size)); + return false; + } + + gdb::byte_vector note (note_size); + + /* Fetch the contents of this particular memtag note. */ + if (!bfd_get_section_contents (abfd, section, + note.data (), 0, note_size)) + { + warning (_("could not get core note contents.")); + return false; + } + + /* Read the generic header of the note. It contains the format, + start address and end address. */ + uint64_t start_address + = bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format)); + uint64_t end_address + = bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format) + + sizeof (tag_dump_header::start_vma)); + + /* Is the address within [start_address, end_address)? */ + if (address >= start_address + && address < end_address) + { + info.start_address = start_address; + info.end_address = end_address; + info.note = note; + info.memtag_section = section; + return true; + } + } + return false; +} diff --git a/gdb/memtag.h b/gdb/memtag.h new file mode 100644 index 00000000000..43c9efb39a3 --- /dev/null +++ b/gdb/memtag.h @@ -0,0 +1,46 @@ +/* GDB generic memory tagging definitions. + 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 . */ + +#ifndef MEMTAG_H +#define MEMTAG_H + +#include "bfd.h" + +struct memtag_note_info +{ + CORE_ADDR start_address; + CORE_ADDR end_address; + gdb::byte_vector note; + asection *memtag_section; +}; + +/* Helper function to walk through NT_MEMTAG notes in a core file. + + Return TRUE if there is a .memtag section containing ADDRESS. Return FALSE + otherwise. + + If SECTION is provided, search from that section onwards. If SECTION is + nullptr, then start a new search. + + If a .memtag section containing ADDRESS is found, fill INFO with data + about such section. Otherwise leave it unchanged. */ + +bool get_next_core_memtag_section (bfd *abfd, asection *section, + CORE_ADDR address, memtag_note_info &info); + +#endif /* MEMTAG_H */ 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 . */ + +/* 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 +#include +#include +#include +#include +#include +#include + +/* 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..d0bcd036972 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp @@ -0,0 +1,111 @@ +# 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 . + +# 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 $binfile + +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" +} diff --git a/gdbsupport/memtag.h b/gdbsupport/memtag.h new file mode 100644 index 00000000000..bb47eed220b --- /dev/null +++ b/gdbsupport/memtag.h @@ -0,0 +1,39 @@ +/* Generic memory tagging definitions. + 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 . */ + +#ifndef GDBSUPPORT_MEMTAG_H +#define GDBSUPPORT_MEMTAG_H + +/* 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; +}; + +/* Size of the generic header for the NT_MEMTAG note. This is OS-independent + and should be shared with OS-specific and arch-specific code. */ +#define NT_MEMTAG_GENERIC_HEADER_SIZE (sizeof (tag_dump_header::format) \ + + sizeof (tag_dump_header::start_vma) \ + + sizeof (tag_dump_header::end_vma)) + +#endif /* GDBSUPPORT_MEMTAG_H */ -- 2.25.1