Hi all,

while debugging a fortran runtime library on a recent MacOS, I tried to use the
libsanitizer provided with gcc. It compiled, but no asan enabled program could
start on MacOS Sequoia. I figured from libsanitizers github, that the shared
cache needs to be consulted from some OS version on. The support present in
github uses proprietary extensions to C++, that do not compile on gcc. I figured
how to translate this into pure C++, which what the patch is about. The
solution is somewhat hacky and I don't know what to do with it. It works,
address faults are reported correctly at least on
x86_64-apple-darwin24.something. I am open for suggestions what to do it.
I do not assume, that upstream libsanitizer will apply the patch, because it is
GCC specific. 

I tried to rebase GCC's libsanitizer to the github's state, but there are more
locations where now proprietary extensions are used. To not loose the patch, I
thought I publish it here, so that at least some one may find it.

Regards,
        Andre
-- 
Andre Vehreschild * Email: vehre ad gmx dot de 
From b6a79171c253a5046e800a491d98b6169518d135 Mon Sep 17 00:00:00 2001
From: Andre Vehreschild <[email protected]>
Date: Mon, 22 Sep 2025 14:03:42 +0200
Subject: [PATCH] Libsanitizer: Mimick Objective-C for shared cache travesal on
 macOS.

Setup an Objective-C "functor" style object from C++ to traverse the
MacOS shared cache.  Most of the code comes from the original repo.  The
original code unfortunately only compile on clang due to certain
extensions.

libsanitizer/ChangeLog:

	* sanitizer_common/sanitizer_procmaps_mac.cpp (struct dyld_shared_cache_dylib_text_info):
	Define MacOS internal structure for shared cache traversal.
	(_dyld_get_shared_cache_uuid): Define MacOS internal routine.
	(_dyld_get_shared_cache_range): Same.
	(dyld_shared_cache_iterate_text): Same.
	(class HeaderIterator): Define a functor to mimick an
	Objective-C style object from C++.
	(GetDyldImageHeaderViaSharedCache): Traverse the shared cache.
	(get_dyld_hdr): Traverse the shared cache where available.
---
 .../sanitizer_procmaps_mac.cpp                | 75 +++++++++++++++++--
 1 file changed, 68 insertions(+), 7 deletions(-)

diff --git a/libsanitizer/sanitizer_common/sanitizer_procmaps_mac.cpp b/libsanitizer/sanitizer_common/sanitizer_procmaps_mac.cpp
index 64e9c4858b6..246c463d676 100644
--- a/libsanitizer/sanitizer_common/sanitizer_procmaps_mac.cpp
+++ b/libsanitizer/sanitizer_common/sanitizer_procmaps_mac.cpp
@@ -176,19 +176,80 @@ static mach_header *GetDyldImageHeaderViaVMRegion() {
   }
 }
 
+extern "C" {
+struct dyld_shared_cache_dylib_text_info {
+  uint64_t version; // current version 2
+  // following fields all exist in version 1
+  uint64_t loadAddressUnslid;
+  uint64_t textSegmentSize;
+  uuid_t dylibUuid;
+  const char *path; // pointer invalid at end of iterations
+  // following fields all exist in version 2
+  uint64_t textSegmentOffset; // offset from start of cache
+};
+typedef struct dyld_shared_cache_dylib_text_info dyld_shared_cache_dylib_text_info;
+extern bool _dyld_get_shared_cache_uuid(uuid_t uuid);
+extern const void *_dyld_get_shared_cache_range(size_t *length);
+extern int dyld_shared_cache_iterate_text(const uuid_t cacheUuid, const void *);
+} // extern "C"
+
+class HeaderIterator {
+public:
+  uptr cacheStart = 0;
+  mach_header *dyld_hdr;
+  typedef void(callback_t)(HeaderIterator *, const dyld_shared_cache_dylib_text_info *);
+  callback_t *callback;
+
+  static void cb_shim(HeaderIterator *that, const dyld_shared_cache_dylib_text_info *info) {
+    if (that)
+      that->find_header (info);
+  }
+
+  void find_header (const dyld_shared_cache_dylib_text_info *info) {
+    CHECK_GE(info->version, 2);
+    mach_header *hdr = (mach_header *) (cacheStart + info->textSegmentOffset);
+    if (IsDyldHdr(hdr))
+      dyld_hdr = hdr;
+  }
+
+  HeaderIterator(uptr cacheStart_)
+      : cacheStart(cacheStart_)
+      , dyld_hdr(nullptr) {
+    callback = &HeaderIterator::cb_shim;
+  }
+};
+
+static mach_header *GetDyldImageHeaderViaSharedCache() {
+  uuid_t uuid;
+  bool hasCache = _dyld_get_shared_cache_uuid(uuid);
+  if (!hasCache)
+    return nullptr;
+
+  size_t cacheLength;
+  uptr cacheStart = (uptr) _dyld_get_shared_cache_range(&cacheLength);
+  CHECK(cacheStart && cacheLength);
+
+  HeaderIterator hi(cacheStart);
+  int res = dyld_shared_cache_iterate_text(uuid, &hi);
+  CHECK_EQ(res, 0);
+
+  return hi.dyld_hdr;
+}
+
 const mach_header *get_dyld_hdr() {
   if (!dyld_hdr) {
     // On macOS 13+, dyld itself has moved into the shared cache.  Looking it up
     // via vm_region_recurse_64() causes spins/hangs/crashes.
-    // FIXME: find a way to do this compatible with GCC.
     if (GetMacosAlignedVersion() >= MacosVersion(13, 0)) {
-        VReport(1,
-                "looking up the dyld image header in the shared cache on "
-                "macOS 13+ is not yet supported.  Falling back to "
-                "lookup via vm_region_recurse_64().\n");
-        dyld_hdr = GetDyldImageHeaderViaVMRegion();
+      dyld_hdr = GetDyldImageHeaderViaSharedCache();
+      if (!dyld_hdr) {
+        Printf("Failed to lookup the dyld image header in the shared cache on "
+               "macOS 13+ (or no shared cache in use).  Falling back to lookup via"
+               "vm_region_recurse_64().\n");
+          dyld_hdr = GetDyldImageHeaderViaVMRegion();
+      }
     } else {
-      dyld_hdr = GetDyldImageHeaderViaVMRegion();
+        dyld_hdr = GetDyldImageHeaderViaVMRegion();
     }
     CHECK(dyld_hdr);
   }
-- 
2.51.0

Reply via email to