Skip to content

Commit

Permalink
[lldb] Add "memory tag write" command
Browse files Browse the repository at this point in the history
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
  • Loading branch information
DavidSpickett committed Jul 28, 2021
1 parent 04b94c7 commit 6a7a2ee
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 156 deletions.
113 changes: 113 additions & 0 deletions lldb/source/Commands/CommandObjectMemoryTag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,114 @@ class CommandObjectMemoryTagRead : public CommandObjectParsed {
}
};

#define LLDB_OPTIONS_memory_tag_write
#include "CommandOptions.inc"

class CommandObjectMemoryTagWrite : public CommandObjectParsed {
public:
CommandObjectMemoryTagWrite(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "tag",
"Write memory tags starting from the granule that "
"contains the given address.",
nullptr,
eCommandRequiresTarget | eCommandRequiresProcess |
eCommandProcessMustBePaused) {
// Address
m_arguments.push_back(
CommandArgumentEntry{CommandArgumentData(eArgTypeAddressOrExpression)});
// One or more tag values
m_arguments.push_back(CommandArgumentEntry{
CommandArgumentData(eArgTypeValue, eArgRepeatPlus)});
}

~CommandObjectMemoryTagWrite() override = default;

protected:
bool DoExecute(Args &command, CommandReturnObject &result) override {
if (command.GetArgumentCount() < 2) {
result.AppendError("wrong number of arguments; expected "
"<address-expression> <tag> [<tag> [...]]");
return false;
}

Status error;
addr_t start_addr = OptionArgParser::ToAddress(
&m_exe_ctx, command[0].ref(), LLDB_INVALID_ADDRESS, &error);
if (start_addr == LLDB_INVALID_ADDRESS) {
result.AppendErrorWithFormatv("Invalid address expression, {0}",
error.AsCString());
return false;
}

command.Shift(); // shift off start address

std::vector<lldb::addr_t> tags;
for (auto &entry : command) {
lldb::addr_t tag_value;
// getAsInteger returns true on failure
if (entry.ref().getAsInteger(0, tag_value)) {
result.AppendErrorWithFormat(
"'%s' is not a valid unsigned decimal string value.\n",
entry.c_str());
return false;
}
tags.push_back(tag_value);
}

Process *process = m_exe_ctx.GetProcessPtr();
llvm::Expected<const MemoryTagManager *> tag_manager_or_err =
process->GetMemoryTagManager();

if (!tag_manager_or_err) {
result.SetError(Status(tag_manager_or_err.takeError()));
return false;
}

const MemoryTagManager *tag_manager = *tag_manager_or_err;

MemoryRegionInfos memory_regions;
// If this fails the list of regions is cleared, so we don't need to read
// the return status here.
process->GetMemoryRegions(memory_regions);

// We have to assume start_addr is not granule aligned.
// So if we simply made a range:
// (start_addr, start_addr + (N * granule_size))
// We would end up with a range that isn't N granules but N+1
// granules. To avoid this we'll align the start first using the method that
// doesn't check memory attributes. (if the final range is untagged we'll
// handle that error later)
lldb::addr_t aligned_start_addr =
tag_manager->ExpandToGranule(MemoryTagManager::TagRange(start_addr, 1))
.GetRangeBase();

// Now we've aligned the start address so if we ask for another range
// using the number of tags N, we'll get back a range that is also N
// granules in size.
llvm::Expected<MemoryTagManager::TagRange> tagged_range =
tag_manager->MakeTaggedRange(
aligned_start_addr,
aligned_start_addr + (tags.size() * tag_manager->GetGranuleSize()),
memory_regions);

if (!tagged_range) {
result.SetError(Status(tagged_range.takeError()));
return false;
}

Status status = process->WriteMemoryTags(tagged_range->GetRangeBase(),
tagged_range->GetByteSize(), tags);

if (status.Fail()) {
result.SetError(status);
return false;
}

result.SetStatus(eReturnStatusSuccessFinishResult);
return true;
}
};

CommandObjectMemoryTag::CommandObjectMemoryTag(CommandInterpreter &interpreter)
: CommandObjectMultiword(
interpreter, "tag", "Commands for manipulating memory tags",
Expand All @@ -123,6 +231,11 @@ CommandObjectMemoryTag::CommandObjectMemoryTag(CommandInterpreter &interpreter)
new CommandObjectMemoryTagRead(interpreter));
read_command_object->SetCommandName("memory tag read");
LoadSubCommand("read", read_command_object);

CommandObjectSP write_command_object(
new CommandObjectMemoryTagWrite(interpreter));
write_command_object->SetCommandName("memory tag write");
LoadSubCommand("write", write_command_object);
}

CommandObjectMemoryTag::~CommandObjectMemoryTag() = default;
1 change: 1 addition & 0 deletions lldb/test/API/functionalities/memory/tag/TestMemoryTag.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ def test_memory_tag_read_unsupported(self):
expected = "error: This architecture does not support memory tagging"

self.expect("memory tag read 0 1", substrs=[expected], error=True)
self.expect("memory tag write 0 1 2", substrs=[expected], error=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
"""
Test "memory tag read" and "memory tag write" commands
on AArch64 Linux with MTE.
"""


import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil


class AArch64LinuxMTEMemoryTagAccessTestCase(TestBase):

mydir = TestBase.compute_mydir(__file__)

NO_DEBUG_INFO_TESTCASE = True

def setup_mte_test(self):
if not self.isAArch64MTE():
self.skipTest('Target must support MTE.')

self.build()
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)

lldbutil.run_break_set_by_file_and_line(self, "main.c",
line_number('main.c', '// Breakpoint here'),
num_expected_locations=1)

self.runCmd("run", RUN_SUCCEEDED)

if self.process().GetState() == lldb.eStateExited:
self.fail("Test program failed to run.")

self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs=['stopped',
'stop reason = breakpoint'])

@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
@skipUnlessAArch64MTELinuxCompiler
def test_mte_tag_read(self):
self.setup_mte_test()

# Argument validation
self.expect("memory tag read",
substrs=["error: wrong number of arguments; expected at least <address-expression>, "
"at most <address-expression> <end-address-expression>"],
error=True)
self.expect("memory tag read mte_buf buf+16 32",
substrs=["error: wrong number of arguments; expected at least <address-expression>, "
"at most <address-expression> <end-address-expression>"],
error=True)
self.expect("memory tag read not_a_symbol",
substrs=["error: Invalid address expression, address expression \"not_a_symbol\" "
"evaluation failed"],
error=True)
self.expect("memory tag read mte_buf not_a_symbol",
substrs=["error: Invalid end address expression, address expression \"not_a_symbol\" "
"evaluation failed"],
error=True)
# Inverted range
self.expect("memory tag read mte_buf mte_buf-16",
patterns=["error: End address \(0x[A-Fa-f0-9]+\) must be "
"greater than the start address \(0x[A-Fa-f0-9]+\)"],
error=True)
# Range of length 0
self.expect("memory tag read mte_buf mte_buf",
patterns=["error: End address \(0x[A-Fa-f0-9]+\) must be "
"greater than the start address \(0x[A-Fa-f0-9]+\)"],
error=True)


# Can't read from a region without tagging
self.expect("memory tag read non_mte_buf",
patterns=["error: Address range 0x[0-9A-Fa-f]+00:0x[0-9A-Fa-f]+10 is not "
"in a memory tagged region"],
error=True)

# If there's no end address we assume 1 granule
self.expect("memory tag read mte_buf",
patterns=["Logical tag: 0x9\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x0$"])

# Range of <1 granule is rounded up to 1 granule
self.expect("memory tag read mte_buf mte_buf+8",
patterns=["Logical tag: 0x9\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x0$"])

# Start address is aligned down, end aligned up
self.expect("memory tag read mte_buf+8 mte_buf+24",
patterns=["Logical tag: 0x9\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x0\n"
"\[0x[0-9A-Fa-f]+10, 0x[0-9A-Fa-f]+20\): 0x1$"])

# You may read up to the end of the tagged region
# Layout is mte_buf, mte_buf_2, non_mte_buf.
# So we read from the end of mte_buf_2 here.
self.expect("memory tag read mte_buf_2+page_size-16 mte_buf_2+page_size",
patterns=["Logical tag: 0x0\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+, 0x[0-9A-Fa-f]+\): 0x0$"])

# Ranges with any part outside the region will error
self.expect("memory tag read mte_buf_2+page_size-16 mte_buf_2+page_size+32",
patterns=["error: Address range 0x[0-9A-fa-f]+f0:0x[0-9A-Fa-f]+20 "
"is not in a memory tagged region"],
error=True)
self.expect("memory tag read mte_buf_2+page_size",
patterns=["error: Address range 0x[0-9A-fa-f]+00:0x[0-9A-Fa-f]+10 "
"is not in a memory tagged region"],
error=True)

# You can read a range that spans more than one mapping
# This spills into mte_buf2 which is also MTE
self.expect("memory tag read mte_buf+page_size-16 mte_buf+page_size+16",
patterns=["Logical tag: 0x9\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+f0, 0x[0-9A-Fa-f]+00\): 0xf\n"
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x0$"])

# Tags in start/end are ignored when creating the range.
# So this is not an error despite start/end having different tags
self.expect("memory tag read mte_buf mte_buf_alt_tag+16 ",
patterns=["Logical tag: 0x9\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x0$"])

@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
@skipUnlessAArch64MTELinuxCompiler
def test_mte_tag_write(self):
self.setup_mte_test()

# Argument validation
self.expect("memory tag write",
substrs=[" wrong number of arguments; expected "
"<address-expression> <tag> [<tag> [...]]"],
error=True)
self.expect("memory tag write mte_buf",
substrs=[" wrong number of arguments; expected "
"<address-expression> <tag> [<tag> [...]]"],
error=True)
self.expect("memory tag write not_a_symbol 9",
substrs=["error: Invalid address expression, address expression \"not_a_symbol\" "
"evaluation failed"],
error=True)

# Can't write to a region without tagging
self.expect("memory tag write non_mte_buf 9",
patterns=["error: Address range 0x[0-9A-Fa-f]+00:0x[0-9A-Fa-f]+10 is not "
"in a memory tagged region"],
error=True)

# Start address is aligned down so we write to the granule that contains it
self.expect("memory tag write mte_buf+8 9")
# Make sure we only modified the first granule
self.expect("memory tag read mte_buf mte_buf+32",
patterns=["Logical tag: 0x9\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x9\n"
"\[0x[0-9A-Fa-f]+10, 0x[0-9A-Fa-f]+20\): 0x1$"])

# You can write multiple tags, range calculated for you
self.expect("memory tag write mte_buf 10 11 12")
self.expect("memory tag read mte_buf mte_buf+48",
patterns=["Logical tag: 0x9\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0xa\n"
"\[0x[0-9A-Fa-f]+10, 0x[0-9A-Fa-f]+20\): 0xb\n"
"\[0x[0-9A-Fa-f]+20, 0x[0-9A-Fa-f]+30\): 0xc$"])

# You may write up to the end of a tagged region
# (mte_buf_2's intial tags will all be 0)
self.expect("memory tag write mte_buf_2+page_size-16 0xe")
self.expect("memory tag read mte_buf_2+page_size-16 mte_buf_2+page_size",
patterns=["Logical tag: 0x0\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+, 0x[0-9A-Fa-f]+\): 0xe$"])

# Ranges with any part outside the region will error
self.expect("memory tag write mte_buf_2+page_size-16 6 7",
patterns=["error: Address range 0x[0-9A-fa-f]+f0:0x[0-9A-Fa-f]+10 "
"is not in a memory tagged region"],
error=True)
self.expect("memory tag write mte_buf_2+page_size 6",
patterns=["error: Address range 0x[0-9A-fa-f]+00:0x[0-9A-Fa-f]+10 "
"is not in a memory tagged region"],
error=True)
self.expect("memory tag write mte_buf_2+page_size 6 7 8",
patterns=["error: Address range 0x[0-9A-fa-f]+00:0x[0-9A-Fa-f]+30 "
"is not in a memory tagged region"],
error=True)

# You can write to a range that spans two mappings, as long
# as they are both tagged.
# buf and buf2 are next to each other so this wirtes into buf2.
self.expect("memory tag write mte_buf+page_size-16 1 2")
self.expect("memory tag read mte_buf+page_size-16 mte_buf+page_size+16",
patterns=["Logical tag: 0x9\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+f0, 0x[0-9A-Fa-f]+00\): 0x1\n"
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x2$"])

# Even if a page is read only the debugger can still write to it
self.expect("memory tag write mte_read_only 1")
self.expect("memory tag read mte_read_only",
patterns=["Logical tag: 0x0\n"
"Allocation tags:\n"
"\[0x[0-9A-Fa-f]+00, 0x[0-9A-Fa-f]+10\): 0x1$"])

# Trying to write a value > maximum tag value is an error
self.expect("memory tag write mte_buf 99",
patterns=["error: Found tag 0x63 which is > max MTE tag value of 0xf."],
error=True)
Loading

0 comments on commit 6a7a2ee

Please sign in to comment.