Skip to content

Commit 45d9885

Browse files
committed
[lldb] Add "memory tag write" command
This adds a new command for writing memory tags. It is based on the existing "memory write" command. Syntax: memory tag write <address-expression> <value> [<value> [...]] (where "value" is a tag value) (lldb) memory tag write mte_buf 1 2 (lldb) memory tag read mte_buf mte_buf+32 Logical tag: 0x0 Allocation tags: [0xfffff7ff9000, 0xfffff7ff9010): 0x1 [0xfffff7ff9010, 0xfffff7ff9020): 0x2 The range you are writing to will be calculated by aligning the address down to a granule boundary then adding as many granules as there are tags. (a repeating mode with an end address will be in a follow up patch) This is why "memory tag write" uses MakeTaggedRange but has some extra steps to get this specific behaviour. The command does all the usual argument validation: * Address must evaluate * You must supply at least one tag value (though lldb-server would just treat that as a nop anyway) * Those tag values must be valid for your tagging scheme (e.g. for MTE the value must be > 0 and < 0xf) * The calculated range must be memory tagged That last error will show you the final range, not just the start address you gave the command. (lldb) memory tag write mte_buf_2+page_size-16 6 (lldb) memory tag write mte_buf_2+page_size-16 6 7 error: Address range 0xfffff7ffaff0:0xfffff7ffb010 is not in a memory tagged region (note that we do not check if the region is writeable since lldb can write to it anyway) The read and write tag tests have been merged into a single set of "tag access" tests as their test programs would have been almost identical. (also I have renamed some of the buffers to better show what each one is used for) Reviewed By: omjavaid Differential Revision: https://reviews.llvm.org/D105182 (cherry picked from commit 6a7a2ee)
1 parent 4ae3353 commit 45d9885

File tree

6 files changed

+368
-156
lines changed

6 files changed

+368
-156
lines changed

lldb/source/Commands/CommandObjectMemoryTag.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,114 @@ class CommandObjectMemoryTagRead : public CommandObjectParsed {
115115
}
116116
};
117117

118+
#define LLDB_OPTIONS_memory_tag_write
119+
#include "CommandOptions.inc"
120+
121+
class CommandObjectMemoryTagWrite : public CommandObjectParsed {
122+
public:
123+
CommandObjectMemoryTagWrite(CommandInterpreter &interpreter)
124+
: CommandObjectParsed(interpreter, "tag",
125+
"Write memory tags starting from the granule that "
126+
"contains the given address.",
127+
nullptr,
128+
eCommandRequiresTarget | eCommandRequiresProcess |
129+
eCommandProcessMustBePaused) {
130+
// Address
131+
m_arguments.push_back(
132+
CommandArgumentEntry{CommandArgumentData(eArgTypeAddressOrExpression)});
133+
// One or more tag values
134+
m_arguments.push_back(CommandArgumentEntry{
135+
CommandArgumentData(eArgTypeValue, eArgRepeatPlus)});
136+
}
137+
138+
~CommandObjectMemoryTagWrite() override = default;
139+
140+
protected:
141+
bool DoExecute(Args &command, CommandReturnObject &result) override {
142+
if (command.GetArgumentCount() < 2) {
143+
result.AppendError("wrong number of arguments; expected "
144+
"<address-expression> <tag> [<tag> [...]]");
145+
return false;
146+
}
147+
148+
Status error;
149+
addr_t start_addr = OptionArgParser::ToAddress(
150+
&m_exe_ctx, command[0].ref(), LLDB_INVALID_ADDRESS, &error);
151+
if (start_addr == LLDB_INVALID_ADDRESS) {
152+
result.AppendErrorWithFormatv("Invalid address expression, {0}",
153+
error.AsCString());
154+
return false;
155+
}
156+
157+
command.Shift(); // shift off start address
158+
159+
std::vector<lldb::addr_t> tags;
160+
for (auto &entry : command) {
161+
lldb::addr_t tag_value;
162+
// getAsInteger returns true on failure
163+
if (entry.ref().getAsInteger(0, tag_value)) {
164+
result.AppendErrorWithFormat(
165+
"'%s' is not a valid unsigned decimal string value.\n",
166+
entry.c_str());
167+
return false;
168+
}
169+
tags.push_back(tag_value);
170+
}
171+
172+
Process *process = m_exe_ctx.GetProcessPtr();
173+
llvm::Expected<const MemoryTagManager *> tag_manager_or_err =
174+
process->GetMemoryTagManager();
175+
176+
if (!tag_manager_or_err) {
177+
result.SetError(Status(tag_manager_or_err.takeError()));
178+
return false;
179+
}
180+
181+
const MemoryTagManager *tag_manager = *tag_manager_or_err;
182+
183+
MemoryRegionInfos memory_regions;
184+
// If this fails the list of regions is cleared, so we don't need to read
185+
// the return status here.
186+
process->GetMemoryRegions(memory_regions);
187+
188+
// We have to assume start_addr is not granule aligned.
189+
// So if we simply made a range:
190+
// (start_addr, start_addr + (N * granule_size))
191+
// We would end up with a range that isn't N granules but N+1
192+
// granules. To avoid this we'll align the start first using the method that
193+
// doesn't check memory attributes. (if the final range is untagged we'll
194+
// handle that error later)
195+
lldb::addr_t aligned_start_addr =
196+
tag_manager->ExpandToGranule(MemoryTagManager::TagRange(start_addr, 1))
197+
.GetRangeBase();
198+
199+
// Now we've aligned the start address so if we ask for another range
200+
// using the number of tags N, we'll get back a range that is also N
201+
// granules in size.
202+
llvm::Expected<MemoryTagManager::TagRange> tagged_range =
203+
tag_manager->MakeTaggedRange(
204+
aligned_start_addr,
205+
aligned_start_addr + (tags.size() * tag_manager->GetGranuleSize()),
206+
memory_regions);
207+
208+
if (!tagged_range) {
209+
result.SetError(Status(tagged_range.takeError()));
210+
return false;
211+
}
212+
213+
Status status = process->WriteMemoryTags(tagged_range->GetRangeBase(),
214+
tagged_range->GetByteSize(), tags);
215+
216+
if (status.Fail()) {
217+
result.SetError(status);
218+
return false;
219+
}
220+
221+
result.SetStatus(eReturnStatusSuccessFinishResult);
222+
return true;
223+
}
224+
};
225+
118226
CommandObjectMemoryTag::CommandObjectMemoryTag(CommandInterpreter &interpreter)
119227
: CommandObjectMultiword(
120228
interpreter, "tag", "Commands for manipulating memory tags",
@@ -123,6 +231,11 @@ CommandObjectMemoryTag::CommandObjectMemoryTag(CommandInterpreter &interpreter)
123231
new CommandObjectMemoryTagRead(interpreter));
124232
read_command_object->SetCommandName("memory tag read");
125233
LoadSubCommand("read", read_command_object);
234+
235+
CommandObjectSP write_command_object(
236+
new CommandObjectMemoryTagWrite(interpreter));
237+
write_command_object->SetCommandName("memory tag write");
238+
LoadSubCommand("write", write_command_object);
126239
}
127240

128241
CommandObjectMemoryTag::~CommandObjectMemoryTag() = default;

lldb/test/API/functionalities/memory/tag/TestMemoryTag.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ def test_memory_tag_read_unsupported(self):
3939
expected = "error: This architecture does not support memory tagging"
4040

4141
self.expect("memory tag read 0 1", substrs=[expected], error=True)
42+
self.expect("memory tag write 0 1 2", substrs=[expected], error=True)
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
"""
2+
Test "memory tag read" and "memory tag write" commands
3+
on AArch64 Linux with MTE.
4+
"""
5+
6+
7+
import lldb
8+
from lldbsuite.test.decorators import *
9+
from lldbsuite.test.lldbtest import *
10+
from lldbsuite.test import lldbutil
11+
12+
13+
class AArch64LinuxMTEMemoryTagAccessTestCase(TestBase):
14+
15+
mydir = TestBase.compute_mydir(__file__)
16+
17+
NO_DEBUG_INFO_TESTCASE = True
18+
19+
def setup_mte_test(self):
20+
if not self.isAArch64MTE():
21+
self.skipTest('Target must support MTE.')
22+
23+
self.build()
24+
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
25+
26+
lldbutil.run_break_set_by_file_and_line(self, "main.c",
27+
line_number('main.c', '// Breakpoint here'),
28+
num_expected_locations=1)
29+
30+
self.runCmd("run", RUN_SUCCEEDED)
31+
32+
if self.process().GetState() == lldb.eStateExited:
33+
self.fail("Test program failed to run.")
34+
35+
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
36+
substrs=['stopped',
37+
'stop reason = breakpoint'])
38+
39+
@skipUnlessArch("aarch64")
40+
@skipUnlessPlatform(["linux"])
41+
@skipUnlessAArch64MTELinuxCompiler
42+
def test_mte_tag_read(self):
43+
self.setup_mte_test()
44+
45+
# Argument validation
46+
self.expect("memory tag read",
47+
substrs=["error: wrong number of arguments; expected at least <address-expression>, "
48+
"at most <address-expression> <end-address-expression>"],
49+
error=True)
50+
self.expect("memory tag read mte_buf buf+16 32",
51+
substrs=["error: wrong number of arguments; expected at least <address-expression>, "
52+
"at most <address-expression> <end-address-expression>"],
53+
error=True)
54+
self.expect("memory tag read not_a_symbol",
55+
substrs=["error: Invalid address expression, address expression \"not_a_symbol\" "
56+
"evaluation failed"],
57+
error=True)
58+
self.expect("memory tag read mte_buf not_a_symbol",
59+
substrs=["error: Invalid end address expression, address expression \"not_a_symbol\" "
60+
"evaluation failed"],
61+
error=True)
62+
# Inverted range
63+
self.expect("memory tag read mte_buf mte_buf-16",
64+
patterns=["error: End address \(0x[A-Fa-f0-9]+\) must be "
65+
"greater than the start address \(0x[A-Fa-f0-9]+\)"],
66+
error=True)
67+
# Range of length 0
68+
self.expect("memory tag read mte_buf mte_buf",
69+
patterns=["error: End address \(0x[A-Fa-f0-9]+\) must be "
70+
"greater than the start address \(0x[A-Fa-f0-9]+\)"],
71+
error=True)
72+
73+
74+
# Can't read from a region without tagging
75+
self.expect("memory tag read non_mte_buf",
76+
patterns=["error: Address range 0x[0-9A-Fa-f]+00:0x[0-9A-Fa-f]+10 is not "
77+
"in a memory tagged region"],
78+
error=True)
79+
80+
# If there's no end address we assume 1 granule
81+
self.expect("memory tag read mte_buf",
82+
patterns=["Logical tag: 0x9\n"
83+
"Allocation tags:\n"
84+
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x0$"])
85+
86+
# Range of <1 granule is rounded up to 1 granule
87+
self.expect("memory tag read mte_buf mte_buf+8",
88+
patterns=["Logical tag: 0x9\n"
89+
"Allocation tags:\n"
90+
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x0$"])
91+
92+
# Start address is aligned down, end aligned up
93+
self.expect("memory tag read mte_buf+8 mte_buf+24",
94+
patterns=["Logical tag: 0x9\n"
95+
"Allocation tags:\n"
96+
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x0\n"
97+
"\[0x[0-9A-Fa-f]+10, 0x[0-9A-Fa-f]+20\): 0x1$"])
98+
99+
# You may read up to the end of the tagged region
100+
# Layout is mte_buf, mte_buf_2, non_mte_buf.
101+
# So we read from the end of mte_buf_2 here.
102+
self.expect("memory tag read mte_buf_2+page_size-16 mte_buf_2+page_size",
103+
patterns=["Logical tag: 0x0\n"
104+
"Allocation tags:\n"
105+
"\[0x[0-9A-Fa-f]+, 0x[0-9A-Fa-f]+\): 0x0$"])
106+
107+
# Ranges with any part outside the region will error
108+
self.expect("memory tag read mte_buf_2+page_size-16 mte_buf_2+page_size+32",
109+
patterns=["error: Address range 0x[0-9A-fa-f]+f0:0x[0-9A-Fa-f]+20 "
110+
"is not in a memory tagged region"],
111+
error=True)
112+
self.expect("memory tag read mte_buf_2+page_size",
113+
patterns=["error: Address range 0x[0-9A-fa-f]+00:0x[0-9A-Fa-f]+10 "
114+
"is not in a memory tagged region"],
115+
error=True)
116+
117+
# You can read a range that spans more than one mapping
118+
# This spills into mte_buf2 which is also MTE
119+
self.expect("memory tag read mte_buf+page_size-16 mte_buf+page_size+16",
120+
patterns=["Logical tag: 0x9\n"
121+
"Allocation tags:\n"
122+
"\[0x[0-9A-Fa-f]+f0, 0x[0-9A-Fa-f]+00\): 0xf\n"
123+
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x0$"])
124+
125+
# Tags in start/end are ignored when creating the range.
126+
# So this is not an error despite start/end having different tags
127+
self.expect("memory tag read mte_buf mte_buf_alt_tag+16 ",
128+
patterns=["Logical tag: 0x9\n"
129+
"Allocation tags:\n"
130+
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x0$"])
131+
132+
@skipUnlessArch("aarch64")
133+
@skipUnlessPlatform(["linux"])
134+
@skipUnlessAArch64MTELinuxCompiler
135+
def test_mte_tag_write(self):
136+
self.setup_mte_test()
137+
138+
# Argument validation
139+
self.expect("memory tag write",
140+
substrs=[" wrong number of arguments; expected "
141+
"<address-expression> <tag> [<tag> [...]]"],
142+
error=True)
143+
self.expect("memory tag write mte_buf",
144+
substrs=[" wrong number of arguments; expected "
145+
"<address-expression> <tag> [<tag> [...]]"],
146+
error=True)
147+
self.expect("memory tag write not_a_symbol 9",
148+
substrs=["error: Invalid address expression, address expression \"not_a_symbol\" "
149+
"evaluation failed"],
150+
error=True)
151+
152+
# Can't write to a region without tagging
153+
self.expect("memory tag write non_mte_buf 9",
154+
patterns=["error: Address range 0x[0-9A-Fa-f]+00:0x[0-9A-Fa-f]+10 is not "
155+
"in a memory tagged region"],
156+
error=True)
157+
158+
# Start address is aligned down so we write to the granule that contains it
159+
self.expect("memory tag write mte_buf+8 9")
160+
# Make sure we only modified the first granule
161+
self.expect("memory tag read mte_buf mte_buf+32",
162+
patterns=["Logical tag: 0x9\n"
163+
"Allocation tags:\n"
164+
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x9\n"
165+
"\[0x[0-9A-Fa-f]+10, 0x[0-9A-Fa-f]+20\): 0x1$"])
166+
167+
# You can write multiple tags, range calculated for you
168+
self.expect("memory tag write mte_buf 10 11 12")
169+
self.expect("memory tag read mte_buf mte_buf+48",
170+
patterns=["Logical tag: 0x9\n"
171+
"Allocation tags:\n"
172+
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0xa\n"
173+
"\[0x[0-9A-Fa-f]+10, 0x[0-9A-Fa-f]+20\): 0xb\n"
174+
"\[0x[0-9A-Fa-f]+20, 0x[0-9A-Fa-f]+30\): 0xc$"])
175+
176+
# You may write up to the end of a tagged region
177+
# (mte_buf_2's intial tags will all be 0)
178+
self.expect("memory tag write mte_buf_2+page_size-16 0xe")
179+
self.expect("memory tag read mte_buf_2+page_size-16 mte_buf_2+page_size",
180+
patterns=["Logical tag: 0x0\n"
181+
"Allocation tags:\n"
182+
"\[0x[0-9A-Fa-f]+, 0x[0-9A-Fa-f]+\): 0xe$"])
183+
184+
# Ranges with any part outside the region will error
185+
self.expect("memory tag write mte_buf_2+page_size-16 6 7",
186+
patterns=["error: Address range 0x[0-9A-fa-f]+f0:0x[0-9A-Fa-f]+10 "
187+
"is not in a memory tagged region"],
188+
error=True)
189+
self.expect("memory tag write mte_buf_2+page_size 6",
190+
patterns=["error: Address range 0x[0-9A-fa-f]+00:0x[0-9A-Fa-f]+10 "
191+
"is not in a memory tagged region"],
192+
error=True)
193+
self.expect("memory tag write mte_buf_2+page_size 6 7 8",
194+
patterns=["error: Address range 0x[0-9A-fa-f]+00:0x[0-9A-Fa-f]+30 "
195+
"is not in a memory tagged region"],
196+
error=True)
197+
198+
# You can write to a range that spans two mappings, as long
199+
# as they are both tagged.
200+
# buf and buf2 are next to each other so this wirtes into buf2.
201+
self.expect("memory tag write mte_buf+page_size-16 1 2")
202+
self.expect("memory tag read mte_buf+page_size-16 mte_buf+page_size+16",
203+
patterns=["Logical tag: 0x9\n"
204+
"Allocation tags:\n"
205+
"\[0x[0-9A-Fa-f]+f0, 0x[0-9A-Fa-f]+00\): 0x1\n"
206+
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x2$"])
207+
208+
# Even if a page is read only the debugger can still write to it
209+
self.expect("memory tag write mte_read_only 1")
210+
self.expect("memory tag read mte_read_only",
211+
patterns=["Logical tag: 0x0\n"
212+
"Allocation tags:\n"
213+
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x1$"])
214+
215+
# Trying to write a value > maximum tag value is an error
216+
self.expect("memory tag write mte_buf 99",
217+
patterns=["error: Found tag 0x63 which is > max MTE tag value of 0xf."],
218+
error=True)

0 commit comments

Comments
 (0)