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

This adds memory tag writing to Process and the
GDB remote code. Supporting work for the
"memory tag write" command. (to follow)

This command will sometimes only know the start
address and number of tags so a new way to get
the tag manager has been added.

GetMemoryTagManagerForGranules will work out
the range from a start address and some number
of granules.

The alignment behaviour of this method is different
from the existing method. In the case where start is
misaligned, we first align it down then add granules.
As opposed to adding granules first which would result
in an extra granule after alignment.
(this makes more sense for a situation like we
will have in the tag write command)

To save the commands then redoing that alignment
both memory tag manager methods now return a struct
of the range we checked and the manager itself.

(ReadMemoryTags and WriteMemoryTags now also use this
type instead of seperate range arguments)

Process WriteMemoryTags is similair to ReadMemoryTags.
It will pack the tags then call DoWriteMemoryTags.
That function will send the QMemTags packet to the gdb-remote.

The QMemTags packet follows the GDB specification in:
https://sourceware.org/gdb/current/onlinedocs/gdb/General-Query-Packets.html#General-Query-Packets

Note that lldb-server will be treating partial writes as
complete failures. So lldb doesn't need to handle the partial
write case in any special way.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D105181

Files:
  lldb/include/lldb/Target/MemoryTagManager.h
  lldb/include/lldb/Target/Process.h
  lldb/source/Commands/CommandObjectMemoryTag.cpp
  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: lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
===================================================================
--- lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
+++ lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
@@ -17,6 +17,7 @@
 #include "llvm/Testing/Support/Error.h"
 #include "gmock/gmock.h"
 #include <future>
+#include <limits>
 
 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<uint8_t> &tags, const char *packet,
+                           llvm::StringRef response, bool should_succeed) {
+  const auto &WriteMemoryTags = [&]() {
+    std::future<Status> 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<uint8_t>{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<uint8_t>{},
+                 "QMemTags:4321,20:9:", "OK", true);
+
+  check_Qmemtags(client, server, 0x8877, 0x123, 0x34,
+                 std::vector<uint8_t>{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<uint8_t>{0x99},
+                 "QMemTags:456789,0:ffffffff:99", "E03", false);
+  check_Qmemtags(client, server, 0x456789, 0,
+                 std::numeric_limits<int32_t>::max(),
+                 std::vector<uint8_t>{0x99}, "QMemTags:456789,0:7fffffff:99",
+                 "E03", false);
+  check_Qmemtags(client, server, 0x456789, 0,
+                 std::numeric_limits<int32_t>::min(),
+                 std::vector<uint8_t>{0x99}, "QMemTags:456789,0:80000000:99",
+                 "E03", false);
+}
Index: lldb/source/Target/Process.cpp
===================================================================
--- lldb/source/Target/Process.cpp
+++ lldb/source/Target/Process.cpp
@@ -6066,8 +6066,45 @@
   return false;
 }
 
-llvm::Expected<const MemoryTagManager *>
+llvm::Expected<MemoryTagManager::TagManagerWithRange>
 Process::GetMemoryTagManager(lldb::addr_t addr, lldb::addr_t end_addr) {
+  return GetMemoryTagManagerImpl(
+      [=](const MemoryTagManager &manager)
+          -> llvm::Expected<MemoryTagManager::TagRange> {
+        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<MemoryTagManager::TagManagerWithRange>
+Process::GetMemoryTagManagerForGranules(lldb::addr_t addr, size_t granules) {
+  return GetMemoryTagManagerImpl(
+      [=](const MemoryTagManager &manager)
+          -> llvm::Expected<MemoryTagManager::TagRange> {
+        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<MemoryTagManager::TagManagerWithRange>
+Process::GetMemoryTagManagerImpl(
+    std::function<
+        llvm::Expected<MemoryTagManager::TagRange>(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<MemoryTagManager::TagRange> 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<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<lldb::addr_t>> 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<std::vector<uint8_t>> 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<lldb::addr_t> &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<std::vector<uint8_t>> packed_tags = manager->PackTags(tags);
+  if (!packed_tags) {
+    return Status(packed_tags.takeError());
+  }
+
+  return DoWriteMemoryTags(range.GetRangeBase(), range.GetByteSize(),
+                           manager->GetAllocationTagType(), *packed_tags);
 }
Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -411,6 +411,9 @@
   llvm::Expected<std::vector<uint8_t>>
   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<uint8_t> &tags) override;
+
 private:
   // For ProcessGDBRemote only
   std::string m_partial_profile_data;
Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ 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<uint8_t> &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<ObjectFile::LoadableData> entries) {
   Status error;
Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ 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<uint8_t> &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.
Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ 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<uint8_t> &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;
Index: lldb/source/Commands/CommandObjectMemoryTag.cpp
===================================================================
--- lldb/source/Commands/CommandObjectMemoryTag.cpp
+++ lldb/source/Commands/CommandObjectMemoryTag.cpp
@@ -67,7 +67,7 @@
     }
 
     Process *process = m_exe_ctx.GetProcessPtr();
-    llvm::Expected<const MemoryTagManager *> tag_manager_or_err =
+    llvm::Expected<MemoryTagManager::TagManagerWithRange> 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<std::vector<lldb::addr_t>> 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;
Index: lldb/include/lldb/Target/Process.h
===================================================================
--- lldb/include/lldb/Target/Process.h
+++ 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<const MemoryTagManager *>
+  ///     Either a TagManagerWithRange or an error describing why one could not
+  ///     be provided.
+  llvm::Expected<MemoryTagManager::TagManagerWithRange>
   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<MemoryTagManager::TagManagerWithRange>
+  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<std::vector<lldb::addr_t>>
-  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<lldb::addr_t> &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<MemoryTagManager::TagManagerWithRange> GetMemoryTagManagerImpl(
+      std::function<
+          llvm::Expected<MemoryTagManager::TagRange>(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<uint8_t> &tags) {
+    return Status("%s does not support writing memory tags",
+                  GetPluginName().GetCString());
+  }
+
   // Type definitions
   typedef std::map<lldb::LanguageType, lldb::LanguageRuntimeSP>
       LanguageRuntimeCollection;
Index: lldb/include/lldb/Target/MemoryTagManager.h
===================================================================
--- lldb/include/lldb/Target/MemoryTagManager.h
+++ lldb/include/lldb/Target/MemoryTagManager.h
@@ -28,6 +28,15 @@
 public:
   typedef Range<lldb::addr_t, lldb::addr_t> 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
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to