[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)
This commit is contained in:
Luis Machado
2022-03-31 11:42:35 +01:00
parent d0ff5ca959
commit 68cffbbd44
17 changed files with 1048 additions and 8 deletions

View File

@ -1122,6 +1122,7 @@ COMMON_SFILES = \
memattr.c \
memory-map.c \
memrange.c \
memtag.c \
minidebug.c \
minsyms.c \
mipsread.c \

View File

@ -3,6 +3,16 @@
*** Changes since GDB 12
* 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.
When a process uses memory-mapped pages protected by memory tags (for
example, AArch64 MTE), this additional information will be recorded in
the core file in the event of a crash or if GDB generates a core file
from the current process state. GDB will show this additional information
automatically, or through one of the memory-tag subcommands.
* "info breakpoints" now displays enabled breakpoint locations of
disabled breakpoints as in the "y-" state. For example:

View File

@ -53,6 +53,9 @@
#include "gdbsupport/selftest.h"
#include "elf/common.h"
#include "elf/aarch64.h"
/* Signal frame handling.
+------------+ ^
@ -1805,6 +1808,159 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
}
}
/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook. */
static asection *
aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
CORE_ADDR address, size_t size)
{
gdb_assert (obfd != nullptr);
gdb_assert (size > 0);
/* Create the section and associated program header.
Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
refuse to write data to this section. */
asection *mte_section
= bfd_make_section_anyway_with_flags (obfd, "memtag", SEC_HAS_CONTENTS);
if (mte_section == nullptr)
return nullptr;
bfd_set_section_vma (mte_section, address);
/* The size of the memory range covered by the memory tags. We reuse the
section's rawsize field for this purpose. */
mte_section->rawsize = size;
/* Fetch the number of tags we need to save. */
size_t tags_count
= aarch64_mte_get_tag_granules (address, size, AARCH64_MTE_GRANULE_SIZE);
/* Tags are stored packed as 2 tags per byte. */
bfd_set_section_size (mte_section, (tags_count + 1) >> 1);
/* Store program header information. */
bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
&mte_section);
return mte_section;
}
/* Maximum number of tags to request. */
#define MAX_TAGS_TO_TRANSFER 1024
/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook. */
static bool
aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
{
/* We only handle MTE tags for now. */
size_t segment_size = osec->rawsize;
CORE_ADDR start_address = bfd_section_vma (osec);
CORE_ADDR end_address = start_address + segment_size;
/* Figure out how many tags we need to store in this memory range. */
size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
AARCH64_MTE_GRANULE_SIZE);
/* If there are no tag granules to fetch, just return. */
if (granules == 0)
return true;
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)));
return false;
}
/* 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;
}
/* Pack the MTE tag bits. */
aarch64_mte_pack_tags (tags);
if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
0, tags.size ()))
{
warning (_("Failed to write %s bytes of corefile memory "
"tag content (%s)."),
pulongest (tags.size ()),
bfd_errmsg (bfd_get_error ()));
}
return true;
}
/* AArch64 Linux implementation of the gdbarch_decode_memtag_section
hook. Decode a memory tag section and return the requested tags.
The section is guaranteed to cover the [ADDRESS, ADDRESS + length)
range. */
static gdb::byte_vector
aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
bfd_section *section,
int type,
CORE_ADDR address, size_t length)
{
gdb_assert (section != nullptr);
/* The requested address must not be less than section->vma. */
gdb_assert (section->vma <= address);
/* Figure out how many tags we need to fetch in this memory range. */
size_t granules = aarch64_mte_get_tag_granules (address, length,
AARCH64_MTE_GRANULE_SIZE);
/* Sanity check. */
gdb_assert (granules > 0);
/* Fetch the total number of tags in the range [VMA, address + length). */
size_t granules_from_vma
= aarch64_mte_get_tag_granules (section->vma,
address - section->vma + length,
AARCH64_MTE_GRANULE_SIZE);
/* Adjust the tags vector to contain the exact number of packed bytes. */
gdb::byte_vector tags (((granules - 1) >> 1) + 1);
/* Figure out the starting offset into the packed tags data. */
file_ptr offset = ((granules_from_vma - granules) >> 1);
if (!bfd_get_section_contents (section->owner, section, tags.data (),
offset, tags.size ()))
error (_("Couldn't read contents from memtag section."));
/* At this point, the tags are packed 2 per byte. Unpack them before
returning. */
bool skip_first = ((granules_from_vma - granules) % 2) != 0;
aarch64_mte_unpack_tags (tags, skip_first);
/* Resize to the exact number of tags that was requested. */
tags.resize (granules);
return tags;
}
static void
aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
{
@ -1888,6 +2044,21 @@ 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 a memory tag section for a particular
PT_LOAD segment. */
set_gdbarch_create_memtag_section
(gdbarch, aarch64_linux_create_memtag_section);
/* Core file helper to fill a memory tag section with tag data. */
set_gdbarch_fill_memtag_section
(gdbarch, aarch64_linux_fill_memtag_section);
/* Core file helper to decode a memory tag section. */
set_gdbarch_decode_memtag_section (gdbarch,
aarch64_linux_decode_memtag_section);
}
/* Initialize the aarch64_linux_record_tdep. */

View File

@ -21,6 +21,62 @@
/* See arch/aarch64-mte-linux.h */
void
aarch64_mte_pack_tags (gdb::byte_vector &tags)
{
/* Nothing to pack? */
if (tags.empty ())
return;
/* If the tags vector has an odd number of elements, add another
zeroed-out element to make it even. This facilitates packing. */
if ((tags.size () % 2) != 0)
tags.emplace_back (0);
for (int unpacked = 0, packed = 0; unpacked < tags.size ();
unpacked += 2, packed++)
tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
/* Now we have half the size. */
tags.resize (tags.size () / 2);
}
/* See arch/aarch64-mte-linux.h */
void
aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
{
/* Nothing to unpack? */
if (tags.empty ())
return;
/* An unpacked MTE tags vector will have twice the number of elements
compared to an unpacked one. */
gdb::byte_vector unpacked_tags (tags.size () * 2);
int unpacked = 0, packed = 0;
if (skip_first)
{
/* We are not interested in the first unpacked element, just discard
it. */
unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
unpacked++;
packed++;
}
for (; packed < tags.size (); unpacked += 2, packed++)
{
unpacked_tags[unpacked] = tags[packed] & 0xf;
unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
}
/* Update the original tags vector. */
tags = std::move (unpacked_tags);
}
/* See arch/aarch64-mte-linux.h */
size_t
aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
{

View File

@ -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,13 @@ 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);
/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
2 tags per byte and resize the vector. */
extern void aarch64_mte_pack_tags (gdb::byte_vector &tags);
/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
1 tag per byte and resize the vector. If SKIP_FIRST is TRUE, skip the
first unpacked element. Otherwise leave it in the unpacked vector. */
extern void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
#endif /* ARCH_AARCH64_MTE_LINUX_H */

View File

@ -52,6 +52,7 @@
#include <unordered_set>
#include "gdbcmd.h"
#include "xml-tdesc.h"
#include "memtag.h"
#ifndef O_LARGEFILE
#define O_LARGEFILE 0
@ -101,6 +102,13 @@ public:
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. */
@ -1177,6 +1185,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 sections. 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_section_p (gdbarch))
error (_("gdbarch_decode_memtag_section not implemented for this "
"architecture."));
memtag_section_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_section (gdbarch, info.memtag_section, type,
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 memory tag
segment. Check if we need to fetch tags from a different section. */
if (!tags_read.empty () && (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. */

View File

@ -345,7 +345,8 @@ extern const char *pc_prefix (CORE_ADDR);
typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned long size,
int read, int write, int exec,
int modified, void *data);
int modified, bool memory_tagged,
void *data);
/* * Possible lvalue types. Like enum language, this should be in
value.h, but needs to be here for the same reason. */

View File

@ -25765,6 +25765,25 @@ 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.
When a process uses memory-mapped pages protected by memory tags (for
example, AArch64 MTE), this additional information will be recorded in
the core file in the event of a crash or if @value{GDBN} generates a core file
from the current process state.
The memory tag data will be used so developers can display the memory
tags from a particular memory region (using the @samp{m} modifier to the
@command{x} command, using the @command{print} command or using the various
@command{memory-tag} subcommands.
In the case of a crash, @value{GDBN} will attempt to retrieve the memory tag
information automatically from the core file, and will show one of the above
messages depending on whether the synchronous or asynchronous mode is selected.
@xref{Memory Tagging}. @xref{Memory}.
@node i386
@subsection x86 Architecture-specific Issues

View File

@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
int p_flags = 0;
int p_type = 0;
/* Memory tag segments have already been handled by the architecture, as
those contain arch-specific information. If we have one of those, just
return. */
if (startswith (bfd_section_name (osec), "memtag"))
return;
/* FIXME: these constants may only be applicable for ELF. */
if (startswith (bfd_section_name (osec), "load"))
p_type = PT_LOAD;
@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
static int
gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
int write, int exec, int modified, void *data)
int write, int exec, int modified, bool memory_tagged,
void *data)
{
bfd *obfd = (bfd *) data;
asection *osec;
@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
return 0;
}
/* gdbarch_find_memory_region callback for creating a memory tag section.
DATA is 'bfd *' for the core file GDB is creating. */
static int
gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned long size,
int read, int write, int exec,
int modified, bool memory_tagged,
void *data)
{
/* Are there memory tags in this particular memory map entry? */
if (!memory_tagged)
return 0;
bfd *obfd = (bfd *) data;
/* Ask the architecture to create a memory tag section for this particular
memory map entry. It will be populated with contents later, as we can't
start writing the contents before we have all the sections sorted out. */
asection *memtag_section
= gdbarch_create_memtag_section (target_gdbarch (), obfd, vaddr, size);
if (memtag_section == nullptr)
{
warning (_("Couldn't make gcore memory tag segment: %s"),
bfd_errmsg (bfd_get_error ()));
return 1;
}
if (info_verbose)
{
gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes "
"at %s\n",
plongest (bfd_section_size (memtag_section)),
paddress (target_gdbarch (), vaddr));
}
return 0;
}
int
objfile_find_memory_regions (struct target_ops *self,
find_memory_region_ftype func, void *obfd)
@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops *self,
(flags & SEC_READONLY) == 0, /* Writable. */
(flags & SEC_CODE) != 0, /* Executable. */
1, /* MODIFIED is unknown, pass it as true. */
false, /* No memory tags in the object file. */
obfd);
if (ret != 0)
return ret;
@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops *self,
1, /* Stack section will be writable. */
0, /* Stack section will not be executable. */
1, /* Stack section will be modified. */
false, /* No memory tags in the object file. */
obfd);
/* Make a heap segment. */
@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops *self,
1, /* Heap section will be writable. */
0, /* Heap section will not be executable. */
1, /* Heap section will be modified. */
false, /* No memory tags in the object file. */
obfd);
return 0;
@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection *osec)
}
}
/* Callback to copy contents to a particular memory tag section. */
static void
gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
{
/* We are only interested in "memtag" sections. */
if (!startswith (bfd_section_name (osec), "memtag"))
return;
/* Fill the section with memory tag contents. */
if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
error (_("Failed to fill memory tag section for core file."));
}
static int
gcore_memory_sections (bfd *obfd)
{
@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
return 0; /* FIXME: error return/msg? */
}
/* Take care of dumping memory tags, if there are any. */
if (!gdbarch_find_memory_regions_p (target_gdbarch ())
|| gdbarch_find_memory_regions (target_gdbarch (),
gcore_create_memtag_section_callback,
obfd) != 0)
{
if (target_find_memory_regions (gcore_create_memtag_section_callback,
obfd) != 0)
return 0;
}
/* Record phdrs for section-to-segment mapping. */
for (asection *sect : gdb_bfd_sections (obfd))
make_output_phdrs (obfd, sect);
/* Copy memory region contents. */
/* Copy memory region and memory tag contents. */
for (asection *sect : gdb_bfd_sections (obfd))
{
gcore_copy_callback (obfd, sect);
gcore_copy_memtag_section_callback (obfd, sect);
}
return 1;
}

View File

@ -1522,6 +1522,41 @@ Find core file memory regions
invalid=True,
)
Method(
comment="""
Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file
""",
type="asection *",
name="create_memtag_section",
params=[("bfd *", "obfd"), ("CORE_ADDR", "address"), ("size_t", "size")],
predicate=True,
invalid=True,
)
Method(
comment="""
Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data
""",
type="bool",
name="fill_memtag_section",
params=[("asection *", "osec")],
predicate=True,
invalid=True,
)
Method(
comment="""
Decode a memory tag SECTION and return the tags of type TYPE contained in
the memory range [ADDRESS, ADDRESS + LENGTH).
If no tags were found, return an empty vector.
""",
type="gdb::byte_vector",
name="decode_memtag_section",
params=[("bfd_section *", "section"), ("int", "type"), ("CORE_ADDR", "address"), ("size_t", "length")],
predicate=True,
invalid=True,
)
Method(
comment="""
Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from

View File

@ -874,6 +874,32 @@ 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);
/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file */
extern bool gdbarch_create_memtag_section_p (struct gdbarch *gdbarch);
typedef asection * (gdbarch_create_memtag_section_ftype) (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
extern asection * gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
extern void set_gdbarch_create_memtag_section (struct gdbarch *gdbarch, gdbarch_create_memtag_section_ftype *create_memtag_section);
/* Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data */
extern bool gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch);
typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch *gdbarch, asection *osec);
extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec);
extern void set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
/* Decode a memory tag SECTION and return the tags of type TYPE contained in
the memory range [ADDRESS, ADDRESS + LENGTH).
If no tags were found, return an empty vector. */
extern bool gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch);
typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype) (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
extern gdb::byte_vector gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
extern void set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch, gdbarch_decode_memtag_section_ftype *decode_memtag_section);
/* 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).

View File

@ -171,6 +171,9 @@ 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_section_ftype *create_memtag_section;
gdbarch_fill_memtag_section_ftype *fill_memtag_section;
gdbarch_decode_memtag_section_ftype *decode_memtag_section;
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;
@ -527,6 +530,9 @@ 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_section, has predicate. */
/* Skip verify of fill_memtag_section, has predicate. */
/* Skip verify of decode_memtag_section, 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. */
@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
gdb_printf (file,
"gdbarch_dump: find_memory_regions = <%s>\n",
host_address_to_string (gdbarch->find_memory_regions));
gdb_printf (file,
"gdbarch_dump: gdbarch_create_memtag_section_p() = %d\n",
gdbarch_create_memtag_section_p (gdbarch));
gdb_printf (file,
"gdbarch_dump: create_memtag_section = <%s>\n",
host_address_to_string (gdbarch->create_memtag_section));
gdb_printf (file,
"gdbarch_dump: gdbarch_fill_memtag_section_p() = %d\n",
gdbarch_fill_memtag_section_p (gdbarch));
gdb_printf (file,
"gdbarch_dump: fill_memtag_section = <%s>\n",
host_address_to_string (gdbarch->fill_memtag_section));
gdb_printf (file,
"gdbarch_dump: gdbarch_decode_memtag_section_p() = %d\n",
gdbarch_decode_memtag_section_p (gdbarch));
gdb_printf (file,
"gdbarch_dump: decode_memtag_section = <%s>\n",
host_address_to_string (gdbarch->decode_memtag_section));
gdb_printf (file,
"gdbarch_dump: gdbarch_core_xfer_shared_libraries_p() = %d\n",
gdbarch_core_xfer_shared_libraries_p (gdbarch));
@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
gdbarch->find_memory_regions = find_memory_regions;
}
bool
gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
{
gdb_assert (gdbarch != NULL);
return gdbarch->create_memtag_section != NULL;
}
asection *
gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size)
{
gdb_assert (gdbarch != NULL);
gdb_assert (gdbarch->create_memtag_section != NULL);
if (gdbarch_debug >= 2)
gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section called\n");
return gdbarch->create_memtag_section (gdbarch, obfd, address, size);
}
void
set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
gdbarch_create_memtag_section_ftype create_memtag_section)
{
gdbarch->create_memtag_section = create_memtag_section;
}
bool
gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
{
gdb_assert (gdbarch != NULL);
return gdbarch->fill_memtag_section != NULL;
}
bool
gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
{
gdb_assert (gdbarch != NULL);
gdb_assert (gdbarch->fill_memtag_section != NULL);
if (gdbarch_debug >= 2)
gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section called\n");
return gdbarch->fill_memtag_section (gdbarch, osec);
}
void
set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
gdbarch_fill_memtag_section_ftype fill_memtag_section)
{
gdbarch->fill_memtag_section = fill_memtag_section;
}
bool
gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
{
gdb_assert (gdbarch != NULL);
return gdbarch->decode_memtag_section != NULL;
}
gdb::byte_vector
gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length)
{
gdb_assert (gdbarch != NULL);
gdb_assert (gdbarch->decode_memtag_section != NULL);
if (gdbarch_debug >= 2)
gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section called\n");
return gdbarch->decode_memtag_section (gdbarch, section, type, address, length);
}
void
set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
gdbarch_decode_memtag_section_ftype decode_memtag_section)
{
gdbarch->decode_memtag_section = decode_memtag_section;
}
bool
gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
{

View File

@ -42,6 +42,7 @@
#include "gcore.h"
#include "gcore-elf.h"
#include "solib-svr4.h"
#include "memtag.h"
#include <ctype.h>
#include <unordered_map>
@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size,
ULONGEST offset, ULONGEST inode,
int read, int write,
int exec, int modified,
bool memory_tagged,
const char *filename,
void *data);
@ -1470,10 +1472,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;
@ -1505,6 +1508,30 @@ 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_section_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);
}
/* List memory regions in the inferior for a corefile. */
static int
@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
map.offset, map.inode, map.read, map.write, map.exec,
1, /* MODIFIED is true because we want to dump
the mapping. */
map.vmflags.memory_tagging != 0,
map.filename.c_str (), obfd);
}
}
@ -1621,12 +1649,14 @@ static int
linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
ULONGEST offset, ULONGEST inode,
int read, int write, int exec, int modified,
bool memory_tagged,
const char *filename, void *arg)
{
struct linux_find_memory_regions_data *data
= (struct linux_find_memory_regions_data *) arg;
return data->func (vaddr, size, read, write, exec, modified, data->obfd);
return data->func (vaddr, size, read, write, exec, modified, memory_tagged,
data->obfd);
}
/* A variant of linux_find_memory_regions_full that is suitable as the
@ -1675,6 +1705,7 @@ static int
linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
ULONGEST offset, ULONGEST inode,
int read, int write, int exec, int modified,
bool memory_tagged,
const char *filename, void *data)
{
struct linux_make_mappings_data *map_data

68
gdb/memtag.c Normal file
View File

@ -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;
}

50
gdb/memtag.h Normal file
View File

@ -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 */

View File

@ -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;
}

View File

@ -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
}