[lldb][AArch64] Add MTE memory tag reading to lldb
authorDavid Spickett <david.spickett@linaro.org>
Fri, 19 Feb 2021 15:57:29 +0000 (15:57 +0000)
committerDavid Spickett <david.spickett@linaro.org>
Thu, 24 Jun 2021 16:17:10 +0000 (17:17 +0100)
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

12 files changed:
lldb/include/lldb/Core/Architecture.h
lldb/include/lldb/Target/Process.h
lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.cpp [new file with mode: 0644]
lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.h [new file with mode: 0644]
lldb/source/Plugins/Architecture/AArch64/CMakeLists.txt [new file with mode: 0644]
lldb/source/Plugins/Architecture/CMakeLists.txt
lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
lldb/source/Target/Process.cpp
lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp

index 2ea8bd3..b68bf27 100644 (file)
@@ -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
index c849bd7..ba51056 100644 (file)
@@ -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<const MemoryTagManager *>
+  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<std::vector<lldb::addr_t>>
+  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<std::vector<uint8_t>>
+  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<lldb::LanguageType, lldb::LanguageRuntimeSP>
       LanguageRuntimeCollection;
diff --git a/lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.cpp b/lldb/source/Plugins/Architecture/AArch64/ArchitectureAArch64.cpp
new file mode 100644 (file)
index 0000000..9994cc2
--- /dev/null
@@ -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<Architecture>
+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<Architecture>(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 (file)
index 0000000..775478c
--- /dev/null
@@ -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<Architecture> 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 (file)
index 0000000..9bcf993
--- /dev/null
@@ -0,0 +1,11 @@
+add_lldb_library(lldbPluginArchitectureAArch64 PLUGIN
+  ArchitectureAArch64.cpp
+
+  LINK_LIBS
+    lldbPluginProcessUtility
+    lldbCore
+    lldbTarget
+    lldbUtility
+  LINK_COMPONENTS
+    Support
+  )
index 14ad916..9ed8edf 100644 (file)
@@ -1,3 +1,4 @@
 add_subdirectory(Arm)
 add_subdirectory(Mips)
 add_subdirectory(PPC64)
+add_subdirectory(AArch64)
index db50afc..a7fcf08 100644 (file)
@@ -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<hex encoded bytes>
+
+  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;
index 599020e..fa67a6c 100644 (file)
@@ -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.
index bd6c548..b86e31c 100644 (file)
@@ -2771,6 +2771,25 @@ bool ProcessGDBRemote::SupportsMemoryTagging() {
   return m_gdb_comm.GetMemoryTaggingSupported();
 }
 
+llvm::Expected<std::vector<uint8_t>>
+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<uint8_t> tag_data = buffer_sp->GetData();
+  std::vector<uint8_t> 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<ObjectFile::LoadableData> entries) {
   Status error;
index b69016a..764d800 100644 (file)
@@ -408,6 +408,9 @@ protected:
 
   bool HasErased(FlashRange range);
 
+  llvm::Expected<std::vector<uint8_t>>
+  DoReadMemoryTags(lldb::addr_t addr, size_t len, int32_t type) override;
+
 private:
   // For ProcessGDBRemote only
   std::string m_partial_profile_data;
index 1449501..f139479 100644 (file)
@@ -6065,3 +6065,84 @@ bool Process::CallVoidArgVoidPtrReturn(const Address *address,
 
   return false;
 }
+
+llvm::Expected<const MemoryTagManager *>
+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<std::vector<lldb::addr_t>>
+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<std::vector<uint8_t>> 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());
+}
index cf5a1a5..b9fc107 100644 (file)
@@ -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<std::vector<uint8_t>> expected_tag_data) {
+  const auto &ReadMemoryTags = [&](size_t len, const char *packet,
+                                   llvm::StringRef response) {
+    std::future<DataBufferSP> 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<uint8_t> expected(*expected_tag_data);
+    llvm::ArrayRef<uint8_t> 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<uint8_t>{});
+
+  // 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<uint8_t>{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<uint8_t>{});
+
+  // Normal responses
+  check_qmemtags(client, server, 16, "qMemTags:def0,10:1", "m66",
+                 std::vector<uint8_t>{0x66});
+  check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m0102",
+                 std::vector<uint8_t>{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);
+}