https://github.com/yln created https://github.com/llvm/llvm-project/pull/160952
Support for `qMemTags` packet in debugserver which allows usage of LLDB's `memory tag read` on Darwin. >From e16adb15486694876c5486604474ba6195f8dece Mon Sep 17 00:00:00 2001 From: Julian Lettner <[email protected]> Date: Wed, 23 Jul 2025 10:53:14 -0700 Subject: [PATCH 1/6] 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 <[email protected]> Date: Thu, 24 Jul 2025 15:39:08 -0700 Subject: [PATCH 2/6] 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 <[email protected]> Date: Mon, 4 Aug 2025 12:08:16 -0700 Subject: [PATCH 3/6] 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 <[email protected]> Date: Tue, 5 Aug 2025 15:38:25 -0700 Subject: [PATCH 4/6] 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 <[email protected]> Date: Thu, 7 Aug 2025 11:52:44 -0700 Subject: [PATCH 5/6] 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 <[email protected]> Date: Fri, 8 Aug 2025 19:45:32 -0700 Subject: [PATCH 6/6] 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> _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
