[lldb][AArch64] Add support for memory tags in core files

This teaches ProcessElfCore to recognise the MTE tag segments.

https://www.kernel.org/doc/html/latest/arm64/memory-tagging-extension.html#core-dump-support

These segments contain all the tags for a matching memory segment
which will have the same size in virtual address terms. In real terms
it's 2 tags per byte so the data in the segment is much smaller.

Since MTE is the only tag type supported I have hardcoded some
things to those values. We could and should support more formats
as they appear but doing so now would leave code untested until that
happens.

A few things to note:
* /proc/pid/smaps is not in the core file, only the details you have
  in "maps". Meaning we mark a region tagged only if it has a tag segment.
* A core file supports memory tagging if it has at least 1 memory
  tag segment, there is no other flag we can check to tell if memory
  tagging was enabled. (unlike a live process that can support memory
  tagging even if there are currently no tagged memory regions)

Tests have been added at the commands level for a core file with
mte and without.

There is a lot of overlap between the "memory tag read" tests here and the unit tests for
MemoryTagManagerAArch64MTE::UnpackTagsFromCoreFileSegment, but I think it's
worth keeping to check ProcessElfCore doesn't cause an assert.

Depends on D129487

Reviewed By: omjavaid

Differential Revision: https://reviews.llvm.org/D129489
This commit is contained in:
David Spickett 2022-07-11 13:26:55 +01:00
parent 4075a811ad
commit 2f9fa9ef53
9 changed files with 332 additions and 2 deletions

View File

@ -1715,8 +1715,8 @@ public:
/// an error saying so.
/// If it does, either the memory tags or an error describing a
/// failure to read or unpack them.
llvm::Expected<std::vector<lldb::addr_t>> ReadMemoryTags(lldb::addr_t addr,
size_t len);
virtual llvm::Expected<std::vector<lldb::addr_t>>
ReadMemoryTags(lldb::addr_t addr, size_t len);
/// Write memory tags for a range of memory.
/// (calls DoWriteMemoryTags to do the target specific work)

View File

@ -144,6 +144,18 @@ lldb::addr_t ProcessElfCore::AddAddressRangeFromLoadSegment(
return addr;
}
lldb::addr_t ProcessElfCore::AddAddressRangeFromMemoryTagSegment(
const elf::ELFProgramHeader &header) {
// If lldb understood multiple kinds of tag segments we would record the type
// of the segment here also. As long as there is only 1 type lldb looks for,
// there is no need.
FileRange file_range(header.p_offset, header.p_filesz);
m_core_tag_ranges.Append(
VMRangeToFileOffset::Entry(header.p_vaddr, header.p_memsz, file_range));
return header.p_vaddr;
}
// Process Control
Status ProcessElfCore::DoLoadCore() {
Status error;
@ -170,9 +182,12 @@ Status ProcessElfCore::DoLoadCore() {
bool ranges_are_sorted = true;
lldb::addr_t vm_addr = 0;
lldb::addr_t tag_addr = 0;
/// Walk through segments and Thread and Address Map information.
/// PT_NOTE - Contains Thread and Register information
/// PT_LOAD - Contains a contiguous range of Process Address Space
/// PT_AARCH64_MEMTAG_MTE - Contains AArch64 MTE memory tags for a range of
/// Process Address Space.
for (const elf::ELFProgramHeader &H : segments) {
DataExtractor data = core->GetSegmentData(H);
@ -187,12 +202,18 @@ Status ProcessElfCore::DoLoadCore() {
if (vm_addr > last_addr)
ranges_are_sorted = false;
vm_addr = last_addr;
} else if (H.p_type == llvm::ELF::PT_AARCH64_MEMTAG_MTE) {
lldb::addr_t last_addr = AddAddressRangeFromMemoryTagSegment(H);
if (tag_addr > last_addr)
ranges_are_sorted = false;
tag_addr = last_addr;
}
}
if (!ranges_are_sorted) {
m_core_aranges.Sort();
m_core_range_infos.Sort();
m_core_tag_ranges.Sort();
}
// Even if the architecture is set in the target, we need to override it to
@ -310,6 +331,15 @@ Status ProcessElfCore::DoGetMemoryRegionInfo(lldb::addr_t load_addr,
? MemoryRegionInfo::eYes
: MemoryRegionInfo::eNo);
region_info.SetMapped(MemoryRegionInfo::eYes);
// A region is memory tagged if there is a memory tag segment that covers
// the exact same range.
region_info.SetMemoryTagged(MemoryRegionInfo::eNo);
const VMRangeToFileOffset::Entry *tag_entry =
m_core_tag_ranges.FindEntryStartsAt(permission_entry->GetRangeBase());
if (tag_entry &&
tag_entry->GetRangeEnd() == permission_entry->GetRangeEnd())
region_info.SetMemoryTagged(MemoryRegionInfo::eYes);
} else if (load_addr < permission_entry->GetRangeBase()) {
region_info.GetRange().SetRangeBase(load_addr);
region_info.GetRange().SetRangeEnd(permission_entry->GetRangeBase());
@ -317,6 +347,7 @@ Status ProcessElfCore::DoGetMemoryRegionInfo(lldb::addr_t load_addr,
region_info.SetWritable(MemoryRegionInfo::eNo);
region_info.SetExecutable(MemoryRegionInfo::eNo);
region_info.SetMapped(MemoryRegionInfo::eNo);
region_info.SetMemoryTagged(MemoryRegionInfo::eNo);
}
return Status();
}
@ -327,6 +358,7 @@ Status ProcessElfCore::DoGetMemoryRegionInfo(lldb::addr_t load_addr,
region_info.SetWritable(MemoryRegionInfo::eNo);
region_info.SetExecutable(MemoryRegionInfo::eNo);
region_info.SetMapped(MemoryRegionInfo::eNo);
region_info.SetMemoryTagged(MemoryRegionInfo::eNo);
return Status();
}
@ -376,6 +408,38 @@ size_t ProcessElfCore::DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
return bytes_copied;
}
llvm::Expected<std::vector<lldb::addr_t>>
ProcessElfCore::ReadMemoryTags(lldb::addr_t addr, size_t len) {
ObjectFile *core_objfile = m_core_module_sp->GetObjectFile();
if (core_objfile == nullptr)
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"No core object file.");
llvm::Expected<const MemoryTagManager *> tag_manager_or_err =
GetMemoryTagManager();
if (!tag_manager_or_err)
return tag_manager_or_err.takeError();
// LLDB only supports AArch64 MTE tag segments so we do not need to worry
// about the segment type here. If you got here then you must have a tag
// manager (meaning you are debugging AArch64) and all the segments in this
// list will have had type PT_AARCH64_MEMTAG_MTE.
const VMRangeToFileOffset::Entry *tag_entry =
m_core_tag_ranges.FindEntryThatContains(addr);
// If we don't have a tag segment or the range asked for extends outside the
// segment.
if (!tag_entry || (addr + len) >= tag_entry->GetRangeEnd())
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"No tag segment that covers this range.");
const MemoryTagManager *tag_manager = *tag_manager_or_err;
return tag_manager->UnpackTagsFromCoreFileSegment(
[core_objfile](lldb::offset_t offset, size_t length, void *dst) {
return core_objfile->CopyData(offset, length, dst);
},
tag_entry->GetRangeBase(), tag_entry->data.GetRangeBase(), addr, len);
}
void ProcessElfCore::Clear() {
m_thread_list.Clear();

View File

@ -86,6 +86,11 @@ public:
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
lldb_private::Status &error) override;
// We do not implement DoReadMemoryTags. Instead all the work is done in
// ReadMemoryTags which avoids having to unpack and repack tags.
llvm::Expected<std::vector<lldb::addr_t>> ReadMemoryTags(lldb::addr_t addr,
size_t len) override;
lldb::addr_t GetImageInfoAddress() override;
lldb_private::ArchSpec GetArchitecture();
@ -105,6 +110,8 @@ protected:
DoGetMemoryRegionInfo(lldb::addr_t load_addr,
lldb_private::MemoryRegionInfo &region_info) override;
bool SupportsMemoryTagging() override { return !m_core_tag_ranges.IsEmpty(); }
private:
struct NT_FILE_Entry {
lldb::addr_t start;
@ -139,6 +146,9 @@ private:
// Permissions for all ranges
VMRangeToPermissions m_core_range_infos;
// Memory tag ranges found in the core
VMRangeToFileOffset m_core_tag_ranges;
// NT_FILE entries found from the NOTE segment
std::vector<NT_FILE_Entry> m_nt_file_entries;
@ -154,6 +164,10 @@ private:
lldb::addr_t
AddAddressRangeFromLoadSegment(const elf::ELFProgramHeader &header);
// Parse a contiguous address range from a memory tag segment
lldb::addr_t
AddAddressRangeFromMemoryTagSegment(const elf::ELFProgramHeader &header);
llvm::Expected<std::vector<lldb_private::CoreNote>>
parseSegment(const lldb_private::DataExtractor &segment);
llvm::Error parseFreeBSDNotes(llvm::ArrayRef<lldb_private::CoreNote> notes);

View File

@ -0,0 +1,170 @@
"""
Test that memory tagging features work with Linux core files.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
class AArch64LinuxMTEMemoryTagCoreFileTestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
NO_DEBUG_INFO_TESTCASE = True
MTE_BUF_ADDR = hex(0xffff82c74000)
BUF_ADDR = hex(0xffff82c73000)
@skipIfLLVMTargetMissing("AArch64")
def test_mte_tag_core_file_memory_region(self):
""" Test that memory regions are marked as tagged when there is a tag
segment in the core file. """
self.runCmd("target create --core core.mte")
# There should only be one tagged region.
self.runCmd("memory region --all")
got = self.res.GetOutput()
found_tagged_region = False
for line in got.splitlines():
if "memory tagging: enabled" in line:
if found_tagged_region:
self.fail("Expected only one tagged region.")
found_tagged_region = True
self.assertTrue(found_tagged_region, "Did not find a tagged memory region.")
# mte_buf is tagged, buf is not.
tagged = "memory tagging: enabled"
self.expect("memory region {}".format(self.MTE_BUF_ADDR),
patterns=[tagged])
self.expect("memory region {}".format(self.BUF_ADDR),
patterns=[tagged], matching=False)
@skipIfLLVMTargetMissing("AArch64")
def test_mte_tag_core_file_tag_write(self):
""" Test that "memory tag write" does not work with core files
as they are read only. """
self.runCmd("target create --core core.mte")
self.expect("memory tag write {} 1".format(self.MTE_BUF_ADDR), error=True,
patterns=["error: elf-core does not support writing memory tags"])
@skipIfLLVMTargetMissing("AArch64")
def test_mte_tag_core_file_tag_read(self):
""" Test that "memory tag read" works with core files."""
self.runCmd("target create --core core.mte")
# Tags are packed 2 per byte meaning that in addition to granule alignment
# there is also 2 x granule alignment going on.
# All input validation should work as normal.
not_tagged_pattern = ("error: Address range 0x[A-Fa-f0-9]+:0x[A-Fa-f0-9]+ "
"is not in a memory tagged region")
self.expect("memory tag read {}".format(self.BUF_ADDR),
error=True, patterns=[not_tagged_pattern])
# The first part of this range is not tagged.
self.expect("memory tag read {addr}-16 {addr}+16".format(
addr=self.MTE_BUF_ADDR), error=True,
patterns=[not_tagged_pattern])
# The last part of this range is not tagged.
self.expect("memory tag read {addr}+4096-16 {addr}+4096+16".format(
addr=self.MTE_BUF_ADDR), error=True,
patterns=[not_tagged_pattern])
self.expect("memory tag read {addr}+16 {addr}".format(
addr=self.MTE_BUF_ADDR), error=True,
patterns=["error: End address \(0x[A-Fa-f0-9]+\) "
"must be greater than the start address "
"\(0x[A-Fa-f0-9]+\)"])
# The simplest scenario. 2 granules means 1 byte of packed tags
# with no realignment required.
self.expect("memory tag read {addr} {addr}+32".format(
addr=self.MTE_BUF_ADDR),
patterns=[
"Allocation tags:\n"
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0\n"
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)$"])
# Here we want just one tag so must use half of the first byte.
# (start is aligned length is not)
self.expect("memory tag read {addr} {addr}+16".format(
addr=self.MTE_BUF_ADDR),
patterns=[
"Allocation tags:\n"
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0$"])
# Get the other half of the first byte.
# (end is aligned start is not)
self.expect("memory tag read {addr}+16 {addr}+32".format(
addr=self.MTE_BUF_ADDR),
patterns=[
"Allocation tags:\n"
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)$"])
# Same thing but with a starting range > 1 granule.
self.expect("memory tag read {addr} {addr}+48".format(
addr=self.MTE_BUF_ADDR),
patterns=[
"Allocation tags:\n"
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0\n"
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)\n"
"\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\): 0x2 \(mismatch\)$"])
self.expect("memory tag read {addr}+16 {addr}+64".format(
addr=self.MTE_BUF_ADDR),
patterns=[
"Allocation tags:\n"
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)\n"
"\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\): 0x2 \(mismatch\)\n"
"\[0x[A-Fa-f0-9]+30, 0x[A-Fa-f0-9]+40\): 0x3 \(mismatch\)$"])
# Here both start and end are unaligned.
self.expect("memory tag read {addr}+16 {addr}+80".format(
addr=self.MTE_BUF_ADDR),
patterns=[
"Allocation tags:\n"
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)\n"
"\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\): 0x2 \(mismatch\)\n"
"\[0x[A-Fa-f0-9]+30, 0x[A-Fa-f0-9]+40\): 0x3 \(mismatch\)\n"
"\[0x[A-Fa-f0-9]+40, 0x[A-Fa-f0-9]+50\): 0x4 \(mismatch\)$"])
# For the intial alignment of start/end to granule boundaries the tag manager
# is used, so this reads 1 tag as it would normally.
self.expect("memory tag read {addr} {addr}+1".format(
addr=self.MTE_BUF_ADDR),
patterns=[
"Allocation tags:\n"
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0$"])
# This range is aligned to granules as mte_buf to mte_buf+32 so the result
# should be 2 granules.
self.expect("memory tag read {addr} {addr}+17".format(
addr=self.MTE_BUF_ADDR),
patterns=[
"Allocation tags:\n"
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0\n"
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)$"])
# Alignment of this range causes it to become unaligned to 2*granule boundaries.
self.expect("memory tag read {addr} {addr}+33".format(
addr=self.MTE_BUF_ADDR),
patterns=[
"Allocation tags:\n"
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0\n"
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)\n",
"\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\): 0x2 \(mismatch\)$"])
@skipIfLLVMTargetMissing("AArch64")
def test_mte_commands_no_mte(self):
""" Test that memory tagging commands fail on an AArch64 corefile without
any tag segments."""
self.runCmd("target create --core core.nomte")
self.expect("memory tag read 0 1",
substrs=["error: Process does not support memory tagging"], error=True)
# Note that this tells you memory tagging is not supported at all, versus
# the MTE core file which does support it but does not allow writing tags.
self.expect("memory tag write 0 1",
substrs=["error: Process does not support memory tagging"], error=True)

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,78 @@
// Program to generate core files to test MTE tag features.
//
// This file uses ACLE intrinsics as detailed in:
// https://developer.arm.com/documentation/101028/0012/10--Memory-tagging-intrinsics?lang=en
//
// Compile with:
// <gcc or clang> -march=armv8.5-a+memtag -g main.c -o a.out.mte
// <gcc or clang> -march=armv8.5-a+memtag -g main.c -DNO_MTE -o a.out.nomte
//
// /proc/self/coredump_filter was set to 2 when the core files were made.
#include <arm_acle.h>
#include <asm/mman.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>
int main(int argc, char const *argv[]) {
#ifdef NO_MTE
*(char *)(0) = 0;
#endif
if (prctl(PR_SET_TAGGED_ADDR_CTRL,
PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
// Allow all tags to be generated by the addg
// instruction __arm_mte_increment_tag produces.
(0xffff << PR_MTE_TAG_SHIFT),
0, 0, 0)) {
return 1;
}
size_t page_size = sysconf(_SC_PAGESIZE);
char *mte_buf = mmap(0, page_size, PROT_READ | PROT_WRITE | PROT_MTE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (!mte_buf)
return 1;
printf("mte_buf: %p\n", mte_buf);
// Allocate some untagged memory before the tagged memory.
char *buf = mmap(0, page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (!buf)
return 1;
printf("buf: %p\n", buf);
// This write means that the memory for buf is included in the corefile.
// So we can read from the end of it into mte_buf during the test.
*buf = 1;
// These must be next to each other for the tests to work.
// <high address>
// mte_buf
// buf
// <low address>
if ((mte_buf - buf) != page_size) {
return 1;
}
// Set incrementing tags until end of the page.
char *tagged_ptr = mte_buf;
// This ignores tag bits when subtracting the addresses.
while (__arm_mte_ptrdiff(tagged_ptr, mte_buf) < page_size) {
// Set the allocation tag for this location.
__arm_mte_set_tag(tagged_ptr);
// + 16 for 16 byte granules.
// Earlier we allowed all tag values, so this will give us an
// incrementing pattern 0-0xF wrapping back to 0.
tagged_ptr = __arm_mte_increment_tag(tagged_ptr + 16, 1);
}
// Will fault because logical tag 0 != allocation tag 1.
*(mte_buf + 16) = 1;
return 0;
}

View File

@ -306,6 +306,8 @@ Changes to LLDB
locations. This prevents us reading locations multiple times, or not
writing out new values if the addresses have different non-address bits.
* LLDB now supports reading memory tags from AArch64 Linux core files.
Changes to Sanitizers
---------------------

View File

@ -1386,6 +1386,8 @@ enum {
// These all contain stack unwind tables.
PT_ARM_EXIDX = 0x70000001,
PT_ARM_UNWIND = 0x70000001,
// MTE memory tag segment type
PT_AARCH64_MEMTAG_MTE = 0x70000002,
// MIPS program header types.
PT_MIPS_REGINFO = 0x70000000, // Register usage information.