Skip to content

Commit 2f9fa9e

Browse files
committed
[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
1 parent 4075a81 commit 2f9fa9e

File tree

9 files changed

+332
-2
lines changed

9 files changed

+332
-2
lines changed

lldb/include/lldb/Target/Process.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,8 +1715,8 @@ class Process : public std::enable_shared_from_this<Process>,
17151715
/// an error saying so.
17161716
/// If it does, either the memory tags or an error describing a
17171717
/// failure to read or unpack them.
1718-
llvm::Expected<std::vector<lldb::addr_t>> ReadMemoryTags(lldb::addr_t addr,
1719-
size_t len);
1718+
virtual llvm::Expected<std::vector<lldb::addr_t>>
1719+
ReadMemoryTags(lldb::addr_t addr, size_t len);
17201720

17211721
/// Write memory tags for a range of memory.
17221722
/// (calls DoWriteMemoryTags to do the target specific work)

lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,18 @@ lldb::addr_t ProcessElfCore::AddAddressRangeFromLoadSegment(
144144
return addr;
145145
}
146146

147+
lldb::addr_t ProcessElfCore::AddAddressRangeFromMemoryTagSegment(
148+
const elf::ELFProgramHeader &header) {
149+
// If lldb understood multiple kinds of tag segments we would record the type
150+
// of the segment here also. As long as there is only 1 type lldb looks for,
151+
// there is no need.
152+
FileRange file_range(header.p_offset, header.p_filesz);
153+
m_core_tag_ranges.Append(
154+
VMRangeToFileOffset::Entry(header.p_vaddr, header.p_memsz, file_range));
155+
156+
return header.p_vaddr;
157+
}
158+
147159
// Process Control
148160
Status ProcessElfCore::DoLoadCore() {
149161
Status error;
@@ -170,9 +182,12 @@ Status ProcessElfCore::DoLoadCore() {
170182

171183
bool ranges_are_sorted = true;
172184
lldb::addr_t vm_addr = 0;
185+
lldb::addr_t tag_addr = 0;
173186
/// Walk through segments and Thread and Address Map information.
174187
/// PT_NOTE - Contains Thread and Register information
175188
/// PT_LOAD - Contains a contiguous range of Process Address Space
189+
/// PT_AARCH64_MEMTAG_MTE - Contains AArch64 MTE memory tags for a range of
190+
/// Process Address Space.
176191
for (const elf::ELFProgramHeader &H : segments) {
177192
DataExtractor data = core->GetSegmentData(H);
178193

@@ -187,12 +202,18 @@ Status ProcessElfCore::DoLoadCore() {
187202
if (vm_addr > last_addr)
188203
ranges_are_sorted = false;
189204
vm_addr = last_addr;
205+
} else if (H.p_type == llvm::ELF::PT_AARCH64_MEMTAG_MTE) {
206+
lldb::addr_t last_addr = AddAddressRangeFromMemoryTagSegment(H);
207+
if (tag_addr > last_addr)
208+
ranges_are_sorted = false;
209+
tag_addr = last_addr;
190210
}
191211
}
192212

193213
if (!ranges_are_sorted) {
194214
m_core_aranges.Sort();
195215
m_core_range_infos.Sort();
216+
m_core_tag_ranges.Sort();
196217
}
197218

198219
// Even if the architecture is set in the target, we need to override it to
@@ -310,13 +331,23 @@ Status ProcessElfCore::DoGetMemoryRegionInfo(lldb::addr_t load_addr,
310331
? MemoryRegionInfo::eYes
311332
: MemoryRegionInfo::eNo);
312333
region_info.SetMapped(MemoryRegionInfo::eYes);
334+
335+
// A region is memory tagged if there is a memory tag segment that covers
336+
// the exact same range.
337+
region_info.SetMemoryTagged(MemoryRegionInfo::eNo);
338+
const VMRangeToFileOffset::Entry *tag_entry =
339+
m_core_tag_ranges.FindEntryStartsAt(permission_entry->GetRangeBase());
340+
if (tag_entry &&
341+
tag_entry->GetRangeEnd() == permission_entry->GetRangeEnd())
342+
region_info.SetMemoryTagged(MemoryRegionInfo::eYes);
313343
} else if (load_addr < permission_entry->GetRangeBase()) {
314344
region_info.GetRange().SetRangeBase(load_addr);
315345
region_info.GetRange().SetRangeEnd(permission_entry->GetRangeBase());
316346
region_info.SetReadable(MemoryRegionInfo::eNo);
317347
region_info.SetWritable(MemoryRegionInfo::eNo);
318348
region_info.SetExecutable(MemoryRegionInfo::eNo);
319349
region_info.SetMapped(MemoryRegionInfo::eNo);
350+
region_info.SetMemoryTagged(MemoryRegionInfo::eNo);
320351
}
321352
return Status();
322353
}
@@ -327,6 +358,7 @@ Status ProcessElfCore::DoGetMemoryRegionInfo(lldb::addr_t load_addr,
327358
region_info.SetWritable(MemoryRegionInfo::eNo);
328359
region_info.SetExecutable(MemoryRegionInfo::eNo);
329360
region_info.SetMapped(MemoryRegionInfo::eNo);
361+
region_info.SetMemoryTagged(MemoryRegionInfo::eNo);
330362
return Status();
331363
}
332364

@@ -376,6 +408,38 @@ size_t ProcessElfCore::DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
376408
return bytes_copied;
377409
}
378410

411+
llvm::Expected<std::vector<lldb::addr_t>>
412+
ProcessElfCore::ReadMemoryTags(lldb::addr_t addr, size_t len) {
413+
ObjectFile *core_objfile = m_core_module_sp->GetObjectFile();
414+
if (core_objfile == nullptr)
415+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
416+
"No core object file.");
417+
418+
llvm::Expected<const MemoryTagManager *> tag_manager_or_err =
419+
GetMemoryTagManager();
420+
if (!tag_manager_or_err)
421+
return tag_manager_or_err.takeError();
422+
423+
// LLDB only supports AArch64 MTE tag segments so we do not need to worry
424+
// about the segment type here. If you got here then you must have a tag
425+
// manager (meaning you are debugging AArch64) and all the segments in this
426+
// list will have had type PT_AARCH64_MEMTAG_MTE.
427+
const VMRangeToFileOffset::Entry *tag_entry =
428+
m_core_tag_ranges.FindEntryThatContains(addr);
429+
// If we don't have a tag segment or the range asked for extends outside the
430+
// segment.
431+
if (!tag_entry || (addr + len) >= tag_entry->GetRangeEnd())
432+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
433+
"No tag segment that covers this range.");
434+
435+
const MemoryTagManager *tag_manager = *tag_manager_or_err;
436+
return tag_manager->UnpackTagsFromCoreFileSegment(
437+
[core_objfile](lldb::offset_t offset, size_t length, void *dst) {
438+
return core_objfile->CopyData(offset, length, dst);
439+
},
440+
tag_entry->GetRangeBase(), tag_entry->data.GetRangeBase(), addr, len);
441+
}
442+
379443
void ProcessElfCore::Clear() {
380444
m_thread_list.Clear();
381445

lldb/source/Plugins/Process/elf-core/ProcessElfCore.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ class ProcessElfCore : public lldb_private::PostMortemProcess {
8686
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
8787
lldb_private::Status &error) override;
8888

89+
// We do not implement DoReadMemoryTags. Instead all the work is done in
90+
// ReadMemoryTags which avoids having to unpack and repack tags.
91+
llvm::Expected<std::vector<lldb::addr_t>> ReadMemoryTags(lldb::addr_t addr,
92+
size_t len) override;
93+
8994
lldb::addr_t GetImageInfoAddress() override;
9095

9196
lldb_private::ArchSpec GetArchitecture();
@@ -105,6 +110,8 @@ class ProcessElfCore : public lldb_private::PostMortemProcess {
105110
DoGetMemoryRegionInfo(lldb::addr_t load_addr,
106111
lldb_private::MemoryRegionInfo &region_info) override;
107112

113+
bool SupportsMemoryTagging() override { return !m_core_tag_ranges.IsEmpty(); }
114+
108115
private:
109116
struct NT_FILE_Entry {
110117
lldb::addr_t start;
@@ -139,6 +146,9 @@ class ProcessElfCore : public lldb_private::PostMortemProcess {
139146
// Permissions for all ranges
140147
VMRangeToPermissions m_core_range_infos;
141148

149+
// Memory tag ranges found in the core
150+
VMRangeToFileOffset m_core_tag_ranges;
151+
142152
// NT_FILE entries found from the NOTE segment
143153
std::vector<NT_FILE_Entry> m_nt_file_entries;
144154

@@ -154,6 +164,10 @@ class ProcessElfCore : public lldb_private::PostMortemProcess {
154164
lldb::addr_t
155165
AddAddressRangeFromLoadSegment(const elf::ELFProgramHeader &header);
156166

167+
// Parse a contiguous address range from a memory tag segment
168+
lldb::addr_t
169+
AddAddressRangeFromMemoryTagSegment(const elf::ELFProgramHeader &header);
170+
157171
llvm::Expected<std::vector<lldb_private::CoreNote>>
158172
parseSegment(const lldb_private::DataExtractor &segment);
159173
llvm::Error parseFreeBSDNotes(llvm::ArrayRef<lldb_private::CoreNote> notes);
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"""
2+
Test that memory tagging features work with Linux core files.
3+
"""
4+
5+
6+
import lldb
7+
from lldbsuite.test.decorators import *
8+
from lldbsuite.test.lldbtest import *
9+
10+
11+
class AArch64LinuxMTEMemoryTagCoreFileTestCase(TestBase):
12+
13+
mydir = TestBase.compute_mydir(__file__)
14+
15+
NO_DEBUG_INFO_TESTCASE = True
16+
17+
MTE_BUF_ADDR = hex(0xffff82c74000)
18+
BUF_ADDR = hex(0xffff82c73000)
19+
20+
@skipIfLLVMTargetMissing("AArch64")
21+
def test_mte_tag_core_file_memory_region(self):
22+
""" Test that memory regions are marked as tagged when there is a tag
23+
segment in the core file. """
24+
self.runCmd("target create --core core.mte")
25+
26+
# There should only be one tagged region.
27+
self.runCmd("memory region --all")
28+
got = self.res.GetOutput()
29+
found_tagged_region = False
30+
31+
for line in got.splitlines():
32+
if "memory tagging: enabled" in line:
33+
if found_tagged_region:
34+
self.fail("Expected only one tagged region.")
35+
found_tagged_region = True
36+
37+
self.assertTrue(found_tagged_region, "Did not find a tagged memory region.")
38+
39+
# mte_buf is tagged, buf is not.
40+
tagged = "memory tagging: enabled"
41+
self.expect("memory region {}".format(self.MTE_BUF_ADDR),
42+
patterns=[tagged])
43+
self.expect("memory region {}".format(self.BUF_ADDR),
44+
patterns=[tagged], matching=False)
45+
46+
@skipIfLLVMTargetMissing("AArch64")
47+
def test_mte_tag_core_file_tag_write(self):
48+
""" Test that "memory tag write" does not work with core files
49+
as they are read only. """
50+
self.runCmd("target create --core core.mte")
51+
52+
self.expect("memory tag write {} 1".format(self.MTE_BUF_ADDR), error=True,
53+
patterns=["error: elf-core does not support writing memory tags"])
54+
55+
@skipIfLLVMTargetMissing("AArch64")
56+
def test_mte_tag_core_file_tag_read(self):
57+
""" Test that "memory tag read" works with core files."""
58+
self.runCmd("target create --core core.mte")
59+
60+
# Tags are packed 2 per byte meaning that in addition to granule alignment
61+
# there is also 2 x granule alignment going on.
62+
63+
# All input validation should work as normal.
64+
not_tagged_pattern = ("error: Address range 0x[A-Fa-f0-9]+:0x[A-Fa-f0-9]+ "
65+
"is not in a memory tagged region")
66+
self.expect("memory tag read {}".format(self.BUF_ADDR),
67+
error=True, patterns=[not_tagged_pattern])
68+
# The first part of this range is not tagged.
69+
self.expect("memory tag read {addr}-16 {addr}+16".format(
70+
addr=self.MTE_BUF_ADDR), error=True,
71+
patterns=[not_tagged_pattern])
72+
# The last part of this range is not tagged.
73+
self.expect("memory tag read {addr}+4096-16 {addr}+4096+16".format(
74+
addr=self.MTE_BUF_ADDR), error=True,
75+
patterns=[not_tagged_pattern])
76+
77+
self.expect("memory tag read {addr}+16 {addr}".format(
78+
addr=self.MTE_BUF_ADDR), error=True,
79+
patterns=["error: End address \(0x[A-Fa-f0-9]+\) "
80+
"must be greater than the start address "
81+
"\(0x[A-Fa-f0-9]+\)"])
82+
83+
# The simplest scenario. 2 granules means 1 byte of packed tags
84+
# with no realignment required.
85+
self.expect("memory tag read {addr} {addr}+32".format(
86+
addr=self.MTE_BUF_ADDR),
87+
patterns=[
88+
"Allocation tags:\n"
89+
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0\n"
90+
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)$"])
91+
92+
# Here we want just one tag so must use half of the first byte.
93+
# (start is aligned length is not)
94+
self.expect("memory tag read {addr} {addr}+16".format(
95+
addr=self.MTE_BUF_ADDR),
96+
patterns=[
97+
"Allocation tags:\n"
98+
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0$"])
99+
# Get the other half of the first byte.
100+
# (end is aligned start is not)
101+
self.expect("memory tag read {addr}+16 {addr}+32".format(
102+
addr=self.MTE_BUF_ADDR),
103+
patterns=[
104+
"Allocation tags:\n"
105+
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)$"])
106+
107+
# Same thing but with a starting range > 1 granule.
108+
self.expect("memory tag read {addr} {addr}+48".format(
109+
addr=self.MTE_BUF_ADDR),
110+
patterns=[
111+
"Allocation tags:\n"
112+
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0\n"
113+
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)\n"
114+
"\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\): 0x2 \(mismatch\)$"])
115+
self.expect("memory tag read {addr}+16 {addr}+64".format(
116+
addr=self.MTE_BUF_ADDR),
117+
patterns=[
118+
"Allocation tags:\n"
119+
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)\n"
120+
"\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\): 0x2 \(mismatch\)\n"
121+
"\[0x[A-Fa-f0-9]+30, 0x[A-Fa-f0-9]+40\): 0x3 \(mismatch\)$"])
122+
# Here both start and end are unaligned.
123+
self.expect("memory tag read {addr}+16 {addr}+80".format(
124+
addr=self.MTE_BUF_ADDR),
125+
patterns=[
126+
"Allocation tags:\n"
127+
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)\n"
128+
"\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\): 0x2 \(mismatch\)\n"
129+
"\[0x[A-Fa-f0-9]+30, 0x[A-Fa-f0-9]+40\): 0x3 \(mismatch\)\n"
130+
"\[0x[A-Fa-f0-9]+40, 0x[A-Fa-f0-9]+50\): 0x4 \(mismatch\)$"])
131+
132+
# For the intial alignment of start/end to granule boundaries the tag manager
133+
# is used, so this reads 1 tag as it would normally.
134+
self.expect("memory tag read {addr} {addr}+1".format(
135+
addr=self.MTE_BUF_ADDR),
136+
patterns=[
137+
"Allocation tags:\n"
138+
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0$"])
139+
140+
# This range is aligned to granules as mte_buf to mte_buf+32 so the result
141+
# should be 2 granules.
142+
self.expect("memory tag read {addr} {addr}+17".format(
143+
addr=self.MTE_BUF_ADDR),
144+
patterns=[
145+
"Allocation tags:\n"
146+
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0\n"
147+
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)$"])
148+
149+
# Alignment of this range causes it to become unaligned to 2*granule boundaries.
150+
self.expect("memory tag read {addr} {addr}+33".format(
151+
addr=self.MTE_BUF_ADDR),
152+
patterns=[
153+
"Allocation tags:\n"
154+
"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0\n"
155+
"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)\n",
156+
"\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\): 0x2 \(mismatch\)$"])
157+
158+
@skipIfLLVMTargetMissing("AArch64")
159+
def test_mte_commands_no_mte(self):
160+
""" Test that memory tagging commands fail on an AArch64 corefile without
161+
any tag segments."""
162+
163+
self.runCmd("target create --core core.nomte")
164+
165+
self.expect("memory tag read 0 1",
166+
substrs=["error: Process does not support memory tagging"], error=True)
167+
# Note that this tells you memory tagging is not supported at all, versus
168+
# the MTE core file which does support it but does not allow writing tags.
169+
self.expect("memory tag write 0 1",
170+
substrs=["error: Process does not support memory tagging"], error=True)
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)