diff --git a/lldb/source/Commands/CommandObjectMemoryTag.cpp b/lldb/source/Commands/CommandObjectMemoryTag.cpp --- a/lldb/source/Commands/CommandObjectMemoryTag.cpp +++ b/lldb/source/Commands/CommandObjectMemoryTag.cpp @@ -115,6 +115,114 @@ } }; +#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 " + " [ [...]]"); + 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 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 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 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", @@ -123,6 +231,11 @@ 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; diff --git a/lldb/test/API/functionalities/memory/tag/TestMemoryTag.py b/lldb/test/API/functionalities/memory/tag/TestMemoryTag.py --- a/lldb/test/API/functionalities/memory/tag/TestMemoryTag.py +++ b/lldb/test/API/functionalities/memory/tag/TestMemoryTag.py @@ -39,3 +39,4 @@ 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) diff --git a/lldb/test/API/linux/aarch64/mte_tag_read/Makefile b/lldb/test/API/linux/aarch64/mte_tag_access/Makefile rename from lldb/test/API/linux/aarch64/mte_tag_read/Makefile rename to lldb/test/API/linux/aarch64/mte_tag_access/Makefile diff --git a/lldb/test/API/linux/aarch64/mte_tag_access/TestAArch64LinuxMTEMemoryTagAccess.py b/lldb/test/API/linux/aarch64/mte_tag_access/TestAArch64LinuxMTEMemoryTagAccess.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_tag_access/TestAArch64LinuxMTEMemoryTagAccess.py @@ -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 , " + "at most "], + error=True) + self.expect("memory tag read mte_buf buf+16 32", + substrs=["error: wrong number of arguments; expected at least , " + "at most "], + 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 " + " [ [...]]"], + error=True) + self.expect("memory tag write mte_buf", + substrs=[" wrong number of arguments; expected " + " [ [...]]"], + 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) diff --git a/lldb/test/API/linux/aarch64/mte_tag_read/main.c b/lldb/test/API/linux/aarch64/mte_tag_access/main.c rename from lldb/test/API/linux/aarch64/mte_tag_read/main.c rename to lldb/test/API/linux/aarch64/mte_tag_access/main.c --- a/lldb/test/API/linux/aarch64/mte_tag_read/main.c +++ b/lldb/test/API/linux/aarch64/mte_tag_access/main.c @@ -1,11 +1,22 @@ #include #include #include +#include #include #include #include #include +// This file uses ACLE intrinsics as detailed in: +// https://developer.arm.com/documentation/101028/0012/10--Memory-tagging-intrinsics?lang=en + +char *checked_mmap(size_t page_size, int prot) { + char *ptr = mmap(0, page_size, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (ptr == MAP_FAILED) + exit(1); + return ptr; +} + int main(int argc, char const *argv[]) { // We assume that the test runner has checked we're on an MTE system @@ -20,38 +31,32 @@ size_t page_size = sysconf(_SC_PAGESIZE); - // Allocate memory with MTE - // We ask for two pages. One is read only so that we get - // 2 mappings in /proc/.../smaps so we can check reading - // a range across mappings. - // The first allocation will start at the highest address, - // so we allocate buf2 first to get: - // | buf | buf2 | - int prot = PROT_READ | PROT_MTE; - int flags = MAP_PRIVATE | MAP_ANONYMOUS; - - char *buf2 = mmap(0, page_size, prot, flags, -1, 0); - if (buf2 == MAP_FAILED) - return 1; - - // Writeable so we can set tags on it later - char *buf = mmap(0, page_size, prot | PROT_WRITE, flags, -1, 0); - if (buf == MAP_FAILED) - return 1; + // We're going to mmap pages in this order: + // + // MTE read/write + // MTE read/write executable + // non MTE + // MTE read only + // + // + // This means that the first two MTE pages end up next + // to each other. Since the second one is also executable + // it will create a new entry in /proc/smaps. + int mte_prot = PROT_READ | PROT_MTE; + char *mte_buf_2 = checked_mmap(page_size, mte_prot | PROT_WRITE); + char *mte_buf = checked_mmap(page_size, mte_prot | PROT_WRITE | PROT_EXEC); // We expect the mappings to be next to each other - if (buf2 - buf != page_size) + if (mte_buf_2 - mte_buf != page_size) return 1; - // And without MTE - char *non_mte_buf = mmap(0, page_size, PROT_READ | PROT_WRITE, flags, -1, 0); - if (non_mte_buf == MAP_FAILED) - return 1; + char *non_mte_buf = checked_mmap(page_size, PROT_READ); + char *mte_read_only = checked_mmap(page_size, mte_prot); // Set incrementing tags until end of the first page - char *tagged_ptr = buf; + char *tagged_ptr = mte_buf; // This ignores tag bits when subtracting the addresses - while (__arm_mte_ptrdiff(tagged_ptr, buf) < page_size) { + 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 @@ -61,16 +66,17 @@ } // Tag the original pointer with 9 - buf = __arm_mte_create_random_tag(buf, ~(1 << 9)); + mte_buf = __arm_mte_create_random_tag(mte_buf, ~(1 << 9)); // A different tag so that buf_alt_tag > buf if you don't handle the tag - char *buf_alt_tag = __arm_mte_create_random_tag(buf, ~(1 << 10)); + char *mte_buf_alt_tag = __arm_mte_create_random_tag(mte_buf, ~(1 << 10)); // lldb should be removing the whole top byte, not just the tags. // So fill 63-60 with something non zero so we'll fail if we only remove tags. #define SET_TOP_NIBBLE(ptr) (char *)((size_t)(ptr) | (0xA << 60)) - buf = SET_TOP_NIBBLE(buf); - buf_alt_tag = SET_TOP_NIBBLE(buf_alt_tag); - buf2 = SET_TOP_NIBBLE(buf2); + mte_buf = SET_TOP_NIBBLE(mte_buf); + mte_buf_alt_tag = SET_TOP_NIBBLE(mte_buf_alt_tag); + mte_buf_2 = SET_TOP_NIBBLE(mte_buf_2); + mte_read_only = SET_TOP_NIBBLE(mte_read_only); // Breakpoint here return 0; diff --git a/lldb/test/API/linux/aarch64/mte_tag_read/TestAArch64LinuxMTEMemoryTagRead.py b/lldb/test/API/linux/aarch64/mte_tag_read/TestAArch64LinuxMTEMemoryTagRead.py deleted file mode 100644 --- a/lldb/test/API/linux/aarch64/mte_tag_read/TestAArch64LinuxMTEMemoryTagRead.py +++ /dev/null @@ -1,126 +0,0 @@ -""" -Test "memory tag read" command on AArch64 Linux with MTE. -""" - - -import lldb -from lldbsuite.test.decorators import * -from lldbsuite.test.lldbtest import * -from lldbsuite.test import lldbutil - - -class AArch64LinuxMTEMemoryTagReadTestCase(TestBase): - - mydir = TestBase.compute_mydir(__file__) - - NO_DEBUG_INFO_TESTCASE = True - - @skipUnlessArch("aarch64") - @skipUnlessPlatform(["linux"]) - @skipUnlessAArch64MTELinuxCompiler - def test_mte_tag_read(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']) - - # Argument validation - self.expect("memory tag read", - substrs=["error: wrong number of arguments; expected at least , " - "at most "], - error=True) - self.expect("memory tag read buf buf+16 32", - substrs=["error: wrong number of arguments; expected at least , " - "at most "], - 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 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 buf 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 buf 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 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 buf 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 buf+8 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 buf (MTE), buf2 (MTE), - # so we read from the end of buf2 here. - self.expect("memory tag read buf2+page_size-16 buf2+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 buf2+page_size-16 buf2+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 buf2+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 buf2 which is also MTE - self.expect("memory tag read buf+page_size-16 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 buf 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$"])