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/MemoryTagManager.h" namespace lldb_private { @@ -97,6 +98,17 @@ 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 --- 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" @@ -1695,6 +1696,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 + /// 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 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 @@ -2715,6 +2754,29 @@ /// 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.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/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/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/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 @@ -448,6 +448,9 @@ 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/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 @@ -658,6 +658,50 @@ 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 @@ -414,6 +414,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 @@ -2770,6 +2770,25 @@ 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 @@ -6019,3 +6019,83 @@ 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%x) must be greater than the start address (0x%x)", + 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->UnpackTags(*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 --- 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); +}