diff options
author | Luis Machado <luis.machado@arm.com> | 2022-03-31 11:42:35 +0100 |
---|---|---|
committer | Luis Machado <luis.machado@arm.com> | 2022-07-19 15:24:31 +0100 |
commit | 68cffbbd4406b4efe1aa6e18460b1d7ca02549f1 (patch) | |
tree | f8a61526011db5bf0c60314f38de6fc48cd82ca0 /gdb/testsuite | |
parent | d0ff5ca959df91dcef16ec57154ff199fad5a4e4 (diff) | |
download | gdb-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.c | 152 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/aarch64-mte-core.exp | 175 |
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 +} |