diff --git a/lldb/source/Commands/CMakeLists.txt b/lldb/source/Commands/CMakeLists.txt --- a/lldb/source/Commands/CMakeLists.txt +++ b/lldb/source/Commands/CMakeLists.txt @@ -16,6 +16,7 @@ CommandObjectLanguage.cpp CommandObjectLog.cpp CommandObjectMemory.cpp + CommandObjectMemoryTag.cpp CommandObjectMultiword.cpp CommandObjectPlatform.cpp CommandObjectPlugin.cpp diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp --- a/lldb/source/Commands/CommandObjectMemory.cpp +++ b/lldb/source/Commands/CommandObjectMemory.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "CommandObjectMemory.h" +#include "CommandObjectMemoryTag.h" #include "lldb/Core/DumpDataExtractor.h" #include "lldb/Core/Section.h" #include "lldb/Core/ValueObjectMemory.h" @@ -1758,6 +1759,8 @@ CommandObjectSP(new CommandObjectMemoryHistory(interpreter))); LoadSubCommand("region", CommandObjectSP(new CommandObjectMemoryRegion(interpreter))); + LoadSubCommand("tag", + CommandObjectSP(new CommandObjectMemoryTag(interpreter))); } CommandObjectMemory::~CommandObjectMemory() = default; diff --git a/lldb/source/Commands/CommandObjectMemoryTag.h b/lldb/source/Commands/CommandObjectMemoryTag.h new file mode 100644 --- /dev/null +++ b/lldb/source/Commands/CommandObjectMemoryTag.h @@ -0,0 +1,25 @@ +//===-- CommandObjectMemoryTag.h --------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTMEMORYTAG_H +#define LLDB_SOURCE_COMMANDS_COMMANDOBJECTMEMORYTAG_H + +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +class CommandObjectMemoryTag : public CommandObjectMultiword { +public: + CommandObjectMemoryTag(CommandInterpreter &interpreter); + + ~CommandObjectMemoryTag() override; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTMEMORYTAG_H diff --git a/lldb/source/Commands/CommandObjectMemoryTag.cpp b/lldb/source/Commands/CommandObjectMemoryTag.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Commands/CommandObjectMemoryTag.cpp @@ -0,0 +1,122 @@ +//===-- CommandObjectMemoryTag.cpp ----------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "CommandObjectMemoryTag.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Target/Process.h" + +using namespace lldb; +using namespace lldb_private; + +#define LLDB_OPTIONS_memory_tag_read +#include "CommandOptions.inc" + +class CommandObjectMemoryTagRead : public CommandObjectParsed { +public: + CommandObjectMemoryTagRead(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "tag", + "Read memory tags for the given range of memory.", + nullptr, + eCommandRequiresTarget | eCommandRequiresProcess | + eCommandProcessMustBePaused) { + // Address + m_arguments.push_back( + CommandArgumentEntry{CommandArgumentData(eArgTypeAddressOrExpression)}); + // Optional end address + m_arguments.push_back(CommandArgumentEntry{ + CommandArgumentData(eArgTypeAddressOrExpression, eArgRepeatOptional)}); + } + + ~CommandObjectMemoryTagRead() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if ((command.GetArgumentCount() < 1) || (command.GetArgumentCount() > 2)) { + result.AppendError( + "wrong number of arguments; expected at least , " + "at most "); + result.SetStatus(eReturnStatusFailed); + 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()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Default 1 byte beyond start, rounds up to at most 1 granule later + addr_t end_addr = start_addr + 1; + + if (command.GetArgumentCount() > 1) { + end_addr = OptionArgParser::ToAddress(&m_exe_ctx, command[1].ref(), + LLDB_INVALID_ADDRESS, &error); + if (end_addr == LLDB_INVALID_ADDRESS) { + result.AppendErrorWithFormatv("Invalid end address expression, {0}", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + Process *process = m_exe_ctx.GetProcessPtr(); + llvm::Expected tag_manager_or_err = + process->GetMemoryTagManager(start_addr, end_addr); + + if (!tag_manager_or_err) { + result.SetError(Status(tag_manager_or_err.takeError())); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const MemoryTagManager *tag_manager = *tag_manager_or_err; + ptrdiff_t len = tag_manager->AddressDiff(end_addr, start_addr); + llvm::Expected> tags = + process->ReadMemoryTags(tag_manager, start_addr, len); + + if (!tags) { + result.SetError(Status(tags.takeError())); + result.SetStatus(eReturnStatusFailed); + return false; + } + + result.AppendMessageWithFormatv("Logical tag: {0:x}", + tag_manager->GetLogicalTag(start_addr)); + result.AppendMessage("Allocation tags:"); + + MemoryTagManager::TagRange initial_range(start_addr, len); + addr_t addr = tag_manager->ExpandToGranule(initial_range).GetRangeBase(); + for (auto tag : *tags) { + addr_t next_addr = addr + tag_manager->GetGranuleSize(); + // Showing tagged adresses here until we have non address bit handling + result.AppendMessageWithFormatv("[{0:x}, {1:x}): {2:x}", addr, next_addr, + tag); + addr = next_addr; + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } +}; + +CommandObjectMemoryTag::CommandObjectMemoryTag(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "tag", "Commands for manipulating memory tags", + "memory tag []") { + CommandObjectSP read_command_object( + new CommandObjectMemoryTagRead(interpreter)); + read_command_object->SetCommandName("memory tag read"); + LoadSubCommand("read", read_command_object); +} + +CommandObjectMemoryTag::~CommandObjectMemoryTag() = default; diff --git a/lldb/test/API/functionalities/memory/tag/Makefile b/lldb/test/API/functionalities/memory/tag/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/memory/tag/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/functionalities/memory/tag/TestMemoryTag.py b/lldb/test/API/functionalities/memory/tag/TestMemoryTag.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/memory/tag/TestMemoryTag.py @@ -0,0 +1,35 @@ +""" +Test errors from 'memory tag' commands on unsupported platforms. +Tests for the only supported platform, AArch64 Linux, are in +API/linux/aarch64/. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +import lldbsuite.test.lldbutil as lldbutil + + +class MemoryTagTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + def test_memory_tag_read_unsupported(self): + """Test that "memory tag read" errors on unsupported platforms""" + if self.isAArch64MTE(): + self.skipTest("Requires a target without AArch64 MTE.") + + self.build() + exe = self.getBuildArtifact("a.out") + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + lldbutil.run_break_set_by_file_and_line(self, "main.cpp", + line_number('main.cpp', '// Breakpoint here'), + num_expected_locations=1) + self.runCmd("run", RUN_SUCCEEDED) + + self.expect("memory tag read 0 1", + substrs=["error: This architecture does not support memory tagging"], + error=True) diff --git a/lldb/test/API/functionalities/memory/tag/main.cpp b/lldb/test/API/functionalities/memory/tag/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/memory/tag/main.cpp @@ -0,0 +1,4 @@ +int main(int argc, char const *argv[]) { + // Breakpoint here + return 0; +} diff --git a/lldb/test/API/linux/aarch64/mte_tag_read/Makefile b/lldb/test/API/linux/aarch64/mte_tag_read/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_tag_read/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := main.c +CFLAGS_EXTRAS := -march=armv8.5-a+memtag + +include Makefile.rules diff --git a/lldb/test/API/linux/aarch64/mte_tag_read/TestAArch64LinuxMTEMemoryTagRead.py b/lldb/test/API/linux/aarch64/mte_tag_read/TestAArch64LinuxMTEMemoryTagRead.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_tag_read/TestAArch64LinuxMTEMemoryTagRead.py @@ -0,0 +1,116 @@ +""" +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 + self.expect("memory tag read buf+page_size-16 buf+page_size", + patterns=["Logical tag: 0x9\n" + "Allocation tags:\n" + "\[0x[0-9A-Fa-f]+, 0x[0-9A-Fa-f]+\): 0x[0-9A-Fa-f]$"]) + + # Ranges with any part outside the region will error + self.expect("memory tag read buf+page_size-16 buf+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 buf+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) + + # 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$"]) diff --git a/lldb/test/API/linux/aarch64/mte_tag_read/main.c b/lldb/test/API/linux/aarch64/mte_tag_read/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_tag_read/main.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char const *argv[]) { + // We assume that the test runner has checked we're on an MTE system + + 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); + + // Allocate memory with MTE + char *buf = mmap(0, sysconf(_SC_PAGESIZE), PROT_READ | PROT_WRITE | PROT_MTE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (buf == MAP_FAILED) + return 1; + + // And without MTE + char *non_mte_buf = mmap(0, sysconf(_SC_PAGESIZE), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (non_mte_buf == MAP_FAILED) + return 1; + + // Set incrementing tags until end of the page + char *tagged_ptr = buf; + // This ignores tag bits when subtracting the addresses + while (__arm_mte_ptrdiff(tagged_ptr, 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); + } + + // Tag the original pointer with 9 + buf = __arm_mte_create_random_tag(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)); + + // 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. + size_t nibble = 0xA; + buf = (char *)((size_t)buf | (nibble << 60)); + buf_alt_tag = (char *)((size_t)buf_alt_tag | (nibble << 60)); + + // Breakpoint here + return 0; +}