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

View File

@ -144,6 +144,18 @@ lldb::addr_t ProcessElfCore::AddAddressRangeFromLoadSegment(
return addr; 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 // Process Control
Status ProcessElfCore::DoLoadCore() { Status ProcessElfCore::DoLoadCore() {
Status error; Status error;
@ -170,9 +182,12 @@ Status ProcessElfCore::DoLoadCore() {
bool ranges_are_sorted = true; bool ranges_are_sorted = true;
lldb::addr_t vm_addr = 0; lldb::addr_t vm_addr = 0;
lldb::addr_t tag_addr = 0;
/// Walk through segments and Thread and Address Map information. /// Walk through segments and Thread and Address Map information.
/// PT_NOTE - Contains Thread and Register information /// PT_NOTE - Contains Thread and Register information
/// PT_LOAD - Contains a contiguous range of Process Address Space /// 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) { for (const elf::ELFProgramHeader &H : segments) {
DataExtractor data = core->GetSegmentData(H); DataExtractor data = core->GetSegmentData(H);
@ -187,12 +202,18 @@ Status ProcessElfCore::DoLoadCore() {
if (vm_addr > last_addr) if (vm_addr > last_addr)
ranges_are_sorted = false; ranges_are_sorted = false;
vm_addr = last_addr; 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) { if (!ranges_are_sorted) {
m_core_aranges.Sort(); m_core_aranges.Sort();
m_core_range_infos.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 // 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::eYes
: MemoryRegionInfo::eNo); : MemoryRegionInfo::eNo);
region_info.SetMapped(MemoryRegionInfo::eYes); 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()) { } else if (load_addr < permission_entry->GetRangeBase()) {
region_info.GetRange().SetRangeBase(load_addr); region_info.GetRange().SetRangeBase(load_addr);
region_info.GetRange().SetRangeEnd(permission_entry->GetRangeBase()); 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.SetWritable(MemoryRegionInfo::eNo);
region_info.SetExecutable(MemoryRegionInfo::eNo); region_info.SetExecutable(MemoryRegionInfo::eNo);
region_info.SetMapped(MemoryRegionInfo::eNo); region_info.SetMapped(MemoryRegionInfo::eNo);
region_info.SetMemoryTagged(MemoryRegionInfo::eNo);
} }
return Status(); return Status();
} }
@ -327,6 +358,7 @@ Status ProcessElfCore::DoGetMemoryRegionInfo(lldb::addr_t load_addr,
region_info.SetWritable(MemoryRegionInfo::eNo); region_info.SetWritable(MemoryRegionInfo::eNo);
region_info.SetExecutable(MemoryRegionInfo::eNo); region_info.SetExecutable(MemoryRegionInfo::eNo);
region_info.SetMapped(MemoryRegionInfo::eNo); region_info.SetMapped(MemoryRegionInfo::eNo);
region_info.SetMemoryTagged(MemoryRegionInfo::eNo);
return Status(); return Status();
} }
@ -376,6 +408,38 @@ size_t ProcessElfCore::DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
return bytes_copied; 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() { void ProcessElfCore::Clear() {
m_thread_list.Clear(); m_thread_list.Clear();

View File

@ -86,6 +86,11 @@ public:
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
lldb_private::Status &error) override; 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::addr_t GetImageInfoAddress() override;
lldb_private::ArchSpec GetArchitecture(); lldb_private::ArchSpec GetArchitecture();
@ -105,6 +110,8 @@ protected:
DoGetMemoryRegionInfo(lldb::addr_t load_addr, DoGetMemoryRegionInfo(lldb::addr_t load_addr,
lldb_private::MemoryRegionInfo &region_info) override; lldb_private::MemoryRegionInfo &region_info) override;
bool SupportsMemoryTagging() override { return !m_core_tag_ranges.IsEmpty(); }
private: private:
struct NT_FILE_Entry { struct NT_FILE_Entry {
lldb::addr_t start; lldb::addr_t start;
@ -139,6 +146,9 @@ private:
// Permissions for all ranges // Permissions for all ranges
VMRangeToPermissions m_core_range_infos; 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 // NT_FILE entries found from the NOTE segment
std::vector<NT_FILE_Entry> m_nt_file_entries; std::vector<NT_FILE_Entry> m_nt_file_entries;
@ -154,6 +164,10 @@ private:
lldb::addr_t lldb::addr_t
AddAddressRangeFromLoadSegment(const elf::ELFProgramHeader &header); 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>> llvm::Expected<std::vector<lldb_private::CoreNote>>
parseSegment(const lldb_private::DataExtractor &segment); parseSegment(const lldb_private::DataExtractor &segment);
llvm::Error parseFreeBSDNotes(llvm::ArrayRef<lldb_private::CoreNote> notes); 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 locations. This prevents us reading locations multiple times, or not
writing out new values if the addresses have different non-address bits. 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 Changes to Sanitizers
--------------------- ---------------------

View File

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