Author: Julian Lettner
Date: 2025-10-02T13:57:40-07:00
New Revision: f4784fd13fb77a609920a06af99c3602bdec530f

URL: 
https://github.com/llvm/llvm-project/commit/f4784fd13fb77a609920a06af99c3602bdec530f
DIFF: 
https://github.com/llvm/llvm-project/commit/f4784fd13fb77a609920a06af99c3602bdec530f.diff

LOG: [debugserver] Support for `qMemTags` packet (#160952)

Support for `qMemTags` packet in debugserver which allows usage of
LLDB's `memory tag read` on Darwin.

Added: 
    lldb/test/API/macosx/mte/Makefile
    lldb/test/API/macosx/mte/TestDarwinMTE.py
    lldb/test/API/macosx/mte/main.c
    lldb/test/API/macosx/mte/mte-entitlements.plist

Modified: 
    lldb/packages/Python/lldbsuite/test/cpu_feature.py
    lldb/tools/debugserver/source/DNB.cpp
    lldb/tools/debugserver/source/DNB.h
    lldb/tools/debugserver/source/DNBDefs.h
    lldb/tools/debugserver/source/MacOSX/MachTask.h
    lldb/tools/debugserver/source/MacOSX/MachTask.mm
    lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
    lldb/tools/debugserver/source/MacOSX/MachVMMemory.h
    lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
    lldb/tools/debugserver/source/MacOSX/MachVMRegion.h
    lldb/tools/debugserver/source/RNBRemote.cpp
    lldb/tools/debugserver/source/RNBRemote.h

Removed: 
    


################################################################################
diff  --git a/lldb/packages/Python/lldbsuite/test/cpu_feature.py 
b/lldb/packages/Python/lldbsuite/test/cpu_feature.py
index b46a5acc596f0..d7668c1884e40 100644
--- a/lldb/packages/Python/lldbsuite/test/cpu_feature.py
+++ b/lldb/packages/Python/lldbsuite/test/cpu_feature.py
@@ -62,7 +62,7 @@ def _is_supported_darwin(self, cmd_runner):
 class AArch64:
     FPMR = CPUFeature("fpmr")
     GCS = CPUFeature("gcs")
-    MTE = CPUFeature("mte")
+    MTE = CPUFeature("mte", "hw.optional.arm.FEAT_MTE4")
     MTE_STORE_ONLY = CPUFeature("mtestoreonly")
     PTR_AUTH = CPUFeature("paca", "hw.optional.arm.FEAT_PAuth2")
     SME = CPUFeature("sme", "hw.optional.arm.FEAT_SME")

diff  --git a/lldb/test/API/macosx/mte/Makefile 
b/lldb/test/API/macosx/mte/Makefile
new file mode 100644
index 0000000000000..cb20942805e2a
--- /dev/null
+++ b/lldb/test/API/macosx/mte/Makefile
@@ -0,0 +1,12 @@
+C_SOURCES := main.c
+
+EXE := uaf_mte
+
+all: uaf_mte sign
+
+include Makefile.rules
+
+sign: mte-entitlements.plist uaf_mte
+ifeq ($(OS),Darwin)
+       codesign -s - -f --entitlements $^
+endif

diff  --git a/lldb/test/API/macosx/mte/TestDarwinMTE.py 
b/lldb/test/API/macosx/mte/TestDarwinMTE.py
new file mode 100644
index 0000000000000..ef858b1fc2710
--- /dev/null
+++ b/lldb/test/API/macosx/mte/TestDarwinMTE.py
@@ -0,0 +1,110 @@
+"""Test MTE Memory Tagging on Apple platforms"""
+
+import lldb
+import re
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+import lldbsuite.test.cpu_feature as cpu_feature
+
+exe_name = "uaf_mte"  # Must match Makefile
+
+
+class TestDarwinMTE(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipUnlessFeature(cpu_feature.AArch64.MTE)
+    def test_tag_fault(self):
+        self.build()
+        exe = self.getBuildArtifact(exe_name)
+
+        target = self.dbg.CreateTarget(exe)
+        self.assertTrue(target, VALID_TARGET)
+
+        process = target.LaunchSimple(None, None, None)
+        self.assertState(process.GetState(), lldb.eStateStopped, 
PROCESS_STOPPED)
+
+        self.expect(
+            "thread info",
+            substrs=[
+                "stop reason = EXC_ARM_MTE_TAG_FAULT",
+                "MTE tag mismatch detected",
+            ],
+        )
+
+    @skipUnlessFeature(cpu_feature.AArch64.MTE)
+    def test_memory_region(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// before free", lldb.SBFileSpec("main.c"), 
exe_name=exe_name
+        )
+
+        # (lldb) memory region ptr
+        # [0x00000001005ec000-0x00000001009ec000) rw-
+        # memory tagging: enabled
+        # Modified memory (dirty) page list provided, 2 entries.
+        # Dirty pages: 0x1005ec000, 0x1005fc000.
+        self.expect("memory region ptr", substrs=["memory tagging: enabled"])
+
+    @skipUnlessFeature(cpu_feature.AArch64.MTE)
+    def test_memory_read_with_tags(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// before free", lldb.SBFileSpec("main.c"), 
exe_name=exe_name
+        )
+
+        # (lldb) memory read ptr-16 ptr+48 --show-tags
+        # 0x7d2c00930: 00 00 00 00 00 00 00 00 d0 e3 a5 0a 02 00 00 00  
................ (tag: 0x3)
+        # 0x7d2c00940: 48 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00  
Hello........... (tag: 0xb)
+        # 0x7d2c00950: 57 6f 72 6c 64 00 00 00 00 00 00 00 00 00 00 00  
World........... (tag: 0xb)
+        # 0x7d2c00960: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
................ (tag: 0x9)
+        self.expect(
+            "memory read ptr-16 ptr+48 --show-tags",
+            substrs=[" Hello...........", " World..........."],
+            patterns=[r"(.*\(tag: 0x[0-9a-f]\)\n){4}"],
+        )
+
+    def _parse_pointer_tag(self, output):
+        return re.search(r"Logical tag: (0x[0-9a-f])", output).group(1)
+
+    def _parse_memory_tags(self, output, expected_tag_count):
+        tags = re.findall(r"\): (0x[0-9a-f])", output)
+        self.assertEqual(len(tags), expected_tag_count)
+        return tags
+
+    @skipUnlessFeature(cpu_feature.AArch64.MTE)
+    def test_memory_tag_read(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// before free", lldb.SBFileSpec("main.c"), 
exe_name=exe_name
+        )
+
+        # (lldb) memory tag read ptr-1 ptr+33
+        # Logical tag: 0x5
+        # Allocation tags:
+        # [0x100a65a40, 0x100a65a50): 0xf (mismatch)
+        # [0x100a65a50, 0x100a65a60): 0x5
+        # [0x100a65a60, 0x100a65a70): 0x5
+        # [0x100a65a70, 0x100a65a80): 0x2 (mismatch)
+        self.expect(
+            "memory tag read ptr-1 ptr+33",
+            substrs=["Logical tag: 0x", "Allocation tags:", "(mismatch)"],
+            patterns=[r"(\[.*\): 0x[0-9a-f].*\n){4}"],
+        )
+        output = self.res.GetOutput()
+        self.assertEqual(output.count("(mismatch)"), 2)
+        ptr_tag = self._parse_pointer_tag(output)
+        tags = self._parse_memory_tags(output, 4)
+        self.assertEqual(tags[1], ptr_tag)
+        self.assertEqual(tags[2], ptr_tag)
+        self.assertNotEqual(tags[0], ptr_tag)  # Memory that comes before/after
+        self.assertNotEqual(tags[3], ptr_tag)  # allocation has 
diff erent tag.
+
+        # Continue running until MTE fault
+        self.expect("process continue", substrs=["stop reason = 
EXC_ARM_MTE_TAG_FAULT"])
+
+        self.runCmd("memory tag read ptr-1 ptr+33")
+        output = self.res.GetOutput()
+        self.assertEqual(output.count("(mismatch)"), 4)
+        tags = self._parse_memory_tags(output, 4)
+        self.assertTrue(all(t != ptr_tag for t in tags))

diff  --git a/lldb/test/API/macosx/mte/main.c b/lldb/test/API/macosx/mte/main.c
new file mode 100644
index 0000000000000..f9f6b1594ef41
--- /dev/null
+++ b/lldb/test/API/macosx/mte/main.c
@@ -0,0 +1,28 @@
+#include <malloc/malloc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Produce some names on the trace
+const size_t tag_granule = 16;
+static uint8_t *my_malloc(void) { return malloc(2 * tag_granule); }
+static uint8_t *allocate(void) { return my_malloc(); }
+
+static void my_free(void *ptr) { free(ptr); }
+static void deallocate(void *ptr) { my_free(ptr); }
+
+static void touch_memory(uint8_t *ptr) { ptr[7] = 1; } // invalid access
+static void modify(uint8_t *ptr) { touch_memory(ptr); }
+
+int main() {
+  uint8_t *ptr = allocate();
+
+  strncpy((char *)ptr, "Hello", 16);
+  strncpy((char *)ptr + 16, "World", 16);
+
+  deallocate(ptr); // before free
+
+  modify(ptr); // use-after-free
+
+  return 0;
+}

diff  --git a/lldb/test/API/macosx/mte/mte-entitlements.plist 
b/lldb/test/API/macosx/mte/mte-entitlements.plist
new file mode 100644
index 0000000000000..6de5d5634d878
--- /dev/null
+++ b/lldb/test/API/macosx/mte/mte-entitlements.plist
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" 
"http://www.apple.com/DTDs/PropertyList-1.0.dtd";>
+<plist version="1.0">
+<dict>
+    <key>com.apple.security.hardened-process</key>
+    <true/>
+    <key>com.apple.security.hardened-process.checked-allocations</key>
+    <true/>
+</dict>
+</plist>

diff  --git a/lldb/tools/debugserver/source/DNB.cpp 
b/lldb/tools/debugserver/source/DNB.cpp
index f541134b43a1b..0cd48d91a682a 100644
--- a/lldb/tools/debugserver/source/DNB.cpp
+++ b/lldb/tools/debugserver/source/DNB.cpp
@@ -1386,6 +1386,16 @@ int DNBProcessMemoryRegionInfo(nub_process_t pid, 
nub_addr_t addr,
   return -1;
 }
 
+nub_bool_t DNBProcessGetMemoryTags(nub_process_t pid, nub_addr_t addr,
+                                   nub_size_t size,
+                                   std::vector<uint8_t> &tags) {
+  MachProcessSP procSP;
+  if (GetProcessSP(pid, procSP))
+    return procSP->Task().GetMemoryTags(addr, size, tags);
+
+  return false;
+}
+
 std::string DNBProcessGetProfileData(nub_process_t pid,
                                      DNBProfileDataScanType scanType) {
   MachProcessSP procSP;

diff  --git a/lldb/tools/debugserver/source/DNB.h 
b/lldb/tools/debugserver/source/DNB.h
index 10d1f68794355..1f3d5392c588f 100644
--- a/lldb/tools/debugserver/source/DNB.h
+++ b/lldb/tools/debugserver/source/DNB.h
@@ -105,6 +105,9 @@ nub_bool_t DNBProcessMemoryDeallocate(nub_process_t pid,
                                       nub_addr_t addr) DNB_EXPORT;
 int DNBProcessMemoryRegionInfo(nub_process_t pid, nub_addr_t addr,
                                DNBRegionInfo *region_info) DNB_EXPORT;
+nub_bool_t DNBProcessGetMemoryTags(nub_process_t pid, nub_addr_t addr,
+                                   nub_size_t size,
+                                   std::vector<uint8_t> &tags) DNB_EXPORT;
 std::string
 DNBProcessGetProfileData(nub_process_t pid,
                          DNBProfileDataScanType scanType) DNB_EXPORT;

diff  --git a/lldb/tools/debugserver/source/DNBDefs.h 
b/lldb/tools/debugserver/source/DNBDefs.h
index df8ca809d412c..d98399aed5e19 100644
--- a/lldb/tools/debugserver/source/DNBDefs.h
+++ b/lldb/tools/debugserver/source/DNBDefs.h
@@ -358,10 +358,11 @@ struct DNBExecutableImageInfo {
 struct DNBRegionInfo {
 public:
   DNBRegionInfo()
-      : addr(0), size(0), permissions(0), dirty_pages(), vm_types() {}
+      : addr(0), size(0), permissions(0), flags(), dirty_pages(), vm_types() {}
   nub_addr_t addr;
   nub_addr_t size;
   uint32_t permissions;
+  std::vector<std::string> flags;
   std::vector<nub_addr_t> dirty_pages;
   std::vector<std::string> vm_types;
 };

diff  --git a/lldb/tools/debugserver/source/MacOSX/MachTask.h 
b/lldb/tools/debugserver/source/MacOSX/MachTask.h
index 2284f6b99de91..c4a20b80fda95 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachTask.h
+++ b/lldb/tools/debugserver/source/MacOSX/MachTask.h
@@ -56,6 +56,8 @@ class MachTask {
   nub_size_t ReadMemory(nub_addr_t addr, nub_size_t size, void *buf);
   nub_size_t WriteMemory(nub_addr_t addr, nub_size_t size, const void *buf);
   int GetMemoryRegionInfo(nub_addr_t addr, DNBRegionInfo *region_info);
+  nub_bool_t GetMemoryTags(nub_addr_t addr, nub_size_t size,
+                           std::vector<uint8_t> &tags);
   std::string GetProfileData(DNBProfileDataScanType scanType);
 
   nub_addr_t AllocateMemory(nub_size_t size, uint32_t permissions);

diff  --git a/lldb/tools/debugserver/source/MacOSX/MachTask.mm 
b/lldb/tools/debugserver/source/MacOSX/MachTask.mm
index e2395cffb3565..21156feecba2c 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachTask.mm
+++ b/lldb/tools/debugserver/source/MacOSX/MachTask.mm
@@ -229,6 +229,23 @@
   return ret;
 }
 
+//----------------------------------------------------------------------
+// MachTask::GetMemoryTags
+//----------------------------------------------------------------------
+nub_bool_t MachTask::GetMemoryTags(nub_addr_t addr, nub_size_t size,
+                                   std::vector<uint8_t> &tags) {
+  task_t task = TaskPort();
+  if (task == TASK_NULL)
+    return false;
+
+  bool ok = m_vm_memory.GetMemoryTags(task, addr, size, tags);
+  DNBLogThreadedIf(LOG_MEMORY, "MachTask::GetMemoryTags ( addr = 0x%8.8llx, "
+                               "size = 0x%8.8llx ) => %s ( tag count = %llu)",
+                  (uint64_t)addr, (uint64_t)size, (ok ? "ok" : "err"),
+                  (uint64_t)tags.size());
+  return ok;
+}
+
 #define TIME_VALUE_TO_TIMEVAL(a, r)                                            
\
   do {                                                                         
\
     (r)->tv_sec = (a)->seconds;                                                
\

diff  --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp 
b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
index f3aa4d7d980fd..bb57245e7da48 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
+++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
@@ -13,6 +13,7 @@
 #include "MachVMMemory.h"
 #include "DNBLog.h"
 #include "MachVMRegion.h"
+#include <cassert>
 #include <dlfcn.h>
 #include <mach/mach_vm.h>
 #include <mach/shared_region.h>
@@ -123,6 +124,7 @@ nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, 
nub_addr_t address,
     region_info->addr = vmRegion.StartAddress();
     region_info->size = vmRegion.GetByteSize();
     region_info->permissions = vmRegion.GetDNBPermissions();
+    region_info->flags = vmRegion.GetFlags();
     region_info->dirty_pages =
         get_dirty_pages(task, vmRegion.StartAddress(), vmRegion.GetByteSize());
     region_info->vm_types = vmRegion.GetMemoryTypes();
@@ -150,6 +152,63 @@ nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, 
nub_addr_t address,
   return true;
 }
 
+// API availability:
+//  mach_vm_update_pointers_with_remote_tags() - 26.0
+//  VM_OFFSET_LIST_MAX macro - 26.1
+#ifndef VM_OFFSET_LIST_MAX
+#define VM_OFFSET_LIST_MAX 512
+#endif
+using mach_vm_offset_list_t = mach_vm_offset_t *;
+using mach_vm_update_pointers_with_remote_tags_t = kern_return_t(
+    mach_port_name_t target, mach_vm_offset_list_t in_pointer_list,
+    mach_msg_type_number_t in_pointer_listCnt,
+    mach_vm_offset_list_t out_pointer_list,
+    mach_msg_type_number_t *out_pointer_listCnt);
+
+nub_bool_t MachVMMemory::GetMemoryTags(task_t task, nub_addr_t address,
+                                       nub_size_t size,
+                                       std::vector<uint8_t> &tags) {
+  static auto mach_vm_update_pointers_with_remote_tags =
+      (mach_vm_update_pointers_with_remote_tags_t *)dlsym(
+          RTLD_DEFAULT, "mach_vm_update_pointers_with_remote_tags");
+  assert(mach_vm_update_pointers_with_remote_tags);
+
+  // Max batch size supported by mach_vm_update_pointers_with_remote_tags.
+  constexpr uint32_t max_ptr_count = VM_OFFSET_LIST_MAX;
+  constexpr uint32_t tag_shift = 56;
+  constexpr nub_addr_t tag_mask =
+      ((nub_addr_t)0x0f << tag_shift); // Lower half of top byte.
+  constexpr uint32_t tag_granule = 16;
+
+  mach_msg_type_number_t ptr_count =
+      (size / tag_granule) + ((size % tag_granule > 0) ? 1 : 0);
+  ptr_count = std::min(ptr_count, max_ptr_count);
+
+  auto ptr_arr = std::make_unique<mach_vm_offset_t[]>(ptr_count);
+  for (size_t i = 0; i < ptr_count; i++)
+    ptr_arr[i] = (address + i * tag_granule);
+
+  mach_msg_type_number_t ptr_count_out = ptr_count;
+  m_err = mach_vm_update_pointers_with_remote_tags(
+      task, ptr_arr.get(), ptr_count, ptr_arr.get(), &ptr_count_out);
+
+  const bool failed = (m_err.Fail() || (ptr_count != ptr_count_out));
+  if (failed || DNBLogCheckLogBit(LOG_MEMORY))
+    m_err.LogThreaded("::mach_vm_update_pointers_with_remote_tags ( task = "
+                      "0x%4.4x, ptr_count = %d ) => %i ( ptr_count_out = %d)",
+                      task, ptr_count, m_err.Status(), ptr_count_out);
+  if (failed)
+    return false;
+
+  tags.reserve(ptr_count);
+  for (size_t i = 0; i < ptr_count; i++) {
+    nub_addr_t tag = (ptr_arr[i] & tag_mask) >> tag_shift;
+    tags.push_back(tag);
+  }
+
+  return true;
+}
+
 static uint64_t GetPhysicalMemory() {
   // This doesn't change often at all. No need to poll each time.
   static uint64_t physical_memory = 0;

diff  --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h 
b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h
index 05d2c029b9980..8a7616091fbb3 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h
+++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h
@@ -28,6 +28,8 @@ class MachVMMemory {
   nub_size_t PageSize(task_t task);
   nub_bool_t GetMemoryRegionInfo(task_t task, nub_addr_t address,
                                  DNBRegionInfo *region_info);
+  nub_bool_t GetMemoryTags(task_t task, nub_addr_t address, nub_size_t size,
+                           std::vector<uint8_t> &tags);
   nub_bool_t GetMemoryProfile(DNBProfileDataScanType scanType, task_t task,
                               struct task_basic_info ti, cpu_type_t cputype,
                               nub_process_t pid, vm_statistics64_data_t 
&vminfo,

diff  --git a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp 
b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
index 97908b4acaf28..9d0d60fdaaed9 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
+++ b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
@@ -114,6 +114,11 @@ bool MachVMRegion::RestoreProtections() {
   return false;
 }
 
+#ifdef VM_REGION_FLAG_JIT_ENABLED
+#define VM_REGION_HAS_FLAGS 1
+#else
+#define VM_REGION_HAS_FLAGS 0
+#endif
 bool MachVMRegion::GetRegionForAddress(nub_addr_t addr) {
   // Restore any original protections and clear our vars
   Clear();
@@ -140,6 +145,30 @@ bool MachVMRegion::GetRegionForAddress(nub_addr_t addr) {
   if (failed)
     return false;
   if (log_protections) {
+#if VM_REGION_HAS_FLAGS
+    DNBLogThreaded("info = { prot = %u, "
+                   "max_prot = %u, "
+                   "inheritance = 0x%8.8x, "
+                   "offset = 0x%8.8llx, "
+                   "user_tag = 0x%8.8x, "
+                   "ref_count = %u, "
+                   "shadow_depth = %u, "
+                   "ext_pager = %u, "
+                   "share_mode = %u, "
+                   "is_submap = %d, "
+                   "behavior = %d, "
+                   "object_id = 0x%8.8x, "
+                   "user_wired_count = 0x%4.4x, "
+                   "flags = %d }",
+                   m_data.protection, m_data.max_protection, 
m_data.inheritance,
+                   (uint64_t)m_data.offset, m_data.user_tag, m_data.ref_count,
+                   m_data.shadow_depth, m_data.external_pager,
+                   m_data.share_mode, m_data.is_submap, m_data.behavior,
+                   m_data.object_id, m_data.user_wired_count, m_data.flags);
+#else
+    // Duplicate log call instead of #if-defing printing of flags to avoid
+    // compiler warning: 'embedding a directive within macro arguments has
+    // undefined behavior'
     DNBLogThreaded("info = { prot = %u, "
                    "max_prot = %u, "
                    "inheritance = 0x%8.8x, "
@@ -158,6 +187,7 @@ bool MachVMRegion::GetRegionForAddress(nub_addr_t addr) {
                    m_data.shadow_depth, m_data.external_pager,
                    m_data.share_mode, m_data.is_submap, m_data.behavior,
                    m_data.object_id, m_data.user_wired_count);
+#endif
   }
   m_curr_protection = m_data.protection;
 
@@ -183,6 +213,22 @@ uint32_t MachVMRegion::GetDNBPermissions() const {
   return dnb_permissions;
 }
 
+#ifndef VM_REGION_FLAG_MTE_ENABLED
+#define VM_REGION_FLAG_MTE_ENABLED 0x4
+#endif
+std::vector<std::string> MachVMRegion::GetFlags() const {
+  std::vector<std::string> flags;
+#if VM_REGION_HAS_FLAGS
+  if (m_data.flags & VM_REGION_FLAG_JIT_ENABLED)
+    flags.push_back("jit");
+  if (m_data.flags & VM_REGION_FLAG_TPRO_ENABLED)
+    flags.push_back("tpro");
+  if (m_data.flags & VM_REGION_FLAG_MTE_ENABLED)
+    flags.push_back("mt");
+#endif
+  return flags;
+}
+
 std::vector<std::string> MachVMRegion::GetMemoryTypes() const {
   std::vector<std::string> types;
   if (m_data.user_tag == VM_MEMORY_STACK) {

diff  --git a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h 
b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h
index cb7705893c7ed..ba6e1f3bfa70e 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h
+++ b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h
@@ -40,9 +40,10 @@ class MachVMRegion {
                       vm_prot_t prot);
   bool RestoreProtections();
   bool GetRegionForAddress(nub_addr_t addr);
-  std::vector<std::string> GetMemoryTypes() const;
 
   uint32_t GetDNBPermissions() const;
+  std::vector<std::string> GetFlags() const;
+  std::vector<std::string> GetMemoryTypes() const;
 
   const DNBError &GetError() { return m_err; }
 

diff  --git a/lldb/tools/debugserver/source/RNBRemote.cpp 
b/lldb/tools/debugserver/source/RNBRemote.cpp
index d9fb22c6a1c06..434e9cfa40fb4 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -22,6 +22,9 @@
 #include <mach/mach_vm.h>
 #include <mach/task_info.h>
 #include <memory>
+#if __has_include(<os/security_config.h>)
+#include <os/security_config.h>
+#endif
 #include <pwd.h>
 #include <string>
 #include <sys/stat.h>
@@ -502,6 +505,8 @@ void RNBRemote::CreatePacketTable() {
       memory_region_info, &RNBRemote::HandlePacket_MemoryRegionInfo, NULL,
       "qMemoryRegionInfo", "Return size and attributes of a memory region that 
"
                            "contains the given address"));
+  t.push_back(Packet(get_memory_tags, &RNBRemote::HandlePacket_qMemTags, NULL,
+                     "qMemTags", "Return tags for a region of memory"));
   t.push_back(Packet(get_profile_data, &RNBRemote::HandlePacket_GetProfileData,
                      NULL, "qGetProfileData",
                      "Return profiling data of the current target."));
@@ -3475,6 +3480,18 @@ static bool GetProcessNameFrom_vAttach(const char *&p,
   return return_val;
 }
 
+static bool supports_memory_tagging() {
+  const char *name = "hw.optional.arm.FEAT_MTE4";
+  uint32_t val;
+  size_t len = sizeof(val);
+  int ret = ::sysctlbyname(name, &val, &len, nullptr, 0);
+  if (ret != 0)
+    return false;
+
+  assert(len == sizeof(val));
+  return val;
+}
+
 rnb_err_t RNBRemote::HandlePacket_qSupported(const char *p) {
   uint32_t max_packet_size = 128 * 1024; // 128 KiB is a reasonable max packet
                                          // size--debugger can always use less
@@ -3505,6 +3522,9 @@ rnb_err_t RNBRemote::HandlePacket_qSupported(const char 
*p) {
   reply << "SupportedWatchpointTypes=x86_64;";
 #endif
 
+  if (supports_memory_tagging())
+    reply << "memory-tagging+;";
+
   return SendPacket(reply.str().c_str());
 }
 
@@ -4251,7 +4271,6 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const 
char *p) {
      is in unmapped memory
          Region lookup cannot be performed on this platform or process is not
      yet launched
-         This packet isn't implemented
 
      Examples of use:
         qMemoryRegionInfo:3a55140
@@ -4303,6 +4322,16 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const 
char *p) {
       ostrm << 'x';
     ostrm << ';';
 
+    if (!region_info.flags.empty()) {
+      ostrm << "flags:";
+      for (size_t i = 0; i < region_info.flags.size(); i++) {
+        if (i != 0)
+          ostrm << " "; // Separator is whitespace
+        ostrm << region_info.flags[i];
+      }
+      ostrm << ";";
+    }
+
     ostrm << "dirty-pages:";
     if (region_info.dirty_pages.size() > 0) {
       bool first = true;
@@ -4327,6 +4356,62 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const 
char *p) {
   return SendPacket(ostrm.str());
 }
 
+// qMemTags:<hex address>,<hex length>:<hex type>
+rnb_err_t RNBRemote::HandlePacket_qMemTags(const char *p) {
+  nub_process_t pid = m_ctx.ProcessID();
+  if (pid == INVALID_NUB_PROCESS)
+    return SendPacket("OK");
+
+  StdStringExtractor packet(p);
+  packet.SetFilePos(strlen("qMemTags:"));
+
+  // Address
+  nub_addr_t addr =
+      packet.GetHexMaxU64(StdStringExtractor::BigEndian, INVALID_NUB_ADDRESS);
+  if (addr == INVALID_NUB_ADDRESS)
+    return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
+                                  "Invalid/missing address in qMemTags 
packet");
+  // ,
+  if (packet.GetChar() != ',')
+    return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
+                                  "Invalid qMemTags packet format");
+  // Length
+  uint64_t length = packet.GetHexMaxU64(StdStringExtractor::BigEndian, 0);
+  if (length == 0)
+    return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
+                                  "Invalid/missing length in qMemTags packet");
+  // :
+  if (packet.GetChar() != ':')
+    return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
+                                  "Invalid qMemTags packet format");
+  // Type
+  // On the LLDB side this is a `int32_t` serialized as (unsigned) hex, which
+  // means negative values will show up as large positive values here.  Right
+  // now, we only support MTE (type 1), so we can ignore this complication.
+  uint32_t type = packet.GetHexMaxU32(StdStringExtractor::BigEndian, 0);
+  if (type != 1 /* MTE */)
+    return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
+                                  "Invalid/missing type in qMemTags packet, "
+                                  "only MTE (type 1) is supported");
+  // <EOF>
+  if (packet.GetBytesLeft() != 0)
+    return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
+                                  "Invalid qMemTags packet format");
+
+  std::vector<uint8_t> tags;
+  bool ok = DNBProcessGetMemoryTags(pid, addr, length, tags);
+  if (!ok)
+    return SendErrorPacket("E91");
+
+  std::ostringstream ostrm;
+  ostrm << "m"; // Multi part replies
+  for (uint8_t tag : tags) {
+    ostrm << RAWHEX8(tag); // 2 hex chars per tag
+  }
+
+  return SendPacket(ostrm.str());
+}
+
 // qGetProfileData;scan_type:0xYYYYYYY
 rnb_err_t RNBRemote::HandlePacket_GetProfileData(const char *p) {
   nub_process_t pid = m_ctx.ProcessID();
@@ -6162,6 +6247,21 @@ GetCPUTypesFromHost(nub_process_t pid) {
   return {cputype, cpusubtype};
 }
 
+static bool ProcessRunningWithMemoryTagging(pid_t pid) {
+#if __has_include(<os/security_config.h>)
+  if (__builtin_available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0,
+                          visionOS 26.0, driverkit 25.0, *)) {
+    os_security_config_t config;
+    int ret = ::os_security_config_get_for_proc(pid, &config);
+    if (ret != 0)
+      return false;
+
+    return (config & OS_SECURITY_CONFIG_MTE);
+  }
+#endif
+  return false;
+}
+
 // Note that all numeric values returned by qProcessInfo are hex encoded,
 // including the pid and the cpu type.
 
@@ -6338,6 +6438,9 @@ rnb_err_t RNBRemote::HandlePacket_qProcessInfo(const char 
*p) {
 
   rep << "vendor:apple;";
 
+  if (ProcessRunningWithMemoryTagging(pid))
+    rep << "mte:enabled;";
+
 #if defined(__LITTLE_ENDIAN__)
   rep << "endian:little;";
 #elif defined(__BIG_ENDIAN__)

diff  --git a/lldb/tools/debugserver/source/RNBRemote.h 
b/lldb/tools/debugserver/source/RNBRemote.h
index ad254ae90e2f7..cf1c978afcd23 100644
--- a/lldb/tools/debugserver/source/RNBRemote.h
+++ b/lldb/tools/debugserver/source/RNBRemote.h
@@ -121,6 +121,7 @@ class RNBRemote {
     set_list_threads_in_stop_reply,     // 'QListThreadsInStopReply:'
     sync_thread_state,                  // 'QSyncThreadState:'
     memory_region_info,                 // 'qMemoryRegionInfo:'
+    get_memory_tags,                    // 'qMemTags:'
     get_profile_data,                   // 'qGetProfileData'
     set_enable_profiling,               // 'QSetEnableAsyncProfiling'
     enable_compression,                 // 'QEnableCompression:'
@@ -237,6 +238,7 @@ class RNBRemote {
   rnb_err_t HandlePacket_SaveRegisterState(const char *p);
   rnb_err_t HandlePacket_RestoreRegisterState(const char *p);
   rnb_err_t HandlePacket_MemoryRegionInfo(const char *p);
+  rnb_err_t HandlePacket_qMemTags(const char *p);
   rnb_err_t HandlePacket_GetProfileData(const char *p);
   rnb_err_t HandlePacket_SetEnableAsyncProfiling(const char *p);
   rnb_err_t HandlePacket_QEnableCompression(const char *p);


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to