diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -85,6 +85,9 @@ Status ReadMemoryWithoutTrap(lldb::addr_t addr, void *buf, size_t size, size_t &bytes_read); + virtual Status ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len, + std::vector &tags); + /// Reads a null terminated string from memory. /// /// Reads up to \p max_size bytes of memory until it finds a '\0'. diff --git a/lldb/include/lldb/Host/linux/Ptrace.h b/lldb/include/lldb/Host/linux/Ptrace.h --- a/lldb/include/lldb/Host/linux/Ptrace.h +++ b/lldb/include/lldb/Host/linux/Ptrace.h @@ -50,6 +50,12 @@ #define ARCH_GET_FS 0x1003 #define ARCH_GET_GS 0x1004 #endif +#ifndef PTRACE_PEEKMTETAGS +#define PTRACE_PEEKMTETAGS 33 +#endif +#ifndef PTRACE_POKEMTETAGS +#define PTRACE_POKEMTETAGS 34 +#endif #define LLDB_PTRACE_NT_ARM_TLS 0x401 // ARM TLS register diff --git a/lldb/include/lldb/Target/MemoryTagHandler.h b/lldb/include/lldb/Target/MemoryTagHandler.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Target/MemoryTagHandler.h @@ -0,0 +1,68 @@ +//===-- MemoryTagHandler.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_TARGET_MEMORYTAGHANDLER_H +#define LLDB_TARGET_MEMORYTAGHANDLER_H + +#include "lldb/Utility/RangeMap.h" +#include "lldb/lldb-private.h" +#include "llvm/Support/Error.h" + +namespace lldb_private { + +// This interface allows high level commands to handle memory tags +// in a generic way. It can be accessed from the Architecture plugin +// when the architecture supports memory tagging. +// +// Definitions: +// logical tag - the tag stored in a pointer +// allocation tag - the tag stored in hardware +// (e.g. special memory, cache line bits) +// granule - number of bytes of memory a single tag applies to + +class MemoryTagHandler { +public: + typedef Range TagRange; + + // Remove the logical tag, returning the untagged pointer + virtual lldb::addr_t RemoveLogicalTag(lldb::addr_t addr) const = 0; + + // Return the number of bytes a single tag covers + virtual lldb::addr_t GetGranuleSize() const = 0; + + // Align an address range to granule boundaries. + // So that reading memory tags for the new range returns + // tags that will cover the original range. + // + // Say your granules are 16 bytes and you want + // tags for 16 bytes of memory starting from address 8. + // 1 granule isn't enough because it only covers addresses + // 0-16, we want addresses 8-24. So the range must be + // expanded to 2 granules. + virtual TagRange AlignToGranules(TagRange range) const = 0; + + // Return the type value to use in GDB protocol qMemTags packets to read + // allocation tags. This is named "Allocation" specifically because the spec + // allows for logical tags to be read the same way, though we do not use that. + // + // This value is unique within a given architecture. Meaning that different + // tagging schemes within the same architecture should use unique values, + // but other architectures can overlap those values. + virtual int32_t GetAllocationTagType() const = 0; + + // Return the number of bytes a single tag will be packed into during + // transport. For example an MTE tag is 4 bits but occupies 1 byte during + // transport. + virtual size_t GetBytesPerTag() const = 0; + + virtual ~MemoryTagHandler() {} +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_MEMORYTAGHANDLER_H diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h --- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -169,6 +169,8 @@ eServerPacketType_jTraceConfigRead, // deprecated eServerPacketType_jLLDBTraceSupportedType, + + eServerPacketType_qMemTags, }; ServerPacketType GetServerPacketType() const; diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py --- a/lldb/packages/Python/lldbsuite/test/decorators.py +++ b/lldb/packages/Python/lldbsuite/test/decorators.py @@ -822,6 +822,40 @@ """Skip this test if the environment is set up to run LLDB *itself* under ASAN.""" return skipTestIfFn(is_running_under_asan)(func) +def skipUnlessAArch64MTELinuxToolchain(func): + """Decorate the item to skip test unless MTE is supported by the test compiler/toolchain.""" + + def is_toolchain_with_mte(self): + compiler_path = self.getCompiler() + compiler = os.path.basename(compiler_path) + f = tempfile.NamedTemporaryFile() + if lldbplatformutil.getPlatform() == 'windows': + return "MTE tests are not compatible with 'windows'" + + cmd = "echo 'int main() {}' | %s -x c -o %s -" % (compiler_path, f.name) + if os.popen(cmd).close() is not None: + # Cannot compile at all, don't skip the test + # so that we report the broken compiler normally. + return None + + # We need the Linux headers and ACLE MTE intrinsics + test_src = """ + #include + #include + #ifndef HWCAP2_MTE + #error + #endif + int main() { + void* ptr = __arm_mte_create_random_tag((void*)(0), 0); + }""" + cmd = "echo '%s' | %s -march=armv8.5-a+memtag -x c -o %s -" % (test_src, compiler_path, f.name) + if os.popen(cmd).close() is not None: + return "Toolchain does not support MTE" + return None + + return skipTestIfFn(is_toolchain_with_mte)(func) + + def _get_bool_config(key, fail_value = True): """ Returns the current LLDB's build config value. diff --git a/lldb/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -54,6 +54,12 @@ return Status("not implemented"); } +lldb_private::Status +NativeProcessProtocol::ReadMemoryTags(int32_t type, lldb::addr_t addr, + size_t len, std::vector &tags) { + return Status("not implemented"); +} + llvm::Optional NativeProcessProtocol::GetExitStatus() { if (m_state == lldb::eStateExited) return m_exit_status; diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -76,6 +76,9 @@ llvm::Error DeallocateMemory(lldb::addr_t addr) override; + Status ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len, + std::vector &tags) override; + size_t UpdateThreads() override; const ArchSpec &GetArchitecture() const override { return m_arch; } diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -1487,6 +1487,47 @@ return llvm::Error::success(); } +Status NativeProcessLinux::ReadMemoryTags(int32_t type, lldb::addr_t addr, + size_t len, + std::vector &tags) { + llvm::Expected details = + GetCurrentThread()->GetRegisterContext().GetMemoryTaggingDetails(type); + if (!details) + return Status(details.takeError()); + + // Ignore 0 length read + if (!len) + return Status(); + + // lldb will align the range it requests but it is not required to by + // the protocol so we'll do it again just in case. + // Also remove the tag here. ptrace may work with it present + // but that is not guaranteed. + MemoryTagHandler::TagRange range(details->handler->RemoveLogicalTag(addr), + len); + range = details->handler->AlignToGranules(range); + + tags.resize((range.GetByteSize() / details->handler->GetGranuleSize()) * + details->handler->GetBytesPerTag()); + + struct iovec tags_vec; + tags_vec.iov_base = &tags[0]; + tags_vec.iov_len = tags.size(); + + Status error = NativeProcessLinux::PtraceWrapper( + details->ptrace_read_req, GetID(), + reinterpret_cast(range.GetRangeBase()), + static_cast(&tags_vec), 0, nullptr); + + if (error.Fail()) + return error; + + if (tags_vec.iov_len != tags.size()) + return Status("Got unexpected number of memory tag bytes from ptrace."); + + return error; +} + size_t NativeProcessLinux::UpdateThreads() { // The NativeProcessLinux monitoring threads are always up to date with // respect to thread state and they keep the thread list populated properly. diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux.h --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux.h @@ -11,6 +11,8 @@ #include "Plugins/Process/Utility/NativeRegisterContextRegisterInfo.h" #include "lldb/Host/common/NativeThreadProtocol.h" +#include "lldb/Target/MemoryTagHandler.h" +#include "llvm/Support/Error.h" namespace lldb_private { namespace process_linux { @@ -55,6 +57,18 @@ /// they are supported. virtual llvm::Optional GetMmapData() { return llvm::None; } + struct MemoryTaggingDetails { + std::unique_ptr handler; + int ptrace_read_req; + int ptrace_write_req; + }; + virtual llvm::Expected + GetMemoryTaggingDetails(int32_t type) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Architecture does not support memory tagging"); + } + protected: lldb::ByteOrder GetByteOrder() const; diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h @@ -85,6 +85,9 @@ // Debug register type select enum DREGType { eDREGTypeWATCH = 0, eDREGTypeBREAK }; + llvm::Expected + GetMemoryTaggingDetails(int32_t type) override; + protected: Status ReadGPR() override; diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -21,6 +21,7 @@ #include "Plugins/Process/Linux/NativeProcessLinux.h" #include "Plugins/Process/Linux/Procfs.h" #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" +#include "Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.h" #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h" // System includes - They have to be included after framework includes because @@ -1152,4 +1153,16 @@ return expedited_reg_nums; } +llvm::Expected +NativeRegisterContextLinux_arm64::GetMemoryTaggingDetails(int32_t type) { + if (type == MemoryTagHandlerAArch64MTE::eMTE_allocation) { + return MemoryTaggingDetails{ + std::unique_ptr(new MemoryTagHandlerAArch64MTE), + PTRACE_PEEKMTETAGS, PTRACE_POKEMTETAGS}; + } + + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Unknown AArch64 memory tag type %d", type); +} + #endif // defined (__arm64__) || defined (__aarch64__) diff --git a/lldb/source/Plugins/Process/Utility/CMakeLists.txt b/lldb/source/Plugins/Process/Utility/CMakeLists.txt --- a/lldb/source/Plugins/Process/Utility/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Utility/CMakeLists.txt @@ -9,6 +9,7 @@ LinuxProcMaps.cpp LinuxSignals.cpp MipsLinuxSignals.cpp + MemoryTagHandlerAArch64MTE.cpp NativeRegisterContextRegisterInfo.cpp NativeRegisterContextWatchpoint_x86.cpp NetBSDSignals.cpp diff --git a/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.h b/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.h @@ -0,0 +1,34 @@ +//===-- MemoryTagHandlerAArch64MTE.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_PLUGINS_PROCESS_UTILITY_MEMORYTAGHANDLERAARCH64MTE_H +#define LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_MEMORYTAGHANDLERAARCH64MTE_H + +#include "lldb/Target/MemoryTagHandler.h" + +namespace lldb_private { + +class MemoryTagHandlerAArch64MTE : public MemoryTagHandler { +public: + // This enum is supposed to be shared for all of AArch64 but until + // there are more tag types than MTE, it will live here. + enum MTETagTypes { + eMTE_logical = 0, + eMTE_allocation = 1, + }; + + lldb::addr_t RemoveLogicalTag(lldb::addr_t addr) const override; + lldb::addr_t GetGranuleSize() const override; + TagRange AlignToGranules(TagRange range) const override; + int32_t GetAllocationTagType() const override; + size_t GetBytesPerTag() const override; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_MEMORYTAGHANDLERAARCH64MTE_H diff --git a/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.cpp b/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.cpp @@ -0,0 +1,52 @@ +//===-- MemoryTagHandlerAArch64MTE.cpp --------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "MemoryTagHandlerAArch64MTE.h" + +using namespace lldb_private; + +static const unsigned MTE_START_BIT = 56; +static const unsigned MTE_TAG_MAX = 0xf; +static const unsigned MTE_GRANULE_SIZE = 16; + +lldb::addr_t +MemoryTagHandlerAArch64MTE::RemoveLogicalTag(lldb::addr_t addr) const { + return addr & (~((lldb::addr_t)MTE_TAG_MAX << MTE_START_BIT)); +} + +lldb::addr_t MemoryTagHandlerAArch64MTE::GetGranuleSize() const { + return MTE_GRANULE_SIZE; +} + +int32_t MemoryTagHandlerAArch64MTE::GetAllocationTagType() const { + return eMTE_allocation; +} + +size_t MemoryTagHandlerAArch64MTE::GetBytesPerTag() const { return 1; } + +MemoryTagHandlerAArch64MTE::TagRange +MemoryTagHandlerAArch64MTE::AlignToGranules(TagRange range) const { + // Ignore reading a length of 0 + if (!range.IsValid()) + return range; + + const size_t granule = GetGranuleSize(); + + // Align start down to granule start + lldb::addr_t new_start = range.GetRangeBase(); + lldb::addr_t align_down_amount = new_start % granule; + new_start -= align_down_amount; + + // Account for the distance we moved the start above + size_t new_len = range.GetByteSize() + align_down_amount; + // Then align up to the end of the granule + if (new_len % granule) + new_len += (granule - (new_len % granule)); + + return TagRange(new_start, new_len); +} \ No newline at end of file diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp @@ -848,6 +848,9 @@ response.PutCString(";qXfer:auxv:read+"); response.PutCString(";qXfer:libraries-svr4:read+"); #endif +#if defined(__linux__) && defined(__aarch64__) + response.PutCString(";memory-tagging+"); +#endif return SendPacketNoLock(response.GetString()); } diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -201,6 +201,8 @@ PacketResult Handle_g(StringExtractorGDBRemote &packet); + PacketResult Handle_qMemTags(StringExtractorGDBRemote &packet); + void SetCurrentThreadID(lldb::tid_t tid); lldb::tid_t GetCurrentThreadID() const; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include "GDBRemoteCommunicationServerLLGS.h" @@ -207,6 +208,10 @@ RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_g, &GDBRemoteCommunicationServerLLGS::Handle_g); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_qMemTags, + &GDBRemoteCommunicationServerLLGS::Handle_qMemTags); + RegisterPacketHandler(StringExtractorGDBRemote::eServerPacketType_k, [this](StringExtractorGDBRemote packet, Status &error, bool &interrupt, bool &quit) { @@ -3511,6 +3516,71 @@ return SendOKResponse(); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_qMemTags( + StringExtractorGDBRemote &packet) { + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); + + // Ensure we have a process. + if (!m_debugged_process_up || + (m_debugged_process_up->GetID() == LLDB_INVALID_PROCESS_ID)) { + LLDB_LOGF( + log, + "GDBRemoteCommunicationServerLLGS::%s failed, no process available", + __FUNCTION__); + return SendErrorResponse(1); + } + + // We are expecting + // qMemTags:,: + + // Address + packet.SetFilePos(strlen("qMemTags:")); + const char *current_char = packet.Peek(); + if (!current_char || *current_char == ',') + return SendIllFormedResponse(packet, "Missing address in qMemTags packet"); + const lldb::addr_t addr = packet.GetHexMaxU64(/*little_endian=*/false, 0); + + // Length + char previous_char = packet.GetChar(); + current_char = packet.Peek(); + // If we don't have a separator or the length field is empty + if (previous_char != ',' || (current_char && *current_char == ':')) + return SendIllFormedResponse(packet, + "Invalid addr,length pair in qMemTags packet"); + + if (packet.GetBytesLeft() < 1) + return SendIllFormedResponse( + packet, "Too short qMemtags: packet (looking for length)"); + const size_t length = packet.GetHexMaxU64(/*little_endian=*/false, 0); + + // Type + const char *invalid_type_err = "Invalid type field in qMemTags: packet"; + if (packet.GetBytesLeft() < 1 || packet.GetChar() != ':') + return SendIllFormedResponse(packet, invalid_type_err); + + int32_t type = + packet.GetS32(std::numeric_limits::max(), /*base=*/16); + if (type == std::numeric_limits::max() || + // To catch inputs like "123aardvark" that will parse but clearly aren't + // valid in this case. + packet.GetBytesLeft()) { + return SendIllFormedResponse(packet, invalid_type_err); + } + + StreamGDBRemote response; + std::vector tags; + Status error = + m_debugged_process_up->ReadMemoryTags(type, addr, length, tags); + if (error.Fail()) { + return SendErrorResponse(1); + } + + response.PutChar('m'); + response.PutBytesAsRawHex8(tags.data(), tags.size()); + return SendPacketNoLock(response.GetString()); +} + void GDBRemoteCommunicationServerLLGS::MaybeCloseInferiorTerminalConnection() { Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -220,6 +220,8 @@ return eServerPacketType_qMemoryRegionInfoSupported; if (PACKET_STARTS_WITH("qModuleInfo:")) return eServerPacketType_qModuleInfo; + if (PACKET_STARTS_WITH("qMemTags:")) + return eServerPacketType_qMemTags; break; case 'P': diff --git a/lldb/test/API/tools/lldb-server/memory-tagging/Makefile b/lldb/test/API/tools/lldb-server/memory-tagging/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/memory-tagging/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/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py b/lldb/test/API/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py @@ -0,0 +1,102 @@ +import gdbremote_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +class TestGdbRemoteMemoryTagging(gdbremote_testcase.GdbRemoteTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + def check_qmemtags_response(self, body, expected): + self.test_sequence.add_log_lines(["read packet: $qMemTags:{}#00".format(body), + "send packet: ${}#00".format(expected), + ], + True) + self.expect_gdbremote_sequence() + + @skipUnlessArch("aarch64") + @skipUnlessPlatform(["linux"]) + @skipUnlessAArch64MTELinuxToolchain + def test_qmemtags_packets(self): + """ Test that qMemTags packets are parsed correctly and/or rejected. """ + + self.build() + self.set_inferior_startup_launch() + procs = self.prep_debug_monitor_and_inferior() + + # Run the process + self.test_sequence.add_log_lines( + [ + # Start running after initial stop + "read packet: $c#63", + # Match the address of the MTE page + {"type": "output_match", "regex": self.maybe_strict_output_regex(r"buffer: (.+)\r\n"), + "capture": {1: "buffer"}}, + # Now stop the inferior + "read packet: {}".format(chr(3)), + # And wait for the stop notification + {"direction": "send", "regex": r"^\$T[0-9a-fA-F]{2}thread:[0-9a-fA-F]+;"}], + True) + + # Run the packet stream + context = self.expect_gdbremote_sequence() + self.assertIsNotNone(context) + + # Grab the address + buf_address = context.get("buffer") + self.assertIsNotNone(buf_address) + + # nil means we couldn't set up a tagged page because the + # target doesn't support it. + if buf_address == "(nil)": + self.skipTest("Target must support MTE.") + + buf_address = int(buf_address, 16) + + # In the tests below E03 means the packet wasn't formed correctly + # and E01 means it was but we had some other error acting upon it. + + # Sanity check that address is correct + self.check_qmemtags_response("{:x},20:1".format(buf_address), "m0001") + + # Invalid packets + + # No content + self.check_qmemtags_response("", "E03") + # Only address + self.check_qmemtags_response("{:x}".format(buf_address), "E03") + # Only address and length + self.check_qmemtags_response("{:x},20".format(buf_address), "E03") + # Empty address + self.check_qmemtags_response(",20:1", "E03") + # Invalid addresses + self.check_qmemtags_response("aardvark,20:1", "E03") + self.check_qmemtags_response("-100,20:1", "E03") + self.check_qmemtags_response("cafe?,20:1", "E03") + # Empty length + self.check_qmemtags_response("{:x},:1".format(buf_address), "E03") + # Invalid lengths + self.check_qmemtags_response("{:x},food:1".format(buf_address), "E03") + self.check_qmemtags_response("{:x},-1:1".format(buf_address), "E03") + self.check_qmemtags_response("{:x},12??:1".format(buf_address), "E03") + # Empty type + self.check_qmemtags_response("{:x},10:".format(buf_address), "E03") + # Types we don't support + self.check_qmemtags_response("{:x},10:FF".format(buf_address), "E01") + # (even if the length of the read is zero) + self.check_qmemtags_response("{:x},0:FF".format(buf_address), "E01") + self.check_qmemtags_response("{:x},10:-1".format(buf_address), "E01") + self.check_qmemtags_response("{:x},10:+20".format(buf_address), "E01") + # Invalid type format + self.check_qmemtags_response("{:x},10:cat".format(buf_address), "E03") + self.check_qmemtags_response("{:x},10:?11".format(buf_address), "E03") + + # Valid packets + + # Reading nothing is allowed + self.check_qmemtags_response("{:x},0:1".format(buf_address), "m") + # A range that's already aligned + self.check_qmemtags_response("{:x},20:1".format(buf_address), "m0001") + # lldb-server should re-align the range + # Here we read from 1/2 way through a granule, into the next. Expands to 2 granules + self.check_qmemtags_response("{:x},10:1".format(buf_address+64-8), "m0304") diff --git a/lldb/test/API/tools/lldb-server/memory-tagging/main.c b/lldb/test/API/tools/lldb-server/memory-tagging/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/memory-tagging/main.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +void print_result(char *ptr) { + printf("buffer: %p\n", ptr); + // Wait for lldb-server to stop us + while (1) { + } +} + +int main(int argc, char const *argv[]) { + 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)) { + print_result(NULL); + } + + size_t page_size = sysconf(_SC_PAGESIZE); + char *buf = mmap(0, sysconf(_SC_PAGESIZE), PROT_READ | PROT_WRITE | PROT_MTE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (buf == MAP_FAILED) + print_result(NULL); + + // Set incrementing tags until end of the page + char *tagged_ptr = buf; + while (__arm_mte_ptrdiff(tagged_ptr, buf) < page_size) { + __arm_mte_set_tag(tagged_ptr); + // 16 byte granules, tags will wrap at 0xF + tagged_ptr = __arm_mte_increment_tag(tagged_ptr + 16, 1); + } + + print_result(buf); + + return 0; +} diff --git a/lldb/unittests/Process/Utility/CMakeLists.txt b/lldb/unittests/Process/Utility/CMakeLists.txt --- a/lldb/unittests/Process/Utility/CMakeLists.txt +++ b/lldb/unittests/Process/Utility/CMakeLists.txt @@ -17,7 +17,9 @@ add_lldb_unittest(ProcessUtilityTests RegisterContextTest.cpp LinuxProcMapsTest.cpp + MemoryTagHandlerAArch64MTETest.cpp ${PLATFORM_SOURCES} LINK_LIBS - lldbPluginProcessUtility) + lldbPluginProcessUtility + LLVMTestingSupport) diff --git a/lldb/unittests/Process/Utility/MemoryTagHandlerAArch64MTETest.cpp b/lldb/unittests/Process/Utility/MemoryTagHandlerAArch64MTETest.cpp new file mode 100644 --- /dev/null +++ b/lldb/unittests/Process/Utility/MemoryTagHandlerAArch64MTETest.cpp @@ -0,0 +1,65 @@ +//===-- MemoryTagHandlerAArch64MTETest.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 "Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace lldb_private; + +TEST(MemoryTagHandlerAArch64MTETest, RemoveLogicalTag) { + MemoryTagHandlerAArch64MTE handler; + + // Should not remove surrounding bits + ASSERT_EQ((lldb::addr_t)0xf0f00000ffffffff, + handler.RemoveLogicalTag(0xfef00000ffffffff)); + // Untagged pointers are unchanged + ASSERT_EQ((lldb::addr_t)0x1034567812345678, + handler.RemoveLogicalTag(0x1034567812345678)); +} + +TEST(MemoryTagHandlerAArch64MTETest, AlignToGranules) { + MemoryTagHandlerAArch64MTE handler; + // Reading nothing, no alignment needed + ASSERT_EQ( + MemoryTagHandlerAArch64MTE::TagRange(0, 0), + handler.AlignToGranules(MemoryTagHandlerAArch64MTE::TagRange(0, 0))); + + // Ranges with 0 size are unchanged even if address is non 0 + // (normally 0x1234 would be aligned to 0x1230) + ASSERT_EQ( + MemoryTagHandlerAArch64MTE::TagRange(0x1234, 0), + handler.AlignToGranules(MemoryTagHandlerAArch64MTE::TagRange(0x1234, 0))); + + // Ranges already aligned don't change + ASSERT_EQ( + MemoryTagHandlerAArch64MTE::TagRange(0x100, 64), + handler.AlignToGranules(MemoryTagHandlerAArch64MTE::TagRange(0x100, 64))); + + // Reading less than 1 granule, rounds up to 1 granule + ASSERT_EQ( + MemoryTagHandlerAArch64MTE::TagRange(0, 16), + handler.AlignToGranules(MemoryTagHandlerAArch64MTE::TagRange(0, 1))); + + // Start address is aligned down, and length modified accordingly + // Here bytes 8 through 24 straddle 2 granules. So the resulting range starts + // at 0 and covers 32 bytes. + ASSERT_EQ( + MemoryTagHandlerAArch64MTE::TagRange(0, 32), + handler.AlignToGranules(MemoryTagHandlerAArch64MTE::TagRange(8, 16))); + + // Here only the size of the range needs aligning + ASSERT_EQ( + MemoryTagHandlerAArch64MTE::TagRange(16, 32), + handler.AlignToGranules(MemoryTagHandlerAArch64MTE::TagRange(16, 24))); + + // Start and size need aligning here but we only need 1 granule to cover it + ASSERT_EQ( + MemoryTagHandlerAArch64MTE::TagRange(16, 16), + handler.AlignToGranules(MemoryTagHandlerAArch64MTE::TagRange(18, 4))); +} \ No newline at end of file