Browse Source
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)users/aburgess/try-mips-disasm-styling
17 changed files with 1048 additions and 8 deletions
@ -0,0 +1,68 @@ |
|||
/* GDB generic memory tagging functions.
|
|||
|
|||
Copyright (C) 2022 Free Software Foundation, Inc. |
|||
|
|||
This file is part of GDB. |
|||
|
|||
This program is free software; you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation; either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
This program is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|||
|
|||
#include "defs.h" |
|||
#include "memtag.h" |
|||
#include "bfd.h" |
|||
|
|||
/* See memtag.h */ |
|||
|
|||
bool |
|||
get_next_core_memtag_section (bfd *abfd, asection *section, |
|||
CORE_ADDR address, memtag_section_info &info) |
|||
{ |
|||
/* If the caller provided no SECTION to start from, search from the
|
|||
beginning. */ |
|||
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 memtag_range_size = section->rawsize; |
|||
size_t tags_size = bfd_section_size (section); |
|||
|
|||
/* Empty memory range or empty tag dump should not happen. Warn about
|
|||
it but keep going through the sections. */ |
|||
if (memtag_range_size == 0 || tags_size == 0) |
|||
{ |
|||
warning (_("Found memtag section with empty memory " |
|||
"range or empty tag dump")); |
|||
continue; |
|||
} |
|||
else |
|||
{ |
|||
CORE_ADDR start_address = bfd_section_vma (section); |
|||
CORE_ADDR end_address = start_address + memtag_range_size; |
|||
|
|||
/* 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.memtag_section = section; |
|||
return true; |
|||
} |
|||
} |
|||
section = bfd_get_next_section_by_name (abfd, section); |
|||
} |
|||
return false; |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
/* GDB generic memory tagging definitions.
|
|||
Copyright (C) 2022 Free Software Foundation, Inc. |
|||
|
|||
This file is part of GDB. |
|||
|
|||
This program is free software; you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation; either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
This program is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|||
|
|||
#ifndef MEMTAG_H |
|||
#define MEMTAG_H |
|||
|
|||
#include "bfd.h" |
|||
|
|||
struct memtag_section_info |
|||
{ |
|||
/* The start address of the tagged memory range. */ |
|||
CORE_ADDR start_address; |
|||
/* The final address of the tagged memory range. */ |
|||
CORE_ADDR end_address; |
|||
/* The section containing tags for the memory range
|
|||
[start_address, end_address). */ |
|||
asection *memtag_section; |
|||
}; |
|||
|
|||
/* Helper function to walk through memory tag sections 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_section_info &info); |
|||
|
|||
#endif /* MEMTAG_H */ |
|||
@ -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; |
|||
} |
|||
@ -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 |
|||
} |
|||
Loading…
Reference in new issue