aboutsummaryrefslogtreecommitdiff
path: root/gdb/testsuite
diff options
context:
space:
mode:
authorLuis Machado <luis.machado@arm.com>2022-03-31 11:42:35 +0100
committerLuis Machado <luis.machado@arm.com>2022-07-19 15:24:31 +0100
commit68cffbbd4406b4efe1aa6e18460b1d7ca02549f1 (patch)
treef8a61526011db5bf0c60314f38de6fc48cd82ca0 /gdb/testsuite
parentd0ff5ca959df91dcef16ec57154ff199fad5a4e4 (diff)
downloadgdb-68cffbbd4406b4efe1aa6e18460b1d7ca02549f1.zip
gdb-68cffbbd4406b4efe1aa6e18460b1d7ca02549f1.tar.gz
gdb-68cffbbd4406b4efe1aa6e18460b1d7ca02549f1.tar.bz2
[AArch64] MTE corefile support
Teach GDB how to dump memory tags for AArch64 when using the gcore command and how to read memory tag data back from a core file generated by GDB (via gcore) or by the Linux kernel. The format is documented in the Linux Kernel documentation [1]. Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is created for each of those segments when reading the core file back. To save a little bit of space, given MTE tags only take 4 bits, the memory tags are stored packed as 2 tags per byte. When reading the data back, the tags are unpacked. I've added a new testcase to exercise the feature. Build-tested with --enable-targets=all and regression tested on aarch64-linux Ubuntu 20.04. [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump Support)
Diffstat (limited to 'gdb/testsuite')
-rw-r--r--gdb/testsuite/gdb.arch/aarch64-mte-core.c152
-rw-r--r--gdb/testsuite/gdb.arch/aarch64-mte-core.exp175
2 files changed, 327 insertions, 0 deletions
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-core.c b/gdb/testsuite/gdb.arch/aarch64-mte-core.c
new file mode 100644
index 0000000..4e9a6e3
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-core.c
@@ -0,0 +1,152 @@
+/* This test program is part of GDB, the GNU debugger.
+
+ Copyright 2022 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 corefile support. We allocate
+ multiple memory mappings with PROT_MTE and assign tag values for all the
+ existing MTE granules. */
+
+/* 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 */
+#ifndef HWCAP2_MTE
+#define HWCAP2_MTE (1 << 18)
+#endif
+
+/* From arch/arm64/include/uapi/asm/mman.h */
+#ifndef PROT_MTE
+#define PROT_MTE 0x20
+#endif
+
+#ifndef PR_SET_TAGGED_ADDR_CTRL
+#define PR_SET_TAGGED_ADDR_CTRL 55
+#define PR_TAGGED_ADDR_ENABLE (1UL << 0)
+#endif
+
+/* From include/uapi/linux/prctl.h */
+#ifndef PR_MTE_TCF_SHIFT
+#define PR_MTE_TCF_SHIFT 1
+#define PR_MTE_TCF_SYNC (1UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TCF_ASYNC (2UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TAG_SHIFT 3
+#define PR_MTE_TAG_MASK (0xffffUL << PR_MTE_TAG_SHIFT)
+#endif
+
+#ifdef ASYNC
+#define TCF_MODE PR_MTE_TCF_ASYNC
+#else
+#define TCF_MODE PR_MTE_TCF_SYNC
+#endif
+
+#define NMAPS 5
+
+/* We store the pointers and sizes of the memory maps we requested. Each
+ of them has a different size. */
+unsigned char *mmap_pointers[NMAPS];
+
+/* Set the allocation tag on the destination address. */
+#define set_tag(tagged_addr) do { \
+ asm volatile("stg %0, [%0]" : : "r" (tagged_addr) : "memory"); \
+} while (0)
+
+
+uintptr_t
+set_logical_tag (uintptr_t ptr, unsigned char tag)
+{
+ ptr &= ~0xFF00000000000000ULL;
+ ptr |= ((uintptr_t) tag << 56);
+ return ptr;
+}
+
+void
+fill_map_with_tags (unsigned char *ptr, size_t size, unsigned char *tag)
+{
+ for (size_t start = 0; start < size; start += 16)
+ {
+ set_tag (set_logical_tag (((uintptr_t)ptr + start) & ~(0xFULL), *tag));
+ *tag = (*tag + 1) % 16;
+ }
+}
+
+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 | TCF_MODE
+ | (0xfffe << PR_MTE_TAG_SHIFT),
+ 0, 0, 0))
+ {
+ perror ("prctl () failed");
+ return 1;
+ }
+
+ /* Map a big area of NMAPS * 2 pages. */
+ unsigned char *big_map = mmap (0, NMAPS * 2 * page_sz, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ if (big_map == MAP_FAILED)
+ {
+ perror ("mmap () failed");
+ return 1;
+ }
+
+ /* Start with a tag of 0x1 so we can crash later. */
+ unsigned char tag = 1;
+
+ /* From that big area of NMAPS * 2 pages, go through each page and protect
+ alternating pages. This should prevent the kernel from merging different
+ mmap's and force the creation of multiple individual MTE-protected entries
+ in /proc/<pid>/smaps. */
+ for (int i = 0; i < NMAPS; i++)
+ {
+ mmap_pointers[i] = big_map + (i * 2 * page_sz);
+
+ /* Enable MTE on alternating pages. */
+ if (mprotect (mmap_pointers[i], page_sz,
+ PROT_READ | PROT_WRITE | PROT_MTE))
+ {
+ perror ("mprotect () failed");
+ return 1;
+ }
+
+ fill_map_with_tags (mmap_pointers[i], page_sz, &tag);
+ }
+
+ /* The following line causes a crash on purpose. */
+ *mmap_pointers[0] = 0x4;
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-core.exp b/gdb/testsuite/gdb.arch/aarch64-mte-core.exp
new file mode 100644
index 0000000..904364d
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-core.exp
@@ -0,0 +1,175 @@
+# Copyright (C) 2018-2022 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.
+
+proc test_mte_core_file { core_filename mode } {
+ # Load the core file and make sure we see the tag violation fault
+ # information.
+ if {$mode == "sync"} {
+ 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 ${::hex}" \
+ "Logical tag ${::hex}\." \
+ "#0.*${::hex} in main \\(.*\\) at .*" \
+ ".*mmap_pointers\\\[0\\\] = 0x4;"] \
+ "core file shows $mode memory tag violation"
+ } else {
+ gdb_test "core $core_filename" \
+ [multi_line \
+ "Core was generated by.*\." \
+ "Program terminated with signal SIGSEGV, Segmentation fault" \
+ "Memory tag violation" \
+ "Fault address unavailable\." \
+ "#0 ${::hex} in .* from .*"] \
+ "core file shows $mode memory tag violation"
+ }
+
+ # Make sure we have the tag_ctl register.
+ gdb_test "info register tag_ctl" \
+ "tag_ctl.*${::hex}.*${::decimal}" \
+ "tag_ctl is available"
+
+ # In ASYNC mode, there is nothing left to test, as the program stops at
+ # a place where further source code inspection is not possible.
+ if {$mode == "async"} {
+ return
+ }
+
+ # First, figure out the page size.
+ set page_size [get_valueof "" "page_sz" "0" \
+ "fetch value of page size"]
+
+ # Get the number of maps for the test
+ set nmaps [get_valueof "" "NMAPS" "0" \
+ "fetch number of maps"]
+ set tag 1
+
+ # Iterate over all of the MTE-protected memory mappings and make sure
+ # GDB retrieves the correct allocation tags for each one. If the tag
+ # has the expected value, that means the core file was generated correctly
+ # and that GDB read the contents correctly.
+ for {set i 0} {$i < $nmaps} {incr i} {
+ for {set offset 0} {$offset < $page_size} {set offset [expr $offset + 16]} {
+ set hex_tag [format "%x" $tag]
+ gdb_test "memory-tag print-allocation-tag mmap_pointers\[$i\] + $offset" \
+ "= 0x$hex_tag" \
+ "mmap_ponters\[$i\] + $offset contains expected tag"
+ # Update the expected tag. The test writes tags in sequential
+ # order.
+ set tag [expr ($tag + 1) % 16]
+ }
+ }
+}
+
+# Exercise MTE corefile support using mode MODE (Async or Sync)
+
+proc test_mode { mode } {
+
+ set compile_flags {"debug" "macros" "additional_flags=-march=armv8.5-a+memtag"}
+
+ # If we are testing async mode, we need to force the testcase to use
+ # such mode.
+ if {$mode == "async"} {
+ lappend compile_flags "additional_flags=-DASYNC"
+ }
+
+ standard_testfile
+ set executable "${::testfile}-${mode}"
+ if {[prepare_for_testing "failed to prepare" ${executable} ${::srcfile} ${compile_flags}]} {
+ return -1
+ }
+ set binfile [standard_output_file ${executable}]
+
+ 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
+ }
+
+ # Run until a crash and confirm GDB displays memory tag violation
+ # information.
+ if {$mode == "sync"} {
+ gdb_test "continue" \
+ [multi_line \
+ "Program received signal SIGSEGV, Segmentation fault" \
+ "Memory tag violation while accessing address ${::hex}" \
+ "Allocation tag 0x1" \
+ "Logical tag 0x0\." \
+ "${::hex} in main \\(.*\\) at .*" \
+ ".*mmap_pointers\\\[0\\\] = 0x4;"] \
+ "run to memory $mode tag violation"
+ } else {
+ gdb_test "continue" \
+ [multi_line \
+ "Program received signal SIGSEGV, Segmentation fault" \
+ "Memory tag violation" \
+ "Fault address unavailable\." \
+ "${::hex} in .* from .*"] \
+ "run to memory $mode tag violation"
+ }
+
+ # Generate the gcore core file.
+ set gcore_filename [standard_output_file "${executable}.gcore"]
+ set gcore_generated [gdb_gcore_cmd "$gcore_filename" "generate gcore file"]
+
+ # Generate a native core file.
+ set core_filename [core_find ${binfile}]
+ set core_generated [expr {$core_filename != ""}]
+
+ # At this point we have a couple core files, the gcore one generated by GDB
+ # and the native one generated by the Linux Kernel. Make sure GDB can read
+ # both correctly.
+
+ if {$gcore_generated} {
+ clean_restart ${binfile}
+ with_test_prefix "gcore corefile" {
+ test_mte_core_file $gcore_filename $mode
+ }
+ } else {
+ fail "gcore corefile not generated"
+ }
+
+ if {$core_generated} {
+ clean_restart ${binfile}
+ with_test_prefix "native corefile" {
+ test_mte_core_file $core_filename $mode
+ }
+ } else {
+ untested "native corefile not generated"
+ }
+
+}
+
+if {![is_aarch64_target]} {
+ verbose "Skipping ${gdb_test_file_name}."
+ return
+}
+
+# Run tests
+foreach_with_prefix mode {"sync" "async"} {
+ test_mode $mode
+}