diff --git a/lldb/include/lldb/Target/MemoryTagManager.h b/lldb/include/lldb/Target/MemoryTagManager.h --- a/lldb/include/lldb/Target/MemoryTagManager.h +++ b/lldb/include/lldb/Target/MemoryTagManager.h @@ -28,6 +28,15 @@ public: typedef Range TagRange; + // This is returned from the process GetMemoryTagManager methods. + // It contains the tag manager to use along with the range it used to check + // that the manager was valid. This saves the caller doing the + // alignment/expansion over again. + struct TagManagerWithRange { + const MemoryTagManager *manager; + MemoryTagManager::TagRange range; + }; + // 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 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 @@ -1710,10 +1710,22 @@ 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. + /// Given an address range, check that the current process and this memory + /// range support memory tagging. If these checks pass then a structure is + /// returned with a tag manager and the memory range it applies to. + /// + /// Tags present in the addresses given are ignored. The returned range may be + /// different to the initial range due to granule alignment. + /// + /// The range is aligned in the following way: + /// * create a range from addr to end_addr + /// * align the start down to a granule boundary + /// * align the end up to a granule boundary + /// + /// This means that if addr is unaligned then the final range will be larger + /// than the intial range. Since it includes an extra granule to cover the + /// final misaligned portion. This "greedy" expansion is in contrast to + /// GetMemoryTagManagerForGranules. /// /// \param[in] addr /// Start of memory range. @@ -1723,30 +1735,73 @@ /// included. /// /// \return - /// Either a valid pointer to a tag manager or an error describing why one - /// could not be provided. - llvm::Expected + /// Either a TagManagerWithRange 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. + /// This acts like GetMemoryTagManager except that the range is provided + /// by a start address and a number of granules. + /// + /// Use this function when you know how many granules (how many tags) you have + /// but do not yet know if that range is memory tagged. This means you don't + /// have a tag manager to get the granule size, to build a start/end range + /// from. (e.g. when doing the "memory tag write" command) /// - /// \param[in] tag_manager - /// The tag manager to get memory tagging information from. + /// If you do get a tag manager, the range that comes with it will have been + /// expanded for you in the following way: + /// * addr is aligned down to a granule boundary + /// * end addr is set to addr plus the granules given + /// + /// This is in contrast to the behaviour of GetMemoryTagManager. Here, if addr + /// is unaligned the resulting range will still have size equal to the number + /// of granules specified. /// /// \param[in] addr - /// Start of memory range to read tags for. + /// Start of memory range. This will be aligned down, so think of this as + /// setting the starting granule, not the starting address. /// - /// \param[in] len - /// Length of memory range to read tags for (in bytes). + /// \param[in] granules + /// Size of the memory range, in granules. (this will be the number of + /// tags you are handling) + /// + /// \return + /// Either a TagManagerWithRange or an error describing why one could not + /// be provided. + llvm::Expected + GetMemoryTagManagerForGranules(lldb::addr_t addr, size_t granules); + + /// Read memory tags for a range of memory. + /// (calls DoReadMemoryTags to do the target specific work) + /// + /// \param[in] manager_and_range + /// The tag manager to get memory tagging information from and the range + /// of memory to read tags for. + /// (get this from one of the GetMemoryTagManager methods) /// /// \return - /// Either the unpacked tags or an error describing a failure to read - /// or unpack them. + /// Either the memory tags or an error describing a failure to read them. llvm::Expected> - ReadMemoryTags(const MemoryTagManager *tag_manager, lldb::addr_t addr, - size_t len); + ReadMemoryTags(MemoryTagManager::TagManagerWithRange manager_and_range); + + /// Write memory tags for a range of memory. + /// (calls DoWriteMemoryTags to do the target specific work) + /// + /// \param[in] manager_and_range + /// The tag manager to get memory tagging information from, along with the + /// range of memory to write tags for. + /// (get this from one of the GetMemoryTagManager methods) + /// + /// \param[in] tags + /// Allocation tags to be written. Since lldb-server can repeat tags for a + /// range, the number of tags doesn't have to match the number of granules + /// in the range. (though most of the time it will) + /// + /// \return + /// A Status telling you if the write succeeded or not. + Status + WriteMemoryTags(MemoryTagManager::TagManagerWithRange mananger_and_range, + const std::vector &tags); /// Resolve dynamically loaded indirect functions. /// @@ -2767,6 +2822,29 @@ /// false otherwise. virtual bool SupportsMemoryTagging() { return false; } + /// Attempt to get a memory tag manager for the range returned by + /// range_callback. Used to implement GetMemoryTagManager and + /// GetMemoryTagManagerForGranules. + /// + /// \param[in] range_callback + /// A callback function that takes a tag manager + /// and returns a TagRange or an error if the range cannot be constructed. + /// (how it is constructed changes depending on situation) + /// This function does not need to remove tags from the range. + /// + /// \return + /// A pointer to a tag manager, along with the range it is valid for. + /// Or, an error explaining why one could not be returned. + /// This could be because: + /// * The current architecture doesn't support memory tagging + /// * The current process doesn't support memory tagging + /// * range_callback failed to make a memory range + /// * Some of that range is not memory tagged + llvm::Expected GetMemoryTagManagerImpl( + std::function< + llvm::Expected(const MemoryTagManager &)> + range_callback); + /// 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. @@ -2790,6 +2868,30 @@ GetPluginName().GetCString()); } + /// Does the final operation to write memory tags. E.g. sending a GDB packet. + /// It assumes that WriteMemoryTags has checked that memory tagging is enabled + /// and has expanded the memory range as needed and packed the tag data. + /// + /// \param[in] addr + /// Start of address range to write memory tags for. + /// + /// \param[in] len + /// Length of the memory range to write tags for (in bytes). + /// + /// \param[in] type + /// Type of tags to read (get this from a MemoryTagManager) + /// + /// \param[in] tags + /// Packed tags to be written. + /// + /// \return + /// Status telling you whether the write succeeded. + virtual Status DoWriteMemoryTags(lldb::addr_t addr, size_t len, int32_t type, + const std::vector &tags) { + return Status("%s does not support writing memory tags", + GetPluginName().GetCString()); + } + // Type definitions typedef std::map LanguageRuntimeCollection; diff --git a/lldb/source/Commands/CommandObjectMemoryTag.cpp b/lldb/source/Commands/CommandObjectMemoryTag.cpp --- a/lldb/source/Commands/CommandObjectMemoryTag.cpp +++ b/lldb/source/Commands/CommandObjectMemoryTag.cpp @@ -67,7 +67,7 @@ } Process *process = m_exe_ctx.GetProcessPtr(); - llvm::Expected tag_manager_or_err = + llvm::Expected tag_manager_or_err = process->GetMemoryTagManager(start_addr, end_addr); if (!tag_manager_or_err) { @@ -75,25 +75,25 @@ return false; } - const MemoryTagManager *tag_manager = *tag_manager_or_err; - ptrdiff_t len = tag_manager->AddressDiff(end_addr, start_addr); llvm::Expected> tags = - process->ReadMemoryTags(tag_manager, start_addr, len); + process->ReadMemoryTags(*tag_manager_or_err); if (!tags) { result.SetError(Status(tags.takeError())); return false; } + const MemoryTagManager *tag_manager = tag_manager_or_err->manager; + MemoryTagManager::TagRange read_range = tag_manager_or_err->range; + result.AppendMessageWithFormatv("Logical tag: {0:x}", tag_manager->GetLogicalTag(start_addr)); result.AppendMessage("Allocation tags:"); - MemoryTagManager::TagRange initial_range(start_addr, len); - addr_t addr = tag_manager->ExpandToGranule(initial_range).GetRangeBase(); - for (auto tag : *tags) { - addr_t next_addr = addr + tag_manager->GetGranuleSize(); - // Showing tagged adresses here until we have non address bit handling + addr_t addr = read_range.GetRangeBase(); + size_t granule_size = tag_manager->GetGranuleSize(); + for (lldb::addr_t tag : *tags) { + addr_t next_addr = addr + granule_size; result.AppendMessageWithFormatv("[{0:x}, {1:x}): {2:x}", addr, next_addr, tag); addr = next_addr; 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 @@ -456,6 +456,9 @@ lldb::DataBufferSP ReadMemoryTags(lldb::addr_t addr, size_t len, int32_t type); + Status WriteMemoryTags(lldb::addr_t addr, size_t len, int32_t type, + const std::vector &tags); + /// 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 @@ -630,6 +630,24 @@ return buffer_sp; } +Status GDBRemoteCommunicationClient::WriteMemoryTags( + lldb::addr_t addr, size_t len, int32_t type, + const std::vector &tags) { + // Format QMemTags:address,length:type:tags + StreamString packet; + packet.Printf("QMemTags:%" PRIx64 ",%zx:%" PRIx32 ":", addr, len, type); + packet.PutBytesAsRawHex8(tags.data(), tags.size()); + + Status status; + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse(packet.GetString(), response, false) != + PacketResult::Success || + !response.IsOKResponse()) { + status.SetErrorString("QMemTags packet failed"); + } + return status; +} + 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 @@ -411,6 +411,9 @@ llvm::Expected> DoReadMemoryTags(lldb::addr_t addr, size_t len, int32_t type) override; + Status DoWriteMemoryTags(lldb::addr_t addr, size_t len, int32_t type, + const std::vector &tags) 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 @@ -2790,6 +2790,14 @@ return got; } +Status ProcessGDBRemote::DoWriteMemoryTags(lldb::addr_t addr, size_t len, + int32_t type, + const std::vector &tags) { + // By now WriteMemoryTags should have validated that tagging is enabled + // for this target/process. + return m_gdb_comm.WriteMemoryTags(addr, len, type, tags); +} + 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 @@ -6066,8 +6066,45 @@ return false; } -llvm::Expected +llvm::Expected Process::GetMemoryTagManager(lldb::addr_t addr, lldb::addr_t end_addr) { + return GetMemoryTagManagerImpl( + [=](const MemoryTagManager &manager) + -> llvm::Expected { + ptrdiff_t len = 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); + } + + MemoryRegionInfo::RangeType range(addr, len); + return manager.ExpandToGranule(range); + }); +} + +llvm::Expected +Process::GetMemoryTagManagerForGranules(lldb::addr_t addr, size_t granules) { + return GetMemoryTagManagerImpl( + [=](const MemoryTagManager &manager) + -> llvm::Expected { + MemoryTagManager::TagRange range(addr, 1); + // This will align the start address down to a granule boundary + range = manager.ExpandToGranule(range); + // Then add the granules, giving a completely aligned range + range.SetByteSize(granules * manager.GetGranuleSize()); + + return range; + }); +} + +llvm::Expected +Process::GetMemoryTagManagerImpl( + std::function< + llvm::Expected(const MemoryTagManager &)> + range_callback) { Architecture *arch = GetTarget().GetArchitecturePlugin(); const MemoryTagManager *tag_manager = arch ? arch->GetMemoryTagManager() : nullptr; @@ -6083,19 +6120,14 @@ "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); + llvm::Expected tag_range_or_err = + range_callback(*tag_manager); + if (!tag_range_or_err) + return tag_range_or_err.takeError(); + MemoryTagManager::TagRange tag_range = *tag_range_or_err; + // Memory region lookup is not address size aware + tag_range.SetRangeBase( + tag_manager->RemoveNonAddressBits(tag_range.GetRangeBase())); // Make a copy so we can use the original range in errors MemoryRegionInfo::RangeType remaining_range(tag_range); @@ -6121,28 +6153,37 @@ } } - return tag_manager; + return MemoryTagManager::TagManagerWithRange{tag_manager, tag_range}; } -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> Process::ReadMemoryTags( + MemoryTagManager::TagManagerWithRange manager_and_range) { + const MemoryTagManager *manager = manager_and_range.manager; + assert(manager && "tag manager required for reading memory tags"); + MemoryTagManager::TagRange range = manager_and_range.range; llvm::Expected> tag_data = DoReadMemoryTags(range.GetRangeBase(), range.GetByteSize(), - tag_manager->GetAllocationTagType()); + manager->GetAllocationTagType()); if (!tag_data) return tag_data.takeError(); - return tag_manager->UnpackTagsData( - *tag_data, range.GetByteSize() / tag_manager->GetGranuleSize()); + return manager->UnpackTagsData(*tag_data, range.GetByteSize() / + manager->GetGranuleSize()); +} + +Status Process::WriteMemoryTags( + MemoryTagManager::TagManagerWithRange mananger_and_range, + const std::vector &tags) { + const MemoryTagManager *manager = mananger_and_range.manager; + assert(manager && "tag manager required for writing memory tags"); + MemoryTagManager::TagRange range = mananger_and_range.range; + + llvm::Expected> packed_tags = manager->PackTags(tags); + if (!packed_tags) { + return Status(packed_tags.takeError()); + } + + return DoWriteMemoryTags(range.GetRangeBase(), range.GetByteSize(), + manager->GetAllocationTagType(), *packed_tags); } 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 @@ -17,6 +17,7 @@ #include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" #include +#include using namespace lldb_private::process_gdb_remote; using namespace lldb_private; @@ -530,3 +531,51 @@ check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m01020", llvm::None); } + +static void check_Qmemtags(TestClient &client, MockServer &server, + lldb::addr_t addr, size_t len, int32_t type, + const std::vector &tags, const char *packet, + llvm::StringRef response, bool should_succeed) { + const auto &WriteMemoryTags = [&]() { + std::future result = std::async(std::launch::async, [&] { + return client.WriteMemoryTags(addr, len, type, tags); + }); + + HandlePacket(server, packet, response); + return result.get(); + }; + + auto result = WriteMemoryTags(); + if (should_succeed) + ASSERT_TRUE(result.Success()); + else + ASSERT_TRUE(result.Fail()); +} + +TEST_F(GDBRemoteCommunicationClientTest, WriteMemoryTags) { + check_Qmemtags(client, server, 0xABCD, 0x20, 1, + std::vector{0x12, 0x34}, "QMemTags:abcd,20:1:1234", + "OK", true); + + // The GDB layer doesn't care that the number of tags != + // the length of the write. + check_Qmemtags(client, server, 0x4321, 0x20, 9, std::vector{}, + "QMemTags:4321,20:9:", "OK", true); + + check_Qmemtags(client, server, 0x8877, 0x123, 0x34, + std::vector{0x55, 0x66, 0x77}, + "QMemTags:8877,123:34:556677", "E01", false); + + // Type is a signed integer but is packed as its raw bytes, + // instead of having a +/-. + check_Qmemtags(client, server, 0x456789, 0, -1, std::vector{0x99}, + "QMemTags:456789,0:ffffffff:99", "E03", false); + check_Qmemtags(client, server, 0x456789, 0, + std::numeric_limits::max(), + std::vector{0x99}, "QMemTags:456789,0:7fffffff:99", + "E03", false); + check_Qmemtags(client, server, 0x456789, 0, + std::numeric_limits::min(), + std::vector{0x99}, "QMemTags:456789,0:80000000:99", + "E03", false); +}