From 5d3436200147a999670f754288a03c4ac5a15aeb Mon Sep 17 00:00:00 2001 From: David Spickett Date: Fri, 19 Feb 2021 15:57:29 +0000 Subject: [PATCH] [lldb][AArch64] Add MTE memory tag reading to lldb This adds GDB client support for the qMemTags packet which reads memory tags. Following the design which was recently committed to GDB. https://sourceware.org/gdb/current/onlinedocs/gdb/General-Query-Packets.html#General-Query-Packets (look for qMemTags) lldb commands will use the new Process methods GetMemoryTagManager and ReadMemoryTags. The former takes a range and checks that: * The current process architecture has an architecture plugin * That plugin provides a MemoryTagManager * That the range of memory requested lies in a tagged range (it will expand it to granules for you) If all that was true you get a MemoryTagManager you can give to ReadMemoryTags. This two step process is done to allow commands to get the tag manager without having to read tags as well. For example you might just want to remove a logical tag, or error early if a range with tagged addresses is inverted. Note that getting a MemoryTagManager doesn't mean that the process or a specific memory range is tagged. Those are seperate checks. Having a tag manager just means this architecture *could* have a tagging feature enabled. An architecture plugin has been added for AArch64 which will return a MemoryTagManagerAArch64MTE, which was added in a previous patch. Reviewed By: omjavaid Differential Revision: https://reviews.llvm.org/D95602 --- lldb/include/lldb/Core/Architecture.h | 12 +++ lldb/include/lldb/Target/Process.h | 62 ++++++++++++++ .../AArch64/ArchitectureAArch64.cpp | 45 +++++++++++ .../AArch64/ArchitectureAArch64.h | 40 +++++++++ .../Architecture/AArch64/CMakeLists.txt | 11 +++ .../Plugins/Architecture/CMakeLists.txt | 1 + .../GDBRemoteCommunicationClient.cpp | 44 ++++++++++ .../gdb-remote/GDBRemoteCommunicationClient.h | 3 + .../Process/gdb-remote/ProcessGDBRemote.cpp | 19 +++++ .../Process/gdb-remote/ProcessGDBRemote.h | 3 + lldb/source/Target/Process.cpp | 81 +++++++++++++++++++ .../GDBRemoteCommunicationClientTest.cpp | 65 +++++++++++++++ 12 files changed, 386 insertions(+) create mode 100644 lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.cpp create mode 100644 lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.h create mode 100644 lldb/source/Plugins/Architecture/AArch64/CMakeLists.txt diff --git a/lldb/include/lldb/Core/Architecture.h b/lldb/include/lldb/Core/Architecture.h index 2ea8bd31ebf4..b68bf27ae0df 100644 --- 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/MemoryTagManager.h" namespace lldb_private { @@ -97,6 +98,17 @@ public: Target &target) const { return addr; } + + // Returns a pointer to an object that can manage 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 MemoryTagManager *GetMemoryTagManager() const { + return nullptr; + } }; } // namespace lldb_private diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index c849bd776601..ba51056b1c9e 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -34,6 +34,7 @@ #include "lldb/Target/ExecutionContextScope.h" #include "lldb/Target/InstrumentationRuntime.h" #include "lldb/Target/Memory.h" +#include "lldb/Target/MemoryTagManager.h" #include "lldb/Target/QueueList.h" #include "lldb/Target/ThreadList.h" #include "lldb/Target/ThreadPlanStack.h" @@ -1709,6 +1710,44 @@ public: 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 + /// manager 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 manager or an error describing why one + /// could not be provided. + llvm::Expected + GetMemoryTagManager(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_manager + /// The tag manager to get memory tagging information from. + /// + /// \param[in] addr + /// Start of memory range to read 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 MemoryTagManager *tag_manager, lldb::addr_t addr, + size_t len); + /// Resolve dynamically loaded indirect functions. /// /// \param[in] address @@ -2728,6 +2767,29 @@ protected: /// 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 MemoryTagManager) + /// + /// \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/source/Plugins/Architecture/AArch64/ArchitectureAArch64.cpp b/lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.cpp new file mode 100644 index 000000000000..9994cc293d6a --- /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/ArchitectureAArch64.h b/lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.h new file mode 100644 index 000000000000..775478cc9338 --- /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/MemoryTagManagerAArch64MTE.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 MemoryTagManager *GetMemoryTagManager() const override { + return &m_memory_tag_manager; + } + +private: + static std::unique_ptr Create(const ArchSpec &arch); + ArchitectureAArch64() = default; + MemoryTagManagerAArch64MTE m_memory_tag_manager; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_ARCHITECTURE_AARCH64_ARCHITECTUREAARCH64_H diff --git a/lldb/source/Plugins/Architecture/AArch64/CMakeLists.txt b/lldb/source/Plugins/Architecture/AArch64/CMakeLists.txt new file mode 100644 index 000000000000..9bcf99318622 --- /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 index 14ad91644595..9ed8edf70af3 100644 --- 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/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp index db50afcfa33d..a7fcf084fa60 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -586,6 +586,50 @@ bool GDBRemoteCommunicationClient::GetMemoryTaggingSupported() { 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/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h index 599020e4e78f..fa67a6c69a53 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -453,6 +453,9 @@ public: 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. diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index bd6c548f708a..b86e31c63e9f 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -2771,6 +2771,25 @@ 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/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h index b69016ab5ae6..764d800731a0 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -408,6 +408,9 @@ protected: 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/Target/Process.cpp b/lldb/source/Target/Process.cpp index 1449501fbe91..f139479db13b 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -6065,3 +6065,84 @@ bool Process::CallVoidArgVoidPtrReturn(const Address *address, return false; } + +llvm::Expected +Process::GetMemoryTagManager(lldb::addr_t addr, lldb::addr_t end_addr) { + Architecture *arch = GetTarget().GetArchitecturePlugin(); + const MemoryTagManager *tag_manager = + arch ? arch->GetMemoryTagManager() : nullptr; + if (!arch || !tag_manager) { + 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_manager->AddressDiff(end_addr, addr); + if (len <= 0) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "End address (0x%" PRIx64 + ") must be greater than the start address (0x%" PRIx64 ")", + end_addr, addr); + } + + // Region lookup is not address size aware so mask the address + MemoryRegionInfo::RangeType tag_range(tag_manager->RemoveNonAddressBits(addr), + len); + tag_range = tag_manager->ExpandToGranule(tag_range); + + // Make a copy so we can use the original range in errors + MemoryRegionInfo::RangeType remaining_range(tag_range); + + // While we haven't found a matching memory region for some of the range + while (remaining_range.IsValid()) { + MemoryRegionInfo region; + Status status = GetMemoryRegionInfo(remaining_range.GetRangeBase(), region); + + if (status.Fail() || region.GetMemoryTagged() != MemoryRegionInfo::eYes) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Address range 0x%lx:0x%lx is not in a memory tagged region", + tag_range.GetRangeBase(), tag_range.GetRangeEnd()); + } + + if (region.GetRange().GetRangeEnd() >= remaining_range.GetRangeEnd()) { + // We've found a region for the whole range or the last piece of a range + remaining_range.SetByteSize(0); + } else { + // We've found some part of the range, look for the rest + remaining_range.SetRangeBase(region.GetRange().GetRangeEnd()); + } + } + + return tag_manager; +} + +llvm::Expected> +Process::ReadMemoryTags(const MemoryTagManager *tag_manager, lldb::addr_t addr, + size_t len) { + if (!tag_manager) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "A memory tag manager is required for reading memory tags."); + } + + MemoryTagManager::TagRange range(tag_manager->RemoveNonAddressBits(addr), + len); + range = tag_manager->ExpandToGranule(range); + + llvm::Expected> tag_data = + DoReadMemoryTags(range.GetRangeBase(), range.GetByteSize(), + tag_manager->GetAllocationTagType()); + if (!tag_data) + return tag_data.takeError(); + + return tag_manager->UnpackTagsData( + *tag_data, range.GetByteSize() / tag_manager->GetGranuleSize()); +} diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp index cf5a1a558006..b9fc107527a2 100644 --- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -465,3 +465,68 @@ TEST_F(GDBRemoteCommunicationClientTest, GetQOffsets) { 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); +}