mirror of
https://github.com/espressif/binutils-gdb.git
synced 2025-06-29 16:38:05 +08:00
[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:
@ -1122,6 +1122,7 @@ COMMON_SFILES = \
|
||||
memattr.c \
|
||||
memory-map.c \
|
||||
memrange.c \
|
||||
memtag.c \
|
||||
minidebug.c \
|
||||
minsyms.c \
|
||||
mipsread.c \
|
||||
|
10
gdb/NEWS
10
gdb/NEWS
@ -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:
|
||||
|
||||
|
@ -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. */
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 */
|
||||
|
@ -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. */
|
||||
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
||||
|
81
gdb/gcore.c
81
gdb/gcore.c
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
68
gdb/memtag.c
Normal 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
50
gdb/memtag.h
Normal 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 */
|
152
gdb/testsuite/gdb.arch/aarch64-mte-core.c
Normal file
152
gdb/testsuite/gdb.arch/aarch64-mte-core.c
Normal 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;
|
||||
}
|
175
gdb/testsuite/gdb.arch/aarch64-mte-core.exp
Normal file
175
gdb/testsuite/gdb.arch/aarch64-mte-core.exp
Normal 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
|
||||
}
|
Reference in New Issue
Block a user