[Lldb-commits] [lldb] [debugserver] Support for `qMemTags` packet (PR #160952)
Julian Lettner via lldb-commits
lldb-commits at lists.llvm.org
Fri Sep 26 15:07:33 PDT 2025
https://github.com/yln updated https://github.com/llvm/llvm-project/pull/160952
>From e16adb15486694876c5486604474ba6195f8dece Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Wed, 23 Jul 2025 10:53:14 -0700
Subject: [PATCH 1/7] Extend MemoryRegionInfo to include flags
Extend handling of `qMemoryRegionInfo` packet to
add flags (`flags:<space-separated-flags>;`)
including whether or not the region is mapped with
taggable memory (`mt`).
---
lldb/tools/debugserver/source/DNBDefs.h | 3 ++-
.../source/MacOSX/MachVMMemory.cpp | 1 +
.../source/MacOSX/MachVMRegion.cpp | 19 +++++++++++++++++--
.../debugserver/source/MacOSX/MachVMRegion.h | 3 ++-
lldb/tools/debugserver/source/RNBRemote.cpp | 11 ++++++++++-
5 files changed, 32 insertions(+), 5 deletions(-)
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/MachVMMemory.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
index f3aa4d7d980fd..e5bc419d83200 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
+++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
@@ -123,6 +123,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();
diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
index 97908b4acaf28..efcc4c5f8d534 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
+++ b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
@@ -152,12 +152,13 @@ bool MachVMRegion::GetRegionForAddress(nub_addr_t addr) {
"is_submap = %d, "
"behavior = %d, "
"object_id = 0x%8.8x, "
- "user_wired_count = 0x%4.4x }",
+ "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.object_id, m_data.user_wired_count, m_data.flags);
}
m_curr_protection = m_data.protection;
@@ -183,6 +184,20 @@ 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 (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");
+ 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..81b8bdb311f5d 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -4251,7 +4251,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 +4302,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;
>From 0f365cd3c3009f8a1cb3c4db6af275bfe38ca7af Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Thu, 24 Jul 2025 15:39:08 -0700
Subject: [PATCH 2/7] Add support for reading memory tags
Add support for reading memory tags (`qMemTags`
packet) to debugserver.
rdar://152169151
---
lldb/tools/debugserver/source/DNB.cpp | 10 ++++
lldb/tools/debugserver/source/DNB.h | 3 +
.../debugserver/source/MacOSX/MachTask.h | 2 +
.../debugserver/source/MacOSX/MachTask.mm | 21 ++++++-
.../source/MacOSX/MachVMMemory.cpp | 47 +++++++++++++++
.../debugserver/source/MacOSX/MachVMMemory.h | 2 +
lldb/tools/debugserver/source/RNBRemote.cpp | 58 +++++++++++++++++++
lldb/tools/debugserver/source/RNBRemote.h | 2 +
8 files changed, 143 insertions(+), 2 deletions(-)
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/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 8ae9d4df99657..f25cfac7b030a 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachTask.mm
+++ b/lldb/tools/debugserver/source/MacOSX/MachTask.mm
@@ -213,7 +213,7 @@
}
//----------------------------------------------------------------------
-// MachTask::MemoryRegionInfo
+// MachTask::GetMemoryRegionInfo
//----------------------------------------------------------------------
int MachTask::GetMemoryRegionInfo(nub_addr_t addr, DNBRegionInfo *region_info) {
task_t task = TaskPort();
@@ -221,7 +221,7 @@
return -1;
int ret = m_vm_memory.GetMemoryRegionInfo(task, addr, region_info);
- DNBLogThreadedIf(LOG_MEMORY, "MachTask::MemoryRegionInfo ( addr = 0x%8.8llx "
+ DNBLogThreadedIf(LOG_MEMORY, "MachTask::GetMemoryRegionInfo ( addr = 0x%8.8llx "
") => %i (start = 0x%8.8llx, size = 0x%8.8llx, "
"permissions = %u)",
(uint64_t)addr, ret, (uint64_t)region_info->addr,
@@ -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 e5bc419d83200..de2060e323d24 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
+++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
@@ -151,6 +151,53 @@ 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
+nub_bool_t MachVMMemory::GetMemoryTags(task_t task, nub_addr_t address,
+ nub_size_t size,
+ std::vector<uint8_t> &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<nub_addr_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/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index 81b8bdb311f5d..0a7a0c7cc202d 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -502,6 +502,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."));
@@ -4336,6 +4338,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();
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);
>From 99ea474013993b5b954aed06abe2f761d4b54dab Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Mon, 4 Aug 2025 12:08:16 -0700
Subject: [PATCH 3/7] Check whether processes may run with MTE enabled
Add support for determining if processes can run
with MTE enabled (`memory-tagging+` feature in
`qSupported` packet).
---
lldb/tools/debugserver/source/RNBRemote.cpp | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index 0a7a0c7cc202d..1fcbcddc3c119 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>) // macOS 26.1
+#include <os/security_config.h>
+#endif
#include <pwd.h>
#include <string>
#include <sys/stat.h>
@@ -3477,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
@@ -3507,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());
}
>From 2b82f3f16f5b328a6e1a46a90bddf36e5d74cf99 Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Tue, 5 Aug 2025 15:38:25 -0700
Subject: [PATCH 4/7] Indicate if process is MTE enabled
Indicate whether a process instance is running
with MTE enabled in the response to the
`qProcessInfo` packet.
---
.../source/MacOSX/MachVMMemory.cpp | 5 ++--
lldb/tools/debugserver/source/RNBRemote.cpp | 27 +++++++++++++++++++
2 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
index de2060e323d24..be1b3ce786d6f 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
+++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
@@ -172,10 +172,9 @@ nub_bool_t MachVMMemory::GetMemoryTags(task_t task, nub_addr_t address,
(size / tag_granule) + ((size % tag_granule > 0) ? 1 : 0);
ptr_count = std::min(ptr_count, max_ptr_count);
- auto ptr_arr = std::make_unique<nub_addr_t[]>(ptr_count);
- for (size_t i = 0; i < ptr_count; i++) {
+ 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(
diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index 1fcbcddc3c119..3499f80eebee6 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -6247,6 +6247,30 @@ GetCPUTypesFromHost(nub_process_t pid) {
return {cputype, cpusubtype};
}
+#if !__has_include(<os/security_config.h>) // macOS 26.1
+extern "C" {
+using os_security_config_t = uint64_t;
+#define OS_SECURITY_CONFIG_MTE 0x4
+
+API_AVAILABLE(macos(26.0), ios(26.0), tvos(26.0), watchos(26.0), visionos(26.0),
+ driverkit(25.0))
+OS_EXPORT OS_NOTHROW OS_NONNULL_ALL int
+os_security_config_get_for_proc(pid_t pid, os_security_config_t *config);
+}
+#endif
+static bool ProcessRunningWithMemoryTagging(pid_t pid) {
+ 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);
+ }
+ return false;
+}
+
// Note that all numeric values returned by qProcessInfo are hex encoded,
// including the pid and the cpu type.
@@ -6423,6 +6447,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__)
>From 5927388933afd379a76ec76acf376a4b4b065861 Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Thu, 7 Aug 2025 11:52:44 -0700
Subject: [PATCH 5/7] Ensure we can build with older SDKs
Ensure we can keep building debugserver with
`memory tag read <addr-expr>` support with older
SDKs.
---
.../source/MacOSX/MachVMMemory.cpp | 16 ++++++++--
.../source/MacOSX/MachVMRegion.cpp | 31 +++++++++++++++++++
lldb/tools/debugserver/source/RNBRemote.cpp | 15 ++-------
3 files changed, 48 insertions(+), 14 deletions(-)
diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
index be1b3ce786d6f..7ecd57e7bbfeb 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>
@@ -157,12 +158,23 @@ nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address,
#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
@@ -177,7 +189,7 @@ nub_bool_t MachVMMemory::GetMemoryTags(task_t task, nub_addr_t address,
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(
+ 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));
diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
index efcc4c5f8d534..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,7 @@ 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, "
@@ -159,6 +165,29 @@ 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, 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, "
+ "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 }",
+ 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);
+#endif
}
m_curr_protection = m_data.protection;
@@ -189,12 +218,14 @@ uint32_t MachVMRegion::GetDNBPermissions() const {
#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;
}
diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index 3499f80eebee6..be7108f633517 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -22,7 +22,7 @@
#include <mach/mach_vm.h>
#include <mach/task_info.h>
#include <memory>
-#if __has_include(<os/security_config.h>) // macOS 26.1
+#if __has_include(<os/security_config.h>)
#include <os/security_config.h>
#endif
#include <pwd.h>
@@ -6247,18 +6247,8 @@ GetCPUTypesFromHost(nub_process_t pid) {
return {cputype, cpusubtype};
}
-#if !__has_include(<os/security_config.h>) // macOS 26.1
-extern "C" {
-using os_security_config_t = uint64_t;
-#define OS_SECURITY_CONFIG_MTE 0x4
-
-API_AVAILABLE(macos(26.0), ios(26.0), tvos(26.0), watchos(26.0), visionos(26.0),
- driverkit(25.0))
-OS_EXPORT OS_NOTHROW OS_NONNULL_ALL int
-os_security_config_get_for_proc(pid_t pid, os_security_config_t *config);
-}
-#endif
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;
@@ -6268,6 +6258,7 @@ static bool ProcessRunningWithMemoryTagging(pid_t pid) {
return (config & OS_SECURITY_CONFIG_MTE);
}
+#endif
return false;
}
>From 433c820248130b0849550e7c3b3b111db36d0afe Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Fri, 8 Aug 2025 19:45:32 -0700
Subject: [PATCH 6/7] Add test for Darwin MTE support
Add test for Darwin MTE support which covers
support for custom tag fault message and reading
memory tags via `memory tag read`.
---
.../Python/lldbsuite/test/cpu_feature.py | 2 +-
lldb/test/API/macosx/mte/Makefile | 12 +++
lldb/test/API/macosx/mte/TestDarwinMTE.py | 90 +++++++++++++++++++
lldb/test/API/macosx/mte/main.c | 29 ++++++
.../API/macosx/mte/mte-entitlements.plist | 10 +++
5 files changed, 142 insertions(+), 1 deletion(-)
create mode 100644 lldb/test/API/macosx/mte/Makefile
create mode 100644 lldb/test/API/macosx/mte/TestDarwinMTE.py
create mode 100644 lldb/test/API/macosx/mte/main.c
create mode 100644 lldb/test/API/macosx/mte/mte-entitlements.plist
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..787f29e3530cd
--- /dev/null
+++ b/lldb/test/API/macosx/mte/TestDarwinMTE.py
@@ -0,0 +1,90 @@
+"""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_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):
+ return re.search(r"Logical tag: (0x[0-9a-f])", self.res.GetOutput()).group(1)
+
+ def _parse_memory_tags(self, expected_tag_count):
+ tags = re.findall(r"\): (0x[0-9a-f])", self.res.GetOutput())
+ 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}"]
+ )
+ self.assertEqual(self.res.GetOutput().count("(mismatch)"), 2)
+ ptr_tag = self._parse_pointer_tag()
+ tags = self._parse_memory_tags(4)
+ self.assertEqual(tags[1], ptr_tag)
+ self.assertEqual(tags[2], ptr_tag)
+ self.assertNotEqual(tags[0], ptr_tag)
+ self.assertNotEqual(tags[3], ptr_tag)
+
+ # Continue running until MTE fault
+ self.runCmd("process continue")
+
+ self.runCmd("memory tag read ptr-1 ptr+33")
+ self.assertEqual(self.res.GetOutput().count("(mismatch)"), 4)
+ tags = self._parse_memory_tags(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..ca61c05b19a38
--- /dev/null
+++ b/lldb/test/API/macosx/mte/main.c
@@ -0,0 +1,29 @@
+#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;
+uint8_t *my_malloc(void) { return malloc(2 * tag_granule); }
+uint8_t *allocate(void) { return my_malloc(); }
+
+void my_free(void *ptr) { free(ptr); }
+void deallocate(void *ptr) { my_free(ptr); }
+
+void touch_memory(uint8_t *ptr) { ptr[7] = 1; } // invalid access
+void modify(uint8_t *ptr) { touch_memory(ptr); }
+
+int main() {
+ uint8_t *ptr = allocate();
+ printf("ptr: %p\n", ptr);
+
+ strcpy((char *)ptr, "Hello");
+ strcpy((char *)ptr+16, "World");
+
+ 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>
>From 90b63966322200edb2a1323930ebe3c4aa4d8f7d Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Fri, 26 Sep 2025 14:59:41 -0700
Subject: [PATCH 7/7] Run formatters [NFC]
---
lldb/test/API/macosx/mte/TestDarwinMTE.py | 10 +++++++---
lldb/test/API/macosx/mte/main.c | 8 ++++----
lldb/tools/debugserver/source/RNBRemote.cpp | 2 +-
3 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/lldb/test/API/macosx/mte/TestDarwinMTE.py b/lldb/test/API/macosx/mte/TestDarwinMTE.py
index 787f29e3530cd..a3f15ce698fe4 100644
--- a/lldb/test/API/macosx/mte/TestDarwinMTE.py
+++ b/lldb/test/API/macosx/mte/TestDarwinMTE.py
@@ -26,7 +26,10 @@ def test_tag_fault(self):
self.expect(
"thread info",
- substrs=["stop reason = EXC_ARM_MTE_TAG_FAULT", "MTE tag mismatch detected"],
+ substrs=[
+ "stop reason = EXC_ARM_MTE_TAG_FAULT",
+ "MTE tag mismatch detected",
+ ],
)
@skipUnlessFeature(cpu_feature.AArch64.MTE)
@@ -44,7 +47,8 @@ def test_memory_read_with_tags(self):
self.expect(
"memory read ptr-16 ptr+48 --show-tags",
substrs=[" Hello...........", " World..........."],
- patterns=[r"(.*\(tag: 0x[0-9a-f]\)\n){4}"])
+ patterns=[r"(.*\(tag: 0x[0-9a-f]\)\n){4}"],
+ )
def _parse_pointer_tag(self):
return re.search(r"Logical tag: (0x[0-9a-f])", self.res.GetOutput()).group(1)
@@ -71,7 +75,7 @@ def test_memory_tag_read(self):
self.expect(
"memory tag read ptr-1 ptr+33",
substrs=["Logical tag: 0x", "Allocation tags:", "(mismatch)"],
- patterns=[r"(\[.*\): 0x[0-9a-f].*\n){4}"]
+ patterns=[r"(\[.*\): 0x[0-9a-f].*\n){4}"],
)
self.assertEqual(self.res.GetOutput().count("(mismatch)"), 2)
ptr_tag = self._parse_pointer_tag()
diff --git a/lldb/test/API/macosx/mte/main.c b/lldb/test/API/macosx/mte/main.c
index ca61c05b19a38..e3dd0119db006 100644
--- a/lldb/test/API/macosx/mte/main.c
+++ b/lldb/test/API/macosx/mte/main.c
@@ -11,7 +11,7 @@ uint8_t *allocate(void) { return my_malloc(); }
void my_free(void *ptr) { free(ptr); }
void deallocate(void *ptr) { my_free(ptr); }
-void touch_memory(uint8_t *ptr) { ptr[7] = 1; } // invalid access
+void touch_memory(uint8_t *ptr) { ptr[7] = 1; } // invalid access
void modify(uint8_t *ptr) { touch_memory(ptr); }
int main() {
@@ -19,11 +19,11 @@ int main() {
printf("ptr: %p\n", ptr);
strcpy((char *)ptr, "Hello");
- strcpy((char *)ptr+16, "World");
+ strcpy((char *)ptr + 16, "World");
- deallocate(ptr); // before free
+ deallocate(ptr); // before free
- modify(ptr); // use-after-free
+ modify(ptr); // use-after-free
return 0;
}
diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index be7108f633517..434e9cfa40fb4 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -4326,7 +4326,7 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const char *p) {
ostrm << "flags:";
for (size_t i = 0; i < region_info.flags.size(); i++) {
if (i != 0)
- ostrm << " "; // Separator is whitespace
+ ostrm << " "; // Separator is whitespace
ostrm << region_info.flags[i];
}
ostrm << ";";
More information about the lldb-commits
mailing list