diff --git a/lldb/include/lldb/Core/Architecture.h b/lldb/include/lldb/Core/Architecture.h --- a/lldb/include/lldb/Core/Architecture.h +++ b/lldb/include/lldb/Core/Architecture.h @@ -10,6 +10,7 @@ #define LLDB_CORE_ARCHITECTURE_H #include "lldb/Core/PluginInterface.h" +#include "lldb/Target/MemoryTagHandler.h" namespace lldb_private { @@ -97,6 +98,17 @@ Target &target) const { return addr; } + + // Returns a pointer to an object that can handle memory tags for this + // Architecture E.g. masking out tags, unpacking tag streams etc. Returns + // nullptr if the architecture does not have a memory tagging extension. + // + // The return pointer being valid does not mean that the current process has + // memory tagging enabled, just that a tagging technology exists for this + // architecture. + virtual const MemoryTagHandler *GetMemoryTagHandler() const { + return nullptr; + } }; } // namespace lldb_private diff --git a/lldb/include/lldb/Target/MemoryTagHandler.h b/lldb/include/lldb/Target/MemoryTagHandler.h --- a/lldb/include/lldb/Target/MemoryTagHandler.h +++ b/lldb/include/lldb/Target/MemoryTagHandler.h @@ -29,9 +29,30 @@ public: typedef Range TagRange; + // Extract the logical tag from a pointer + // The tag is returned as a plain value, with any shifts removed. + // For example if your tags are stored in bits 56-60 then the logical tag + // you get will have been shifted down 56 before being returned. + virtual lldb::addr_t GetLogicalTag(lldb::addr_t addr) const = 0; + // Remove the logical tag, returning the untagged pointer virtual lldb::addr_t RemoveLogicalTag(lldb::addr_t addr) const = 0; + // Set a new logical tag in a pointer, overwriting any existing tag + // The new tag does not need to be shifted, that is done for you. + // Errors if the tag is out of the valid tag value range + virtual llvm::Expected + SetLogicalTag(lldb::addr_t addr, lldb::addr_t tag) const = 0; + + // Copy the logical tag from one pointer to another. + virtual lldb::addr_t CopyLogicalTag(lldb::addr_t from, + lldb::addr_t to) const = 0; + + // Return the difference between two addresses, ignoring any logical tags they + // have. + virtual ptrdiff_t AddressDiff(lldb::addr_t addr1, + lldb::addr_t addr2) const = 0; + // Return the number of bytes a single tag covers virtual lldb::addr_t GetGranuleSize() const = 0; @@ -60,6 +81,13 @@ // transport. virtual size_t GetBytesPerTag() const = 0; + // Unpack tags from their stored format (e.g. gdb qMemTags data) into seperate + // tags. Checks that each tag is within the expected value range and that the + // number of tags found matches the number of granules we originally asked + // for. + virtual llvm::Expected> + UnpackTags(const std::vector &tags, size_t granules) const = 0; + virtual ~MemoryTagHandler() {} }; diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -1695,6 +1695,44 @@ lldb::addr_t CallocateMemory(size_t size, uint32_t permissions, Status &error); + /// If the address range given is in a memory tagged range and this + /// architecture and process supports memory tagging, return a tag + /// handler that can be used to maniupulate those memory tags. + /// Tags present in the addresses given are ignored. + /// + /// \param[in] addr + /// Start of memory range. + /// + /// \param[in] end_addr + /// End of the memory range. Where end is one beyond the last byte to be + /// included. + /// + /// \return + /// Either a valid pointer to a tag handler or an error describing why one + /// could not be provided. + llvm::Expected + GetMemoryTagHandler(lldb::addr_t addr, lldb::addr_t end_addr); + + /// Expands the range addr to addr+len to align with granule boundaries and + /// then calls DoReadMemoryTags to do the target specific operations. + /// Tags are returned unpacked so can be used without conversion. + /// + /// \param[in] tag_handler + /// The tag_handler to get memory tagging information from. + /// + /// \param[in] addr + /// Start of memory range to tags for. + /// + /// \param[in] len + /// Length of memory range to read tags for (in bytes). + /// + /// \return + /// Either the unpacked tags or an error describing a failure to read + /// or unpack them. + llvm::Expected> + ReadMemoryTags(const MemoryTagHandler *tag_handler, lldb::addr_t addr, + size_t len); + /// Resolve dynamically loaded indirect functions. /// /// \param[in] address @@ -2708,6 +2746,36 @@ /// false. bool RouteAsyncStructuredData(const StructuredData::ObjectSP object_sp); + /// Check whether the remote supports memory tagging. + /// + /// \return + /// true if the remote supports memory tagging, + /// false otherwise. + virtual bool SupportsMemoryTagging() { return false; } + + /// Does the final operation to read memory tags. E.g. sending a GDB packet. + /// It assumes that ReadMemoryTags has checked that memory tagging is enabled + /// and has expanded the memory range as needed. + /// + /// \param[in] addr + /// Start of address range to read memory tags for. + /// + /// \param[in] len + /// Length of the memory range to read tags for (in bytes). + /// + /// \param[in] type + /// Type of tags to read (get this from a MemoryTagHandler) + /// + /// \return + /// The packed tag data received from the remote or an error + /// if the read failed. + virtual llvm::Expected> + DoReadMemoryTags(lldb::addr_t addr, size_t len, int32_t type) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "%s does not support reading memory tags", + GetPluginName().GetCString()); + } + // Type definitions typedef std::map LanguageRuntimeCollection; diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -1269,7 +1269,7 @@ return True return False - def isAArch64SVE(self): + def hasLinuxCPUInfoFeature(self, feature): triple = self.dbg.GetSelectedPlatform().GetTriple() # TODO other platforms, please implement this function @@ -1290,7 +1290,19 @@ except: return False - return " sve " in cpuinfo + features = [] + for line in cpuinfo.splitlines(): + if line.startswith("Features"): + features = line.split(':')[1].split() + break + + return feature in features + + def isAArch64SVE(self): + return self.hasLinuxCPUInfoFeature("sve") + + def isAArch64MTE(self): + return self.hasLinuxCPUInfoFeature("mte") def hasLinuxVmFlags(self): """ Check that the target machine has "VmFlags" lines in 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,123 @@ +//===-- 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_handler_or_err = + process->GetMemoryTagHandler(start_addr, end_addr); + + if (!tag_handler_or_err) { + result.SetError(Status(tag_handler_or_err.takeError())); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const MemoryTagHandler *tag_handler = *tag_handler_or_err; + ptrdiff_t len = tag_handler->AddressDiff(end_addr, start_addr); + llvm::Expected> tags = + process->ReadMemoryTags(tag_handler, start_addr, len); + + if (!tags) { + result.SetError(Status(tags.takeError())); + result.SetStatus(eReturnStatusFailed); + return false; + } + + result.AppendMessageWithFormatv("Logical tag: {0:x}", + tag_handler->GetLogicalTag(start_addr)); + result.AppendMessage("Allocation tags:"); + + MemoryTagHandler::TagRange initial_range(start_addr, len); + addr_t addr = tag_handler->AlignToGranules(initial_range).GetRangeBase(); + auto tags_end = tags->end(); + for (auto tag = tags->begin(); tag != tags_end; ++tag) { + addr_t next_addr = addr + tag_handler->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/source/Plugins/Architecture/AArch64/ArchitectureAArch64.h b/lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.h @@ -0,0 +1,40 @@ +//===-- ArchitectureAArch64.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_ARCHITECTURE_AARCH64_ARCHITECTUREAARCH64_H +#define LLDB_SOURCE_PLUGINS_ARCHITECTURE_AARCH64_ARCHITECTUREAARCH64_H + +#include "Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.h" +#include "lldb/Core/Architecture.h" + +namespace lldb_private { + +class ArchitectureAArch64 : public Architecture { +public: + static ConstString GetPluginNameStatic(); + static void Initialize(); + static void Terminate(); + + ConstString GetPluginName() override; + uint32_t GetPluginVersion() override; + + void OverrideStopInfo(Thread &thread) const override{}; + + const MemoryTagHandler *GetMemoryTagHandler() const override { + return &m_memory_tag_handler; + } + +private: + static std::unique_ptr Create(const ArchSpec &arch); + ArchitectureAArch64() = default; + MemoryTagHandlerAArch64MTE m_memory_tag_handler; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_ARCHITECTURE_AARCH64_ARCHITECTUREAARCH64_H diff --git a/lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.cpp b/lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.cpp @@ -0,0 +1,45 @@ +//===-- ArchitectureAArch64.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/Architecture/AArch64/ArchitectureAArch64.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Utility/ArchSpec.h" + +using namespace lldb_private; +using namespace lldb; + +LLDB_PLUGIN_DEFINE(ArchitectureAArch64) + +ConstString ArchitectureAArch64::GetPluginNameStatic() { + return ConstString("aarch64"); +} + +void ArchitectureAArch64::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + "AArch64-specific algorithms", + &ArchitectureAArch64::Create); +} + +void ArchitectureAArch64::Terminate() { + PluginManager::UnregisterPlugin(&ArchitectureAArch64::Create); +} + +std::unique_ptr +ArchitectureAArch64::Create(const ArchSpec &arch) { + auto machine = arch.GetMachine(); + if (machine != llvm::Triple::aarch64 && machine != llvm::Triple::aarch64_be && + machine != llvm::Triple::aarch64_32) { + return nullptr; + } + return std::unique_ptr(new ArchitectureAArch64()); +} + +ConstString ArchitectureAArch64::GetPluginName() { + return GetPluginNameStatic(); +} +uint32_t ArchitectureAArch64::GetPluginVersion() { return 1; } diff --git a/lldb/source/Plugins/Architecture/AArch64/CMakeLists.txt b/lldb/source/Plugins/Architecture/AArch64/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Architecture/AArch64/CMakeLists.txt @@ -0,0 +1,11 @@ +add_lldb_library(lldbPluginArchitectureAArch64 PLUGIN + ArchitectureAArch64.cpp + + LINK_LIBS + lldbPluginProcessUtility + lldbCore + lldbTarget + lldbUtility + LINK_COMPONENTS + Support + ) diff --git a/lldb/source/Plugins/Architecture/CMakeLists.txt b/lldb/source/Plugins/Architecture/CMakeLists.txt --- a/lldb/source/Plugins/Architecture/CMakeLists.txt +++ b/lldb/source/Plugins/Architecture/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(Arm) add_subdirectory(Mips) add_subdirectory(PPC64) +add_subdirectory(AArch64) diff --git a/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.h b/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.h --- a/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.h +++ b/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.h @@ -22,11 +22,19 @@ eMTE_allocation = 1, }; + lldb::addr_t GetLogicalTag(lldb::addr_t addr) const override; lldb::addr_t RemoveLogicalTag(lldb::addr_t addr) const override; + llvm::Expected SetLogicalTag(lldb::addr_t addr, + lldb::addr_t tag) const override; + lldb::addr_t CopyLogicalTag(lldb::addr_t from, + lldb::addr_t to) const override; + ptrdiff_t AddressDiff(lldb::addr_t addr1, lldb::addr_t addr2) const override; lldb::addr_t GetGranuleSize() const override; TagRange AlignToGranules(TagRange range) const override; int32_t GetAllocationTagType() const override; size_t GetBytesPerTag() const override; + llvm::Expected> + UnpackTags(const std::vector &tags, size_t granules) const override; }; } // namespace lldb_private diff --git a/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.cpp b/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.cpp --- a/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.cpp +++ b/lldb/source/Plugins/Process/Utility/MemoryTagHandlerAArch64MTE.cpp @@ -15,10 +15,36 @@ static const unsigned MTE_GRANULE_SIZE = 16; lldb::addr_t +MemoryTagHandlerAArch64MTE::GetLogicalTag(lldb::addr_t addr) const { + return (addr >> MTE_START_BIT) & MTE_TAG_MAX; +} + +lldb::addr_t MemoryTagHandlerAArch64MTE::RemoveLogicalTag(lldb::addr_t addr) const { return addr & (~((lldb::addr_t)MTE_TAG_MAX << MTE_START_BIT)); } +llvm::Expected +MemoryTagHandlerAArch64MTE::SetLogicalTag(lldb::addr_t addr, + lldb::addr_t tag) const { + if (tag > MTE_TAG_MAX) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "MTE logical tag value is > MTE max value of 0x%x.", MTE_TAG_MAX); + + return RemoveLogicalTag(addr) | (tag << MTE_START_BIT); +} + +lldb::addr_t MemoryTagHandlerAArch64MTE::CopyLogicalTag(lldb::addr_t from, + lldb::addr_t to) const { + return RemoveLogicalTag(to) | (GetLogicalTag(from) << MTE_START_BIT); +} + +ptrdiff_t MemoryTagHandlerAArch64MTE::AddressDiff(lldb::addr_t addr1, + lldb::addr_t addr2) const { + return RemoveLogicalTag(addr1) - RemoveLogicalTag(addr2); +} + lldb::addr_t MemoryTagHandlerAArch64MTE::GetGranuleSize() const { return MTE_GRANULE_SIZE; } @@ -49,4 +75,35 @@ new_len += (granule - (new_len % granule)); return TagRange(new_start, new_len); -} \ No newline at end of file +} + +llvm::Expected> +MemoryTagHandlerAArch64MTE::UnpackTags(const std::vector &tags, + size_t granules) const { + size_t num_tags = tags.size() / GetBytesPerTag(); + if (num_tags != granules) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Packed tag data size does not match expected number of tags. " + "Expected %" PRIu64 " tag(s) for %" PRIu64 " granules, got %" PRIu64 + " tag(s).", + granules, granules, num_tags); + } + + // (if bytes per tag was not 1, we would reconstruct them here) + + std::vector unpacked; + unpacked.reserve(tags.size()); + for (auto it = tags.begin(); it != tags.end(); ++it) { + // Check all tags are in range + if (*it > MTE_TAG_MAX) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Found tag 0x%x which is > max MTE tag value of 0x%x.", *it, + MTE_TAG_MAX); + } + unpacked.push_back(*it); + } + + return unpacked; +} diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -446,6 +446,11 @@ bool GetSharedCacheInfoSupported(); + bool GetMemoryTaggingSupported(); + + lldb::DataBufferSP ReadMemoryTags(lldb::addr_t addr, size_t len, + int32_t type); + /// Use qOffsets to query the offset used when relocating the target /// executable. If successful, the returned structure will contain at least /// one value in the offsets field. @@ -558,6 +563,7 @@ LazyBool m_supports_jGetSharedCacheInfo; LazyBool m_supports_QPassSignals; LazyBool m_supports_error_string_reply; + LazyBool m_supports_memory_tagging; bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1, m_supports_qUserName : 1, m_supports_qGroupName : 1, diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -89,6 +89,7 @@ m_supports_jGetSharedCacheInfo(eLazyBoolCalculate), m_supports_QPassSignals(eLazyBoolCalculate), m_supports_error_string_reply(eLazyBoolCalculate), + m_supports_memory_tagging(eLazyBoolCalculate), m_supports_qProcessInfoPID(true), m_supports_qfProcessInfo(true), m_supports_qUserName(true), m_supports_qGroupName(true), m_supports_qThreadStopInfo(true), m_supports_z0(true), @@ -342,6 +343,7 @@ m_supports_augmented_libraries_svr4_read = eLazyBoolNo; m_supports_qXfer_features_read = eLazyBoolNo; m_supports_qXfer_memory_map_read = eLazyBoolNo; + m_supports_memory_tagging = eLazyBoolNo; m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if // not, we assume no limit @@ -433,6 +435,9 @@ else m_supports_QPassSignals = eLazyBoolNo; + if (::strstr(response_cstr, "memory-tagging+")) + m_supports_memory_tagging = eLazyBoolYes; + const char *packet_size_str = ::strstr(response_cstr, "PacketSize="); if (packet_size_str) { StringExtractorGDBRemote packet_response(packet_size_str + @@ -646,6 +651,57 @@ return m_supports_jGetSharedCacheInfo; } +bool GDBRemoteCommunicationClient::GetMemoryTaggingSupported() { + if (m_supports_memory_tagging == eLazyBoolCalculate) { + GetRemoteQSupported(); + } + return m_supports_memory_tagging == eLazyBoolYes; +} + +DataBufferSP GDBRemoteCommunicationClient::ReadMemoryTags(lldb::addr_t addr, + size_t len, + int32_t type) { + StreamString packet; + packet.Printf("qMemTags:%lx,%lx:%x", addr, len, type); + StringExtractorGDBRemote response; + + Log *log = ProcessGDBRemoteLog::GetLogIfAnyCategoryIsSet(GDBR_LOG_MEMORY); + + if (SendPacketAndWaitForResponse(packet.GetString(), response, false) != + PacketResult::Success || + !response.IsNormalResponse()) { + LLDB_LOGF(log, "GDBRemoteCommunicationClient::%s: qMemTags packet failed", + __FUNCTION__); + return nullptr; + } + + // We are expecting + // m + + if (response.GetChar() != 'm') { + LLDB_LOGF(log, + "GDBRemoteCommunicationClient::%s: qMemTags response did not " + "begin with \"m\"", + __FUNCTION__); + return nullptr; + } + + size_t expected_bytes = response.GetBytesLeft() / 2; + DataBufferSP buffer_sp(new DataBufferHeap(expected_bytes, 0)); + size_t got_bytes = response.GetHexBytesAvail(buffer_sp->GetData()); + // Check both because in some situations chars are consumed even + // if the decoding fails. + if (response.GetBytesLeft() || (expected_bytes != got_bytes)) { + LLDB_LOGF( + log, + "GDBRemoteCommunicationClient::%s: Invalid data in qMemTags response", + __FUNCTION__); + return nullptr; + } + + return buffer_sp; +} + bool GDBRemoteCommunicationClient::GetxPacketSupported() { if (m_supports_x == eLazyBoolCalculate) { StringExtractorGDBRemote response; diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -239,6 +239,8 @@ friend class GDBRemoteCommunicationClient; friend class GDBRemoteRegisterContext; + bool SupportsMemoryTagging() override; + /// Broadcaster event bits definitions. enum { eBroadcastBitAsyncContinue = (1 << 0), @@ -410,6 +412,9 @@ bool HasErased(FlashRange range); + llvm::Expected> + DoReadMemoryTags(lldb::addr_t addr, size_t len, int32_t type) override; + private: // For ProcessGDBRemote only std::string m_partial_profile_data; diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -2757,6 +2757,29 @@ return 0; } +bool ProcessGDBRemote::SupportsMemoryTagging() { + return m_gdb_comm.GetMemoryTaggingSupported(); +} + +llvm::Expected> +ProcessGDBRemote::DoReadMemoryTags(lldb::addr_t addr, size_t len, + int32_t type) { + // By this point ReadMemoryTags has validated that tagging is enabled + // for this target/process/address. + DataBufferSP buffer_sp = m_gdb_comm.ReadMemoryTags(addr, len, type); + if (!buffer_sp) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Error reading memory tags from remote"); + } + + // Return the raw tag data + llvm::ArrayRef tag_data = buffer_sp->GetData(); + std::vector got; + got.reserve(tag_data.size()); + std::copy(tag_data.begin(), tag_data.end(), std::back_inserter(got)); + return got; +} + Status ProcessGDBRemote::WriteObjectFile( std::vector entries) { Status error; diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -6023,3 +6023,70 @@ return false; } + +llvm::Expected +Process::GetMemoryTagHandler(lldb::addr_t addr, lldb::addr_t end_addr) { + Architecture *arch = GetTarget().GetArchitecturePlugin(); + const MemoryTagHandler *tag_handler = + arch ? arch->GetMemoryTagHandler() : nullptr; + if (!arch || !tag_handler) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "This architecture does not support memory tagging", + GetPluginName().GetCString()); + } + + if (!SupportsMemoryTagging()) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Process does not support memory tagging"); + } + + ptrdiff_t len = tag_handler->AddressDiff(end_addr, addr); + if (len <= 0) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "End address (0x%x) must be greater than the start address (0x%x)", + end_addr, addr); + } + + MemoryRegionInfo region; + // Region lookup is not tag aware so untag here + lldb::addr_t untagged_addr = tag_handler->RemoveLogicalTag(addr); + Status status = GetMemoryRegionInfo(untagged_addr, region); + + MemoryRegionInfo::RangeType tag_range(untagged_addr, len); + tag_range = tag_handler->AlignToGranules(tag_range); + + if (status.Fail() || region.GetMemoryTagged() != MemoryRegionInfo::eYes || + !region.GetRange().Contains(tag_range)) { + // Addresses here will be untagged + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Address range 0x%lx:0x%lx is not in a memory tagged region", + tag_range.GetRangeBase(), tag_range.GetRangeEnd()); + } + + return tag_handler; +} + +llvm::Expected> +Process::ReadMemoryTags(const MemoryTagHandler *tag_handler, lldb::addr_t addr, + size_t len) { + if (!tag_handler) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "A memory tag handler is required for reading memory tags."); + } + + MemoryTagHandler::TagRange range(tag_handler->RemoveLogicalTag(addr), len); + range = tag_handler->AlignToGranules(range); + + llvm::Expected> tag_data = + DoReadMemoryTags(range.GetRangeBase(), range.GetByteSize(), + tag_handler->GetAllocationTagType()); + if (!tag_data) + return tag_data.takeError(); + + return tag_handler->UnpackTags(*tag_data, range.GetByteSize() / + tag_handler->GetGranuleSize()); +} 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"]) + @skipUnlessAArch64MTELinuxToolchain + 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,50 @@ +#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; + 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); + } + + // 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)); + + // Breakpoint here + return 0; +} diff --git a/lldb/unittests/Process/Utility/MemoryTagHandlerAArch64MTETest.cpp b/lldb/unittests/Process/Utility/MemoryTagHandlerAArch64MTETest.cpp --- a/lldb/unittests/Process/Utility/MemoryTagHandlerAArch64MTETest.cpp +++ b/lldb/unittests/Process/Utility/MemoryTagHandlerAArch64MTETest.cpp @@ -12,6 +12,46 @@ using namespace lldb_private; +TEST(MemoryTagHandlerAArch64MTETest, UnpackTags) { + MemoryTagHandlerAArch64MTE handler; + + // Error for insufficient tag data + std::vector input; + ASSERT_THAT_EXPECTED( + handler.UnpackTags(input, 2), + llvm::FailedWithMessage( + "Packed tag data size does not match expected number of tags. " + "Expected 2 tag(s) for 2 granules, got 0 tag(s).")); + + // This is out of the valid tag range + input.push_back(0x1f); + ASSERT_THAT_EXPECTED( + handler.UnpackTags(input, 1), + llvm::FailedWithMessage( + "Found tag 0x1f which is > max MTE tag value of 0xf.")); + + // MTE tags are 1 per byte + input.pop_back(); + input.push_back(0xe); + input.push_back(0xf); + + std::vector expected{0xe, 0xf}; + + llvm::Expected> got = handler.UnpackTags(input, 2); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_THAT(expected, testing::ContainerEq(*got)); +} + +TEST(MemoryTagHandlerAArch64MTETest, GetLogicalTag) { + MemoryTagHandlerAArch64MTE handler; + + // Set surrounding bits to check shift is correct + ASSERT_EQ((lldb::addr_t)0, handler.GetLogicalTag(0xe0e00000ffffffff)); + // Max tag value + ASSERT_EQ((lldb::addr_t)0xf, handler.GetLogicalTag(0x0f000000ffffffff)); + ASSERT_EQ((lldb::addr_t)2, handler.GetLogicalTag(0x02000000ffffffff)); +} + TEST(MemoryTagHandlerAArch64MTETest, RemoveLogicalTag) { MemoryTagHandlerAArch64MTE handler; @@ -23,6 +63,54 @@ handler.RemoveLogicalTag(0x1034567812345678)); } +TEST(MemoryTagHandlerAArch64MTETest, SetLogicalTag) { + MemoryTagHandlerAArch64MTE handler; + + // Error if tag > mte range + ASSERT_THAT_EXPECTED(handler.SetLogicalTag(0x0, 0x1f), + llvm::FailedWithMessage( + "MTE logical tag value is > MTE max value of 0xf.")); + + // Surrounding bits should be unchanged + llvm::Expected got = + handler.SetLogicalTag(0xe0e00000ffffffff, 0xf); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_EQ(0xefe00000ffffffff, *got); + + // Old tag removed first, does not change the result + llvm::Expected g1 = + handler.SetLogicalTag(0x00000000ffffffff, 0xa); + ASSERT_THAT_EXPECTED(g1, llvm::Succeeded()); + llvm::Expected g2 = + handler.SetLogicalTag(0x0f000000ffffffff, 0xa); + ASSERT_THAT_EXPECTED(g2, llvm::Succeeded()); + ASSERT_EQ(*g1, *g2); + + // Should roundtrip with the other methods + lldb::addr_t tag = 0xe; + lldb::addr_t addr = 0x1011222233334444; + llvm::Expected set = handler.SetLogicalTag(addr, tag); + ASSERT_THAT_EXPECTED(set, llvm::Succeeded()); + + ASSERT_EQ(tag, handler.GetLogicalTag(*set)); + ASSERT_EQ(addr, handler.RemoveLogicalTag(*set)); +} + +TEST(MemoryTagHandlerAArch64MTETest, CopyLogicalTag) { + MemoryTagHandlerAArch64MTE handler; + + // Round trips + lldb::addr_t addr = 0x1111222233334444; + ASSERT_EQ(addr, handler.CopyLogicalTag(addr, addr)); + addr = 0x1011222233334444; + ASSERT_EQ(addr, handler.CopyLogicalTag(addr, addr)); + + ASSERT_EQ((lldb::addr_t)0xf0ffeeeeddddcccc, + handler.CopyLogicalTag(0x1011222233334444, 0xffffeeeeddddcccc)); + ASSERT_EQ((lldb::addr_t)0x0e00111122223333, + handler.CopyLogicalTag(0xfeffffffffffffff, 0x0000111122223333)); +} + TEST(MemoryTagHandlerAArch64MTETest, AlignToGranules) { MemoryTagHandlerAArch64MTE handler; // Reading nothing, no alignment needed @@ -62,4 +150,17 @@ ASSERT_EQ( MemoryTagHandlerAArch64MTE::TagRange(16, 16), handler.AlignToGranules(MemoryTagHandlerAArch64MTE::TagRange(18, 4))); -} \ No newline at end of file +} + +TEST(MemoryTagHandlerAArch64MTETest, AddressDiff) { + MemoryTagHandlerAArch64MTE handler; + + ASSERT_EQ(0, handler.AddressDiff(0, 0)); + // Result is signed + ASSERT_EQ(10, handler.AddressDiff(10, 0)); + ASSERT_EQ(-10, handler.AddressDiff(0, 10)); + // Tag bits ignored + ASSERT_EQ(0, handler.AddressDiff(0x1911222233334444, 0x1811222233334444)); + ASSERT_EQ(-32, handler.AddressDiff(0x1911222233334400, 0x1811222233334420)); + ASSERT_EQ(65, handler.AddressDiff(0x1011222233334441, 0x1711222233334400)); +} diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp --- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -657,3 +657,68 @@ EXPECT_EQ(llvm::None, GetQOffsets("TextSeg=0x1234")); EXPECT_EQ(llvm::None, GetQOffsets("TextSeg=12345678123456789")); } + +static void +check_qmemtags(TestClient &client, MockServer &server, size_t read_len, + const char *packet, llvm::StringRef response, + llvm::Optional> expected_tag_data) { + const auto &ReadMemoryTags = [&](size_t len, const char *packet, + llvm::StringRef response) { + std::future result = std::async(std::launch::async, [&] { + return client.ReadMemoryTags(0xDEF0, read_len, 1); + }); + + HandlePacket(server, packet, response); + return result.get(); + }; + + auto result = ReadMemoryTags(0, packet, response); + if (expected_tag_data) { + ASSERT_TRUE(result); + llvm::ArrayRef expected(*expected_tag_data); + llvm::ArrayRef got = result->GetData(); + ASSERT_THAT(expected, testing::ContainerEq(got)); + } else { + ASSERT_FALSE(result); + } +} + +TEST_F(GDBRemoteCommunicationClientTest, ReadMemoryTags) { + // Zero length reads are valid + check_qmemtags(client, server, 0, "qMemTags:def0,0:1", "m", + std::vector{}); + + // The client layer does not check the length of the received data. + // All we need is the "m" and for the decode to use all of the chars + check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m09", + std::vector{0x9}); + + // Zero length response is fine as long as the "m" is present + check_qmemtags(client, server, 0, "qMemTags:def0,0:1", "m", + std::vector{}); + + // Normal responses + check_qmemtags(client, server, 16, "qMemTags:def0,10:1", "m66", + std::vector{0x66}); + check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m0102", + std::vector{0x1, 0x2}); + + // Empty response is an error + check_qmemtags(client, server, 17, "qMemTags:def0,11:1", "", llvm::None); + // Usual error response + check_qmemtags(client, server, 17, "qMemTags:def0,11:1", "E01", llvm::None); + // Leading m missing + check_qmemtags(client, server, 17, "qMemTags:def0,11:1", "01", llvm::None); + // Anything other than m is an error + check_qmemtags(client, server, 17, "qMemTags:def0,11:1", "z01", llvm::None); + // Decoding tag data doesn't use all the chars in the packet + check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m09zz", llvm::None); + // Data that is not hex bytes + check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "mhello", + llvm::None); + // Data is not a complete hex char + check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m9", llvm::None); + // Data has a trailing hex char + check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m01020", + llvm::None); +}