DavidSpickett created this revision.
Herald added subscribers: danielkiss, kristof.beyls, mgorny.
DavidSpickett requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

This adds the MemoryTagManager class and a specialisation
of that class for AArch64 MTE tags. It provides a generic
interface for various tagging operations.
Adding/removing tags, diffing tagged pointers, etc.

Later patches will use this manager to handle memory tags
in generic code in both lldb and lldb-server.
Since it will be used in both, the base class header is in
lldb/Target.
(MemoryRegionInfo is another example of this pattern)


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D97281

Files:
  lldb/include/lldb/Target/MemoryTagManager.h
  lldb/source/Plugins/Process/Utility/CMakeLists.txt
  lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp
  lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h
  lldb/unittests/Process/Utility/CMakeLists.txt
  lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp

Index: lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp
===================================================================
--- /dev/null
+++ lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp
@@ -0,0 +1,166 @@
+//===-- MemoryTagManagerAArch64MTETest.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/Process/Utility/MemoryTagManagerAArch64MTE.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+
+TEST(MemoryTagManagerAArch64MTETest, UnpackTags) {
+  MemoryTagManagerAArch64MTE handler;
+
+  // Error for insufficient tag data
+  std::vector<uint8_t> 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<lldb::addr_t> expected{0xe, 0xf};
+
+  llvm::Expected<std::vector<lldb::addr_t>> got = handler.UnpackTags(input, 2);
+  ASSERT_THAT_EXPECTED(got, llvm::Succeeded());
+  ASSERT_THAT(expected, testing::ContainerEq(*got));
+}
+
+TEST(MemoryTagManagerAArch64MTETest, GetLogicalTag) {
+  MemoryTagManagerAArch64MTE 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(MemoryTagManagerAArch64MTETest, RemoveLogicalTag) {
+  MemoryTagManagerAArch64MTE handler;
+
+  // Should not remove surrounding bits
+  ASSERT_EQ((lldb::addr_t)0xf0f00000ffffffff,
+            handler.RemoveLogicalTag(0xfef00000ffffffff));
+  // Untagged pointers are unchanged
+  ASSERT_EQ((lldb::addr_t)0x1034567812345678,
+            handler.RemoveLogicalTag(0x1034567812345678));
+}
+
+TEST(MemoryTagManagerAArch64MTETest, SetLogicalTag) {
+  MemoryTagManagerAArch64MTE 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<lldb::addr_t> 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<lldb::addr_t> g1 =
+      handler.SetLogicalTag(0x00000000ffffffff, 0xa);
+  ASSERT_THAT_EXPECTED(g1, llvm::Succeeded());
+  llvm::Expected<lldb::addr_t> 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<lldb::addr_t> set = handler.SetLogicalTag(addr, tag);
+  ASSERT_THAT_EXPECTED(set, llvm::Succeeded());
+
+  ASSERT_EQ(tag, handler.GetLogicalTag(*set));
+  ASSERT_EQ(addr, handler.RemoveLogicalTag(*set));
+}
+
+TEST(MemoryTagManagerAArch64MTETest, CopyLogicalTag) {
+  MemoryTagManagerAArch64MTE 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(MemoryTagManagerAArch64MTETest, ExpandToGranule) {
+  MemoryTagManagerAArch64MTE handler;
+  // Reading nothing, no alignment needed
+  ASSERT_EQ(
+      MemoryTagManagerAArch64MTE::TagRange(0, 0),
+      handler.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(0, 0)));
+
+  // Ranges with 0 size are unchanged even if address is non 0
+  // (normally 0x1234 would be aligned to 0x1230)
+  ASSERT_EQ(
+      MemoryTagManagerAArch64MTE::TagRange(0x1234, 0),
+      handler.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(0x1234, 0)));
+
+  // Ranges already aligned don't change
+  ASSERT_EQ(
+      MemoryTagManagerAArch64MTE::TagRange(0x100, 64),
+      handler.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(0x100, 64)));
+
+  // Reading less than 1 granule, rounds up to 1 granule
+  ASSERT_EQ(
+      MemoryTagManagerAArch64MTE::TagRange(0, 16),
+      handler.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(0, 1)));
+
+  // Start address is aligned down, and length modified accordingly
+  // Here bytes 8 through 24 straddle 2 granules. So the resulting range starts
+  // at 0 and covers 32 bytes.
+  ASSERT_EQ(
+      MemoryTagManagerAArch64MTE::TagRange(0, 32),
+      handler.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(8, 16)));
+
+  // Here only the size of the range needs aligning
+  ASSERT_EQ(
+      MemoryTagManagerAArch64MTE::TagRange(16, 32),
+      handler.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(16, 24)));
+
+  // Start and size need aligning here but we only need 1 granule to cover it
+  ASSERT_EQ(
+      MemoryTagManagerAArch64MTE::TagRange(16, 16),
+      handler.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(18, 4)));
+}
+
+TEST(MemoryTagManagerAArch64MTETest, AddressDiff) {
+  MemoryTagManagerAArch64MTE 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));
+}
Index: lldb/unittests/Process/Utility/CMakeLists.txt
===================================================================
--- lldb/unittests/Process/Utility/CMakeLists.txt
+++ lldb/unittests/Process/Utility/CMakeLists.txt
@@ -17,7 +17,9 @@
 add_lldb_unittest(ProcessUtilityTests
   RegisterContextTest.cpp
   LinuxProcMapsTest.cpp
+  MemoryTagManagerAArch64MTETest.cpp
   ${PLATFORM_SOURCES}
 
   LINK_LIBS
-    lldbPluginProcessUtility)
+    lldbPluginProcessUtility
+    LLVMTestingSupport)
Index: lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h
===================================================================
--- /dev/null
+++ lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h
@@ -0,0 +1,42 @@
+//===-- MemoryTagManagerAArch64MTE.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_PROCESS_UTILITY_MEMORYTAGMANAGERAARCH64MTE_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_MEMORYTAGMANAGERAARCH64MTE_H
+
+#include "lldb/Target/MemoryTagManager.h"
+
+namespace lldb_private {
+
+class MemoryTagManagerAArch64MTE : public MemoryTagManager {
+public:
+  // This enum is supposed to be shared for all of AArch64 but until
+  // there are more tag types than MTE, it will live here.
+  enum MTETagTypes {
+    eMTE_logical = 0,
+    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<lldb::addr_t> 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 ExpandToGranule(TagRange range) const override;
+  int32_t GetAllocationTagType() const override;
+  size_t GetBytesPerTag() const override;
+  llvm::Expected<std::vector<lldb::addr_t>>
+  UnpackTags(const std::vector<uint8_t> &tags, size_t granules) const override;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_MEMORYTAGMANAGERAARCH64MTE_H
Index: lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp
===================================================================
--- /dev/null
+++ lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp
@@ -0,0 +1,109 @@
+//===-- MemoryTagManagerAArch64MTE.cpp --------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "MemoryTagManagerAArch64MTE.h"
+
+using namespace lldb_private;
+
+static const unsigned MTE_START_BIT = 56;
+static const unsigned MTE_TAG_MAX = 0xf;
+static const unsigned MTE_GRANULE_SIZE = 16;
+
+lldb::addr_t
+MemoryTagManagerAArch64MTE::GetLogicalTag(lldb::addr_t addr) const {
+  return (addr >> MTE_START_BIT) & MTE_TAG_MAX;
+}
+
+lldb::addr_t
+MemoryTagManagerAArch64MTE::RemoveLogicalTag(lldb::addr_t addr) const {
+  return addr & (~((lldb::addr_t)MTE_TAG_MAX << MTE_START_BIT));
+}
+
+llvm::Expected<lldb::addr_t>
+MemoryTagManagerAArch64MTE::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 MemoryTagManagerAArch64MTE::CopyLogicalTag(lldb::addr_t from,
+                                                        lldb::addr_t to) const {
+  return RemoveLogicalTag(to) | (GetLogicalTag(from) << MTE_START_BIT);
+}
+
+ptrdiff_t MemoryTagManagerAArch64MTE::AddressDiff(lldb::addr_t addr1,
+                                                  lldb::addr_t addr2) const {
+  return RemoveLogicalTag(addr1) - RemoveLogicalTag(addr2);
+}
+
+lldb::addr_t MemoryTagManagerAArch64MTE::GetGranuleSize() const {
+  return MTE_GRANULE_SIZE;
+}
+
+int32_t MemoryTagManagerAArch64MTE::GetAllocationTagType() const {
+  return eMTE_allocation;
+}
+
+size_t MemoryTagManagerAArch64MTE::GetBytesPerTag() const { return 1; }
+
+MemoryTagManagerAArch64MTE::TagRange
+MemoryTagManagerAArch64MTE::ExpandToGranule(TagRange range) const {
+  // Ignore reading a length of 0
+  if (!range.IsValid())
+    return range;
+
+  const size_t granule = GetGranuleSize();
+
+  // Align start down to granule start
+  lldb::addr_t new_start = range.GetRangeBase();
+  lldb::addr_t align_down_amount = new_start % granule;
+  new_start -= align_down_amount;
+
+  // Account for the distance we moved the start above
+  size_t new_len = range.GetByteSize() + align_down_amount;
+  // Then align up to the end of the granule
+  if (new_len % granule)
+    new_len += (granule - (new_len % granule));
+
+  return TagRange(new_start, new_len);
+}
+
+llvm::Expected<std::vector<lldb::addr_t>>
+MemoryTagManagerAArch64MTE::UnpackTags(const std::vector<uint8_t> &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<lldb::addr_t> 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;
+}
Index: lldb/source/Plugins/Process/Utility/CMakeLists.txt
===================================================================
--- lldb/source/Plugins/Process/Utility/CMakeLists.txt
+++ lldb/source/Plugins/Process/Utility/CMakeLists.txt
@@ -8,6 +8,7 @@
   InferiorCallPOSIX.cpp
   LinuxProcMaps.cpp
   LinuxSignals.cpp
+  MemoryTagManagerAArch64MTE.cpp
   MipsLinuxSignals.cpp
   NativeProcessSoftwareSingleStep.cpp
   NativeRegisterContextRegisterInfo.cpp
Index: lldb/include/lldb/Target/MemoryTagManager.h
===================================================================
--- /dev/null
+++ lldb/include/lldb/Target/MemoryTagManager.h
@@ -0,0 +1,95 @@
+//===-- MemoryTagManager.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_TARGET_MEMORYTAGMANAGER_H
+#define LLDB_TARGET_MEMORYTAGMANAGER_H
+
+#include "lldb/Utility/RangeMap.h"
+#include "lldb/lldb-private.h"
+#include "llvm/Support/Error.h"
+
+namespace lldb_private {
+
+// This interface allows high level commands to handle memory tags
+// in a generic way.
+//
+// Definitions:
+//   logical tag    - the tag stored in a pointer
+//   allocation tag - the tag stored in hardware
+//                    (e.g. special memory, cache line bits)
+//   granule        - number of bytes of memory a single tag applies to
+
+class MemoryTagManager {
+public:
+  typedef Range<lldb::addr_t, size_t> 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<lldb::addr_t>
+  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;
+
+  // Align an address range to granule boundaries.
+  // So that reading memory tags for the new range returns
+  // tags that will cover the original range.
+  //
+  // Say your granules are 16 bytes and you want
+  // tags for 16 bytes of memory starting from address 8.
+  // 1 granule isn't enough because it only covers addresses
+  // 0-16, we want addresses 8-24. So the range must be
+  // expanded to 2 granules.
+  virtual TagRange ExpandToGranule(TagRange range) const = 0;
+
+  // Return the type value to use in GDB protocol qMemTags packets to read
+  // allocation tags. This is named "Allocation" specifically because the spec
+  // allows for logical tags to be read the same way, though we do not use that.
+  //
+  // This value is unique within a given architecture. Meaning that different
+  // tagging schemes within the same architecture should use unique values,
+  // but other architectures can overlap those values.
+  virtual int32_t GetAllocationTagType() const = 0;
+
+  // Return the number of bytes a single tag will be packed into during
+  // transport. For example an MTE tag is 4 bits but occupies 1 byte during
+  // 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<std::vector<lldb::addr_t>>
+  UnpackTags(const std::vector<uint8_t> &tags, size_t granules) const = 0;
+
+  virtual ~MemoryTagManager() {}
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_MEMORYTAGMANAGER_H
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to