Loading...
dyld/DyldAPIs.cpp dyld-1340 dyld-1165.3
--- dyld/dyld-1340/dyld/DyldAPIs.cpp
+++ dyld/dyld-1165.3/dyld/DyldAPIs.cpp
@@ -47,9 +47,7 @@
 
   #include "dyld_process_info_internal.h"
   #include "OptimizerObjC.h"
-#else
-  #include <liblibc/plat/dyld/exclaves_dyld.h>
-#endif // !TARGET_OS_EXCLAVEKIT
+#endif
 
 #include "mach-o/dyld.h"
 #include "mach-o/dyld_priv.h"
@@ -64,10 +62,7 @@
 #include "objc-shared-cache.h"
 #include "DyldAPIs.h"
 #include "JustInTimeLoader.h"
-#include "Utilities.h"
-
-#include "Header.h"
-#include "Universal.h"
+#include "Utils.h"
 
 #if !TARGET_OS_EXCLAVEKIT
 // internal libc.a variable that needs to be reset during fork()
@@ -76,11 +71,6 @@
 
 using dyld3::MachOFile;
 using dyld3::MachOLoaded;
-using mach_o::Header;
-using mach_o::Platform;
-using mach_o::PlatformAndVersions;
-using mach_o::Universal;
-using mach_o::Version32;
 
 extern const dyld3::MachOLoaded __dso_handle;
 
@@ -133,19 +123,16 @@
 
 static const Loader* loaderFromHandle(void* h, bool& firstOnly)
 {
-    firstOnly = false;
-    if ( h == nullptr )
-        return nullptr;
-
     uintptr_t dyldStart  = (uintptr_t)&__dso_handle;
 
 #if __has_feature(ptrauth_calls)
-    // Note we don't use ptrauth_auth_data, as we don't want to crash on bad handles
-    void* strippedHandle = ptrauth_strip(h, ptrauth_key_process_dependent_data);
-    void* validHandle = ptrauth_sign_unauthenticated(strippedHandle, ptrauth_key_process_dependent_data, ptrauth_string_discriminator("dlopen"));
-    if ( h != validHandle )
-        return nullptr;
-    h = strippedHandle;
+    if ( h != nullptr ) {
+        // Note we don't use ptrauth_auth_data, as we don't want to crash on bad handles
+        void* strippedHandle = ptrauth_strip(h, ptrauth_key_process_dependent_data);
+        void* validHandle = ptrauth_sign_unauthenticated(strippedHandle, ptrauth_key_process_dependent_data, ptrauth_string_discriminator("dlopen"));
+        if ( h == validHandle )
+            h = strippedHandle;
+    }
 #endif
 
     firstOnly = (((uintptr_t)h) & 1);
@@ -203,16 +190,14 @@
 #endif
 }
 
-// called during libSystem_initializer
-void APIs::_libdyld_initialize()
+void APIs::_libdyld_initialize(const dyld4::LibSystemHelpers* helpers)
 {
     // Since this called from libdyld`_dyld_initializer the allocator will be marked read only
-    MemoryManager::withWritableMemory([&]{
-        // up to this point locks in dyld did nothing.
-        // now that libSystem is initialized, actually start using locks in dyld
-        this->locks.setHelpers(libSystemHelpers);
-
-        // set up thread-local-variables in initial images and dlerror handling
+    memoryManager.withWritableMemory([&]{
+        // libSystem.dylib is being initialized, set helpers pointer
+        this->setHelpers(helpers);
+
+        // set up thread-local-variable and dlerror handling
         this->initialize();
     });
 }
@@ -233,7 +218,7 @@
 #if BUILDING_DYLD && TARGET_OS_OSX && __x86_64__
     // some old macOS apps assume index of zero is always the main executable even when dylibs are inserted, so permute order
     uint32_t insertCount = config.pathOverrides.insertedDylibCount();
-    if ( (insertCount != 0) && (config.process.platform == Platform::macOS) && (config.process.mainExecutableMinOSVersion < 0x0000C0000) ) {
+    if ( (insertCount != 0) && (config.process.platform == dyld3::Platform::macOS) && (config.process.mainExecutableMinOSVersion < 0x0000C0000) ) {
         // special case index==0 to map to the main executable
         if ( index == 0 )
             return insertCount;
@@ -295,7 +280,7 @@
     __block const char* result = 0;
     locks.withLoadersReadLock(^{
         if ( imageIndex < loaded.size() )
-            result = loaded[normalizeImageIndex(config, imageIndex)]->path(*this);
+            result = loaded[normalizeImageIndex(config, imageIndex)]->path();
     });
     if ( config.log.apis )
         log("_dyld_get_image_name(%u) => %s\n", imageIndex, result);
@@ -345,12 +330,12 @@
     __block int32_t result = -1;
     locks.withLoadersReadLock(^{
         for ( const dyld4::Loader* image : loaded ) {
-            const Header* hdr = (const Header*)image->loadAddress(*this);
+            const MachOLoaded* ml = image->loadAddress(*this);
             const char*        installName;
-            Version32          currentVersion;
-            Version32          compatVersion;
-            if ( hdr->getDylibInstallName(&installName, &compatVersion, &currentVersion) && nameMatch(installName, libraryName) ) {
-                result = currentVersion.value();
+            uint32_t           currentVersion;
+            uint32_t           compatVersion;
+            if ( ml->getDylibInstallName(&installName, &compatVersion, &currentVersion) && nameMatch(installName, libraryName) ) {
+                result = currentVersion;
                 break;
             }
         }
@@ -362,13 +347,17 @@
 
 uint32_t APIs::dyld_get_program_sdk_watch_os_version()
 {
-    uint32_t            retval  = 0;
-    PlatformAndVersions pvs     = getImagePlatformAndVersions(config.process.mainExecutableHdr);
-    
-    if ( pvs.platform.basePlatform() == Platform::watchOS ) {
-        retval = pvs.sdk.value();
-    }
-    
+    __block uint32_t retval       = 0;
+    __block bool     versionFound = false;
+    forEachImageVersion(config.process.mainExecutable, ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) {
+        if ( versionFound )
+            return;
+
+        if ( MachOFile::basePlatform((dyld3::Platform)platform) == dyld3::Platform::watchOS ) {
+            versionFound = true;
+            retval       = sdk_version;
+        }
+    });
     if ( config.log.apis )
         log("dyld_get_program_sdk_watch_os_version() => 0x%08X\n", retval);
     return retval;
@@ -376,47 +365,56 @@
 
 uint32_t APIs::dyld_get_program_min_watch_os_version()
 {
-    uint32_t            retval  = 0;
-    PlatformAndVersions pvs     = getImagePlatformAndVersions(config.process.mainExecutableHdr);
-    
-    if ( pvs.platform.basePlatform() == Platform::watchOS ) {
-        retval = pvs.minOS.value();
-    }
-    
+    __block uint32_t retval       = 0;
+    __block bool     versionFound = false;
+    forEachImageVersion(config.process.mainExecutable, ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) {
+        if ( versionFound )
+            return;
+
+        if ( MachOFile::basePlatform((dyld3::Platform)platform) == dyld3::Platform::watchOS ) {
+            versionFound = true;
+            retval       = min_version;
+        }
+    });
     if ( config.log.apis )
         log("dyld_get_program_min_watch_os_version() => 0x%08X\n", retval);
     return retval;
 }
 
-void APIs::obsolete_dyld_get_program_sdk_bridge_os_version()
-{
-#if BUILDING_DYLD
-    halt("obsolete dyld SPI called");
-#else
-    abort();
-#endif
-}
-
-void APIs::obsolete_dyld_get_program_min_bridge_os_version()
-{
-#if BUILDING_DYLD
-    halt("obsolete dyld SPI called");
-#else
-    abort();
-#endif
-}
-
-// For watchOS and bridgeOS this returns the equivalent iOS SDK version.
-static uint32_t mapLegacyVersion(mach_o::Version32 version, Platform basePlatform)
-{
-    if ( basePlatform == Platform::bridgeOS ) {
-        return version.value() + 0x00090000;
-    }
-    else if ( basePlatform == Platform::watchOS ) {
-        if ( version.major() < 26 )
-            return version.value() + 0x00070000;
-    }
-    return version.value();
+uint32_t APIs::dyld_get_program_sdk_bridge_os_version()
+{
+    __block uint32_t retval       = 0;
+    __block bool     versionFound = false;
+    forEachImageVersion(config.process.mainExecutable, ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) {
+        if ( versionFound )
+            return;
+
+        if ( MachOFile::basePlatform((dyld3::Platform)platform) == dyld3::Platform::bridgeOS ) {
+            versionFound = true;
+            retval       = sdk_version;
+        }
+    });
+    if ( config.log.apis )
+        log("dyld_get_program_sdk_bridge_os_version() => 0x%08X\n", retval);
+    return retval;
+}
+
+uint32_t APIs::dyld_get_program_min_bridge_os_version()
+{
+    __block uint32_t retval       = 0;
+    __block bool     versionFound = false;
+    forEachImageVersion(config.process.mainExecutable, ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) {
+        if ( versionFound )
+            return;
+
+        if ( MachOFile::basePlatform((dyld3::Platform)platform) == dyld3::Platform::bridgeOS ) {
+            versionFound = true;
+            retval       = min_version;
+        }
+    });
+    if ( config.log.apis )
+        log("dyld_get_program_min_bridge_os_version() => 0x%08X\n", retval);
+    return retval;
 }
 
 //
@@ -428,16 +426,34 @@
 // Otherwise, looks for the libSystem.B.dylib the binary linked
 // against and uses a table to convert that to an sdk version.
 //
-// For watchOS and bridgeOS this returns the equivalent iOS SDK version.
-//
 uint32_t APIs::getSdkVersion(const mach_header* mh)
 {
-    uint32_t            retval  = 0;
-    PlatformAndVersions pvs     = getImagePlatformAndVersions((const Header*)mh);
-    
-    if ( pvs.platform == config.process.platform ) {
-        retval = mapLegacyVersion(pvs.sdk, pvs.platform.basePlatform());
-    }
+    __block bool     versionFound = false;
+    __block uint32_t retval       = 0;
+    forEachImageVersion(mh, ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) {
+        if ( versionFound )
+            return;
+
+        if ( platform == (dyld_platform_t)config.process.platform ) {
+            versionFound = true;
+            switch ( MachOFile::basePlatform((dyld3::Platform)platform)  ) {
+                case dyld3::Platform::bridgeOS:
+                    retval = sdk_version + 0x00090000;
+                    return;
+                case dyld3::Platform::watchOS:
+                    retval = sdk_version + 0x00070000;
+                    return;
+                default:
+                    retval = sdk_version;
+                    return;
+            }
+        }
+        else if ( platform == PLATFORM_IOSSIMULATOR && (dyld_platform_t)config.process.platform == PLATFORM_IOSMAC ) {
+            //FIXME bringup hack
+            versionFound = true;
+            retval       = 0x000C0000;
+        }
+    });
 
     return retval;
 }
@@ -452,7 +468,7 @@
 
 uint32_t APIs::dyld_get_program_sdk_version()
 {
-    uint32_t result = getSdkVersion(config.process.mainExecutableMF);
+    uint32_t result = getSdkVersion(config.process.mainExecutable);
     if ( config.log.apis )
         log("dyld_get_program_sdk_version() => 0x%08X\n", result);
     return result;
@@ -460,13 +476,32 @@
 
 uint32_t APIs::dyld_get_min_os_version(const mach_header* mh)
 {
-    uint32_t            retval  = 0;
-    PlatformAndVersions pvs     = getImagePlatformAndVersions((const Header*)mh);
-    
-    if ( pvs.platform == config.process.platform ) {
-        retval = mapLegacyVersion(pvs.minOS, pvs.platform.basePlatform());
-    }
-    
+    __block bool     versionFound = false;
+    __block uint32_t retval       = 0;
+    forEachImageVersion(mh, ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) {
+        if ( versionFound )
+            return;
+
+        if ( platform == (dyld_platform_t)config.process.platform ) {
+            versionFound = true;
+            switch ( MachOFile::basePlatform((dyld3::Platform)platform) ) {
+                case dyld3::Platform::bridgeOS:
+                    retval = min_version + 0x00090000;
+                    return;
+                case dyld3::Platform::watchOS:
+                    retval = min_version + 0x00070000;
+                    return;
+                default:
+                    retval = min_version;
+                    return;
+            }
+        }
+        else if ( platform == PLATFORM_IOSSIMULATOR && (dyld_platform_t)config.process.platform == PLATFORM_IOSMAC ) {
+            //FIXME bringup hack
+            versionFound = true;
+            retval       = 0x000C0000;
+        }
+    });
     if ( config.log.apis )
         log("dyld_get_min_os_version(%p) => 0x%08X\n", mh, retval);
     return retval;
@@ -474,7 +509,7 @@
 
 dyld_platform_t APIs::dyld_get_active_platform(void)
 {
-    dyld_platform_t result = config.process.platform.value();
+    dyld_platform_t result = (dyld_platform_t)config.process.platform;
     if ( config.log.apis )
         log("dyld_get_active_platform() => %d\n", result);
     return result;
@@ -482,7 +517,7 @@
 
 dyld_platform_t APIs::dyld_get_base_platform(dyld_platform_t platform)
 {
-    dyld_platform_t result = Platform(platform).basePlatform().value();
+    dyld_platform_t result = (dyld_platform_t)MachOFile::basePlatform((dyld3::Platform)platform);
     if ( config.log.apis )
         log("dyld_get_base_platform(%d) => %d\n", platform, result);
     return result;
@@ -490,13 +525,13 @@
 
 bool APIs::dyld_is_simulator_platform(dyld_platform_t platform)
 {
-    bool result = Platform(platform).isSimulator();
+    bool result = MachOFile::isSimulatorPlatform((dyld3::Platform)platform);
     if ( config.log.apis )
         log("dyld_is_simulator_platform(%d) => %d\n", platform, result);
     return result;
 }
 
-dyld_build_version_t APIs::mapFromVersionSet(dyld_build_version_t versionSet, Platform platform)
+dyld_build_version_t APIs::mapFromVersionSet(dyld_build_version_t versionSet)
 {
 #if TARGET_OS_EXCLAVEKIT
     return { 0, 0 }; //FIXME
@@ -513,35 +548,35 @@
     if ( foundEntry == nullptr ) {
         return { .platform = 0, .version = 0 };
     }
-    platform = platform.basePlatform();
-    if ( platform == Platform::macOS )
-        return { .platform = PLATFORM_MACOS,    .version = foundEntry->macos };
-    if ( platform == Platform::iOS )
-        return { .platform = PLATFORM_IOS,      .version = foundEntry->ios };
-    if ( platform == Platform::watchOS )
-        return { .platform = PLATFORM_WATCHOS,  .version = foundEntry->watchos };
-    if ( platform == Platform::tvOS )
-        return { .platform = PLATFORM_TVOS,     .version = foundEntry->tvos };
-    if ( platform == Platform::bridgeOS )
-        return { .platform = PLATFORM_BRIDGEOS, .version = foundEntry->bridgeos };
-    if ( platform == Platform::visionOS )
-        return { .platform = PLATFORM_VISIONOS, .version = foundEntry->visionos };
-    
-    return { .platform = (dyld_platform_t)platform.value(), .version = 0 };
+    switch ( MachOFile::basePlatform(config.process.platform) ) {
+        case dyld3::Platform::macOS:
+            return { .platform = PLATFORM_MACOS,    .version = foundEntry->macos };
+        case dyld3::Platform::iOS:
+            return { .platform = PLATFORM_IOS,      .version = foundEntry->ios };
+        case dyld3::Platform::watchOS:
+            return { .platform = PLATFORM_WATCHOS,  .version = foundEntry->watchos };
+        case dyld3::Platform::tvOS:
+            return { .platform = PLATFORM_TVOS,     .version = foundEntry->tvos };
+        case dyld3::Platform::bridgeOS:
+            return { .platform = PLATFORM_BRIDGEOS, .version = foundEntry->bridgeos };
+        default:
+            return { .platform = (dyld_platform_t)MachOFile::basePlatform(config.process.platform), .version = 0 };
+    }
 #endif
 }
 
 bool APIs::dyld_sdk_at_least(const mach_header* mh, dyld_build_version_t atLeast)
 {
-    dyld_build_version_t    concreteAtLeast = mapFromVersionSet(atLeast, config.process.platform);
-    bool                    retval          = false;
-    PlatformAndVersions     pvs             = getImagePlatformAndVersions((const Header*)mh);
-    
-    if ( pvs.platform.basePlatform() == Platform(concreteAtLeast.platform).basePlatform() ) {
-        if ( !pvs.platform.basePlatform().empty() && pvs.sdk.value() >= concreteAtLeast.version )
-            retval = true;
-    }
-    
+    dyld_build_version_t concreteAtLeast = mapFromVersionSet(atLeast);
+    __block bool retval = false;
+    forEachImageVersion(mh, ^(dyld_platform_t imagePlatform, uint32_t imageSDK, uint32_t imageOS) {
+        if ( MachOFile::basePlatform((dyld3::Platform)imagePlatform) == MachOFile::basePlatform((dyld3::Platform)concreteAtLeast.platform) ) {
+            if ( MachOFile::basePlatform((dyld3::Platform)imagePlatform) == dyld3::Platform::unknown )
+                return;
+            if ( imageSDK >= concreteAtLeast.version )
+                retval = true;
+        }
+    });
     if ( config.log.apis )
         log("dyld_sdk_at_least(%p, <%d,0x%08X>) => %d\n", mh, atLeast.platform, atLeast.version, retval);
     return retval;
@@ -549,15 +584,16 @@
 
 bool APIs::dyld_minos_at_least(const mach_header* mh, dyld_build_version_t atLeast)
 {
-    dyld_build_version_t    concreteAtLeast = mapFromVersionSet(atLeast, config.process.platform);
-    bool                    retval          = false;
-    PlatformAndVersions     pvs             = getImagePlatformAndVersions((const Header*)mh);
-    
-    if ( pvs.platform.basePlatform() == Platform(concreteAtLeast.platform).basePlatform() ) {
-        if ( !pvs.platform.basePlatform().empty() && pvs.minOS.value() >= concreteAtLeast.version )
-            retval = true;
-    }
-    
+    dyld_build_version_t concreteAtLeast = mapFromVersionSet(atLeast);
+    __block bool         retval          = false;
+    forEachImageVersion(mh, ^(dyld_platform_t imagePlatform, uint32_t imageSDK, uint32_t imageMinOS) {
+        if ( MachOFile::basePlatform((dyld3::Platform)imagePlatform) == MachOFile::basePlatform((dyld3::Platform)concreteAtLeast.platform) ) {
+            if ( MachOFile::basePlatform((dyld3::Platform)imagePlatform) == dyld3::Platform::unknown )
+                return;
+            if ( imageMinOS >= concreteAtLeast.version )
+                retval = true;
+        }
+    });
     if ( config.log.apis )
         log("dyld_minos_at_least(%p, <%d,0x%08X>) => %d\n", mh, atLeast.platform, atLeast.version, retval);
     return retval;
@@ -571,14 +607,14 @@
 
     uint32_t currentVersion = 0;
     bool defaultResult = true;
-    if ( config.process.basePlatform.empty() ) {
+    if ( config.process.basePlatform == dyld3::Platform::unknown ) {
         defaultResult = false;
     }
     if (version.platform == 0xffffffff) {
         currentVersion = config.process.mainExecutableMinOSVersionSet;
-    } else if (version.platform == config.process.basePlatform) {
+    } else if (version.platform == (dyld_platform_t)config.process.basePlatform) {
         currentVersion = config.process.mainExecutableMinOSVersion;
-    } else if (version.platform == config.process.platform) {
+    } else if (version.platform == (dyld_platform_t)config.process.platform) {
         currentVersion = config.process.mainExecutableMinOSVersion;
     } else {
         // Hack
@@ -598,14 +634,14 @@
 
     uint32_t currentVersion = 0;
     bool defaultResult = true;
-    if ( config.process.basePlatform.empty() ) {
+    if ( config.process.basePlatform == dyld3::Platform::unknown ) {
         defaultResult = false;
     }
     if (version.platform == 0xffffffff) {
         currentVersion = config.process.mainExecutableSDKVersionSet;
-    } else if (version.platform == config.process.basePlatform) {
+    } else if (version.platform == (dyld_platform_t)config.process.basePlatform) {
         currentVersion = config.process.mainExecutableSDKVersion;
-    } else if (version.platform == config.process.platform) {
+    } else if (version.platform == (dyld_platform_t)config.process.platform) {
         currentVersion = config.process.mainExecutableSDKVersion;
     } else {
         // Hack
@@ -617,148 +653,139 @@
     return ( currentVersion >= version.version ) ? defaultResult : false;
 }
 
-uint64_t APIs::dyld_get_program_sdk_version_token() {
-    uint64_t result = 0;
-    dyld_build_version_t* token = (dyld_build_version_t*)&result;
-    token->platform = config.process.platform.value();
-    token->version = config.process.mainExecutableSDKVersion;
-    return result;
-}
-
-uint64_t APIs::dyld_get_program_minos_version_token() {
-    uint64_t result = 0;
-    dyld_build_version_t* token = (dyld_build_version_t*)&result;
-    token->platform = config.process.platform.value();
-    token->version = config.process.mainExecutableMinOSVersion;
-    return result;
-}
-
-dyld_platform_t APIs::dyld_version_token_get_platform(uint64_t token) {
-    dyld_build_version_t* versionToken = (dyld_build_version_t*)&token;
-    return versionToken->platform;
-}
-
-
-bool APIs::dyld_version_token_at_least(uint64_t token, dyld_build_version_t version) {
-    dyld_build_version_t tokenVersion = *(dyld_build_version_t*)&token;
-    version = mapFromVersionSet(version, Platform(tokenVersion.platform));
-    if (tokenVersion.platform
-        && Platform(tokenVersion.platform).basePlatform() == version.platform
-        && tokenVersion.version >= version.version) {
-        return true;
-    }
-    return false;
-}
-
-Version32 APIs::linkedDylibVersion(const Header* header, const char* installname)
-{
-    __block Version32 retval(0);
-    header->forEachLinkedDylib(^(const char *loadPath, mach_o::LinkedDylibAttributes kind, Version32 compatVersion, Version32 currentVersion, 
-                                 bool synthesizedLink, bool& stop) {
+uint32_t APIs::linkedDylibVersion(const MachOFile* mf, const char* installname)
+{
+    __block uint32_t retval = 0;
+    mf->forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t compatVersion, uint32_t currentVersion, bool& stop) {
         if ( strcmp(loadPath, installname) == 0 ) {
             retval = currentVersion;
             stop   = true;
         }
     });
-    
     return retval;
 }
 
-Version32 APIs::deriveVersionFromDylibs(const Header* header)
+#define PACKED_VERSION(major, minor, tiny) ((((major)&0xffff) << 16) | (((minor)&0xff) << 8) | ((tiny)&0xff))
+
+uint32_t APIs::deriveVersionFromDylibs(const MachOFile* mf)
 {
     // This is a binary without a version load command, we need to infer things
     struct DylibToOSMapping
     {
-        Version32 dylibVersion;
-        Version32 osVersion;
+        uint32_t dylibVersion;
+        uint32_t osVersion;
     };
-    Version32 linkedVersion(0);
+    uint32_t linkedVersion = 0;
 #if TARGET_OS_OSX
-    linkedVersion                                  = linkedDylibVersion(header, "/usr/lib/libSystem.B.dylib");
-    static constinit const DylibToOSMapping versionMapping[] = {
-        { {   88, 1, 3 }, Version32(0x000A0400) },
-        { {  111, 0, 0 }, Version32(0x000A0500) },
-        { {  123, 0, 0 }, Version32(0x000A0600) },
-        { {  159, 0, 0 }, Version32(0x000A0700) },
-        { {  169, 3, 0 }, Version32(0x000A0800) },
-        { { 1197, 0, 0 }, Version32(0x000A0900) },
-        { {    0, 0, 0 }, Version32(0x000A0900) }
+    linkedVersion                                  = linkedDylibVersion(mf, "/usr/lib/libSystem.B.dylib");
+    static const DylibToOSMapping versionMapping[] = {
+        { PACKED_VERSION(88, 1, 3), 0x000A0400 },
+        { PACKED_VERSION(111, 0, 0), 0x000A0500 },
+        { PACKED_VERSION(123, 0, 0), 0x000A0600 },
+        { PACKED_VERSION(159, 0, 0), 0x000A0700 },
+        { PACKED_VERSION(169, 3, 0), 0x000A0800 },
+        { PACKED_VERSION(1197, 0, 0), 0x000A0900 },
+        { PACKED_VERSION(0, 0, 0), 0x000A0900 }
         // We don't need to expand this table because all recent
         // binaries have LC_VERSION_MIN_ load command.
     };
+#elif TARGET_OS_IOS
+    linkedVersion                                  = linkedDylibVersion(mf, "/System/Library/Frameworks/Foundation.framework/Foundation");
+    static const DylibToOSMapping versionMapping[] = {
+        { PACKED_VERSION(678, 24, 0), 0x00020000 },
+        { PACKED_VERSION(678, 26, 0), 0x00020100 },
+        { PACKED_VERSION(678, 29, 0), 0x00020200 },
+        { PACKED_VERSION(678, 47, 0), 0x00030000 },
+        { PACKED_VERSION(678, 51, 0), 0x00030100 },
+        { PACKED_VERSION(678, 60, 0), 0x00030200 },
+        { PACKED_VERSION(751, 32, 0), 0x00040000 },
+        { PACKED_VERSION(751, 37, 0), 0x00040100 },
+        { PACKED_VERSION(751, 49, 0), 0x00040200 },
+        { PACKED_VERSION(751, 58, 0), 0x00040300 },
+        { PACKED_VERSION(881, 0, 0), 0x00050000 },
+        { PACKED_VERSION(890, 1, 0), 0x00050100 },
+        { PACKED_VERSION(992, 0, 0), 0x00060000 },
+        { PACKED_VERSION(993, 0, 0), 0x00060100 },
+        { PACKED_VERSION(1038, 14, 0), 0x00070000 },
+        { PACKED_VERSION(0, 0, 0), 0x00070000 }
+        // We don't need to expand this table because all recent
+        // binaries have LC_VERSION_MIN_ load command.
+    };
 #else
     static const DylibToOSMapping versionMapping[] = {};
 #endif
-    if ( linkedVersion.value() != 0 ) {
-        Version32 lastOsVersion(0);
-        for ( const DylibToOSMapping& map : versionMapping ) {
-            if ( map.dylibVersion.value() == 0 ) {
-                return map.osVersion;
-            }
-            if ( linkedVersion < map.dylibVersion ) {
+    if ( linkedVersion != 0 ) {
+        uint32_t lastOsVersion = 0;
+        for ( const DylibToOSMapping* p = versionMapping;; ++p ) {
+            if ( p->dylibVersion == 0 ) {
+                return p->osVersion;
+            }
+            if ( linkedVersion < p->dylibVersion ) {
                 return lastOsVersion;
             }
-            lastOsVersion = map.osVersion;
-        }
-    }
-    return Version32(0);
+            lastOsVersion = p->osVersion;
+        }
+    }
+    return 0;
 }
 
 // assumes mh has already been validated
-PlatformAndVersions APIs::getPlatformAndVersions(const Header* header)
-{
-    PlatformAndVersions pvs = header->platformAndVersions();
-    if ( !pvs.platform.empty() ) {
-        // The origin LC_VERSION_MIN_MACOSX did not have an "sdk" field.  It was reserved and set to zero.
-        // If the sdk field is zero, we assume it is an old binary and try to backsolve for its SDK.
-        if ( pvs.sdk.value() == 0 ) {
-            pvs.sdk = deriveVersionFromDylibs(header);
-        }
-        return pvs;
-    }
+void APIs::forEachPlatform(const MachOFile* mf, void (^callback)(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version))
+{
+    __block bool lcFound = false;
+    mf->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
+        lcFound = true;
+        // If SDK field is empty then derive the value from library linkages
+        if ( sdk == 0 ) {
+            sdk = deriveVersionFromDylibs(mf);
+        }
+        callback((const dyld_platform_t)platform, sdk, minOS);
+    });
 
     // No load command was found, so again, fallback to deriving it from library linkages
-    Platform platform = Platform::current();
-    Version32 derivedVersion = deriveVersionFromDylibs(header);
-    if ( derivedVersion.value() != 0 ) {
-        return PlatformAndVersions(platform, derivedVersion, Version32(0));
-    }
-    
-    return PlatformAndVersions(Platform(), Version32(0), Version32(0));
+    if ( !lcFound ) {
+#if TARGET_OS_IOS
+    #if __x86_64__ || __x86__
+        dyld_platform_t platform = PLATFORM_IOSSIMULATOR;
+    #else
+        dyld_platform_t platform = PLATFORM_IOS;
+    #endif
+#elif TARGET_OS_OSX
+        dyld_platform_t platform = PLATFORM_MACOS;
+#else
+        dyld_platform_t platform = 0;
+#endif
+        uint32_t derivedVersion = deriveVersionFromDylibs(mf);
+        if ( platform != 0 && derivedVersion != 0 ) {
+            callback(platform, derivedVersion, 0);
+        }
+    }
 }
 
 void APIs::dyld_get_image_versions(const mach_header* mh, void (^callback)(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version))
 {
     if ( config.log.apis )
         log("dyld_get_image_versions(%p, %p)\n", mh, callback);
-    PlatformAndVersions pvs = getImagePlatformAndVersions((const Header*)mh);
-    if ( !pvs.platform.empty() ) {
-        pvs.unzip(^(PlatformAndVersions pvs2) {
-            callback(pvs2.platform.value(), pvs2.sdk.value(), pvs2.minOS.value());
-        });
-    }
-}
-
-// getImagePlatformAndVersions will always return one PVS.
-PlatformAndVersions APIs::getImagePlatformAndVersions(const Header* hdr)
+    forEachImageVersion(mh, callback);
+}
+
+void APIs::forEachImageVersion(const mach_header* mh, void (^callback)(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version))
 {
 #if !TARGET_OS_EXCLAVEKIT
-    if ( hdr == config.process.mainExecutableHdr ) {
+    Diagnostics      diag;
+    const MachOFile* mf = (MachOFile*)mh;
+
+    if ( mh == config.process.mainExecutable ) {
         // Special case main executable, that info is store in ProcessConfig
-        return PlatformAndVersions(config.process.platform, Version32(config.process.mainExecutableMinOSVersion), Version32(config.process.mainExecutableSDKVersion));
-    }
-    else if ( DyldSharedCache::inDyldCache(config.dyldCache.addr, hdr) ) {
+        callback((dyld_platform_t)config.process.platform, config.process.mainExecutableSDKVersion, config.process.mainExecutableMinOSVersion);
+    }
+    else if ( DyldSharedCache::inDyldCache(config.dyldCache.addr, mf) ) {
         // If the image is in the shared cache, then all versions OS and SDK versions are the same
-        return PlatformAndVersions(config.dyldCache.platform, Version32(config.dyldCache.osVersion), Version32(config.dyldCache.osVersion));
-    }
-    else if ( hdr->hasMachOMagic() ) {
-        // validiate load commands are well formed
-        if ( mach_o::Error err = hdr->validStructureLoadCommands(0x10000) )
-            return PlatformAndVersions(Platform(0), Version32(0), Version32(0));
+       callback((dyld_platform_t)config.dyldCache.platform, config.dyldCache.osVersion, config.dyldCache.osVersion);
+    }
+    else if ( mf->isMachO(diag, mh->sizeofcmds + sizeof(mach_header_64)) ) {
         // look for LC_BUILD_VERSION or derive from dylib info
-        return this->getPlatformAndVersions(hdr);
-    } else {
-        return PlatformAndVersions(Platform(0), Version32(0), Version32(0));
+        this->forEachPlatform(mf, callback);
     }
 #else
     abort();
@@ -767,15 +794,15 @@
 
 uint32_t APIs::dyld_get_program_min_os_version()
 {
-    return dyld_get_min_os_version(config.process.mainExecutableMF);
+    return dyld_get_min_os_version(config.process.mainExecutable);
 }
 
 bool APIs::_dyld_get_image_uuid(const mach_header* mh, uuid_t uuid)
 {
     if ( config.log.apis )
         log("_dyld_get_image_uuid(%p, %p)\n", mh, uuid);
-    const Header* header = (Header*)mh;
-    return (header->hasMachOMagic() && header->getUuid(uuid));
+    const MachOFile* mf = (MachOFile*)mh;
+    return (mf->hasMachOMagic() && mf->getUuid(uuid));
 }
 
 int APIs::_NSGetExecutablePath(char* buf, uint32_t* bufsize)
@@ -783,7 +810,7 @@
     if ( config.log.apis )
         log("_NSGetExecutablePath(%p, %p)\n", buf, bufsize);
     const char* path     = config.process.mainExecutablePath;
-    if ( config.process.platform == Platform::macOS )
+    if ( config.process.platform == dyld3::Platform::macOS )
         path = config.process.mainUnrealPath; // Note: this is not real-path. It may be a symlink rdar://74451681
     size_t      pathSize = strlen(path) + 1;
     if ( *bufsize >= pathSize ) {
@@ -794,10 +821,10 @@
     return -1;
 }
 
-void APIs::_dyld_register_func_for_add_image(NotifyFunc func)
-{
-    if ( config.log.apis )
-        log("_dyld_register_func_for_add_image(%p)\n", func.raw());
+void APIs::_dyld_register_func_for_add_image(void (*func)(const mach_header* mh, intptr_t slide))
+{
+    if ( config.log.apis )
+        log("_dyld_register_func_for_add_image(%p)\n", func);
 #if !TARGET_OS_EXCLAVEKIT
     // callback about already loaded images
     locks.withLoadersReadLock(^{
@@ -812,14 +839,14 @@
         }
         for ( unsigned i = 0; i < count; ++i ) {
             if ( config.log.notifications )
-                log("add notifier %p called with mh=%p\n", func.raw(), mhs[i]);
+                log("add notifier %p called with mh=%p\n", func, mhs[i]);
             func(mhs[i], slides[i]);
         }
     });
 
     // add to list of functions to call about future loads
-    const Loader* callbackLoader = this->findImageContaining(func.raw());
-    locks.withNotifiersWriteLock(^{
+    const Loader* callbackLoader = this->findImageContaining((void*)func);
+    locks.withNotifiersWriteLock(memoryManager, ^() {
         addNotifyAddFunc(callbackLoader, func);
     });
 #else
@@ -827,14 +854,14 @@
 #endif // !TARGET_OS_EXCLAVEKIT
 }
 
-void APIs::_dyld_register_func_for_remove_image(NotifyFunc func)
-{
-    if ( config.log.apis )
-        log("_dyld_register_func_for_remove_image(%p)\n", func.raw());
+void APIs::_dyld_register_func_for_remove_image(void (*func)(const mach_header* mh, intptr_t slide))
+{
+    if ( config.log.apis )
+        log("_dyld_register_func_for_remove_image(%p)\n", func);
 #if !TARGET_OS_EXCLAVEKIT
     // add to list of functions to call about future unloads
-    const Loader* callbackLoader = this->findImageContaining(func.raw());
-    locks.withNotifiersWriteLock(^{
+    const Loader* callbackLoader = this->findImageContaining((void*)func);
+    locks.withNotifiersWriteLock(memoryManager, ^() {
         addNotifyRemoveFunc(callbackLoader, func);
     });
 #else
@@ -843,16 +870,14 @@
 }
 
 // FIXME: Remove this once libobjc moves to _dyld_objc_register_callbacks()
-void APIs::_dyld_objc_notify_register(ReadOnlyCallback<_dyld_objc_notify_mapped>,
-                                      ReadOnlyCallback<_dyld_objc_notify_init>,
-                                      ReadOnlyCallback<_dyld_objc_notify_unmapped>)
-{
-#if BUILDING_DYLD
+void APIs::_dyld_objc_notify_register(_dyld_objc_notify_mapped   mapped,
+                                      _dyld_objc_notify_init     init,
+                                      _dyld_objc_notify_unmapped unmapped)
+{
     halt("_dyld_objc_notify_register is unsupported");
-#endif
-}
-
-void APIs::_dyld_objc_register_callbacks(const ObjCCallbacks* callbacks)
+}
+
+void APIs::_dyld_objc_register_callbacks(const _dyld_objc_callbacks* callbacks)
 {
     if ( config.log.apis ) {
         void** p = (void**)callbacks;
@@ -860,28 +885,18 @@
     }
 
     if ( callbacks->version == 1 ) {
-#if BUILDING_DYLD
         halt("_dyld_objc_register_callbacks v1 is no longer supported");
-#endif
     }
     else if ( callbacks->version == 2 ) {
-#if BUILDING_DYLD
-        halt("_dyld_objc_register_callbacks v2 is no longer supported");
-#endif
+        const _dyld_objc_callbacks_v2* v2 = (const _dyld_objc_callbacks_v2*)callbacks;
+        setObjCNotifiers(v2->unmapped, v2->patches, v2->mapped, v2->init, nullptr);
     }
     else if ( callbacks->version == 3 ) {
-#if BUILDING_DYLD
-        halt("_dyld_objc_register_callbacks v3 is no longer supported");
-#endif
-    }
-    else if ( callbacks->version == 4 ) {
-        const ObjCCallbacksV4* v4 = (const ObjCCallbacksV4*)callbacks;
-        setObjCNotifiers(v4->unmapped, v4->patches, v4->init, v4->mapped);
+        const _dyld_objc_callbacks_v3* v3 = (const _dyld_objc_callbacks_v3*)callbacks;
+        setObjCNotifiers(v3->unmapped, v3->patches, nullptr, v3->init, v3->mapped);
     }
     else {
-#if BUILDING_DYLD
         halt("_dyld_objc_register_callbacks unknown version");
-#endif
     }
 
 #if SUPPORT_PREBUILTLOADERS
@@ -897,6 +912,7 @@
     __block bool result = false;
 
     bool inSharedCache = false;
+#if !TARGET_OS_EXCLAVEKIT
     // if address is in cache, do fast search of TEXT segments in cache
     const DyldSharedCache* dyldCache = config.dyldCache.addr;
     if ( (dyldCache != nullptr) && (addr > dyldCache) ) {
@@ -955,7 +971,7 @@
         if ( neverUnloads != nullptr )
             *neverUnloads = true;
         if ( path != nullptr )
-            *path = ldr->path(*this);
+            *path = ldr->path();
         if ( (segAddr != nullptr) || (segSize != nullptr) ) {
             // only needed by _dyld_images_for_addresses()
             const void* ldrSegAddr;
@@ -974,6 +990,7 @@
           *loader = ldr;
         return true;
     }
+#endif // !TARGET_OS_EXCLAVEKIT
 
     // slow path - search image list
     locks.withLoadersReadLock(^{
@@ -990,7 +1007,7 @@
                 if ( neverUnloads != nullptr )
                     *neverUnloads = image->neverUnload;
                 if ( path != nullptr )
-                    *path = image->path(*this);
+                    *path = image->path();
                 if ( segAddr != nullptr )
                     *segAddr = sgAddr;
                 if ( segSize != nullptr )
@@ -1046,21 +1063,19 @@
 
     // if address is in cache, only TEXT is immutable
     __block bool result = false;
+#if !TARGET_OS_EXCLAVEKIT
     const DyldSharedCache* dyldCache = config.dyldCache.addr;
     if ( (dyldCache != nullptr) && (addr > dyldCache) ) {
         if ( addr < (void*)((uint8_t*)dyldCache + dyldCache->mappedSize()) ) {
             dyldCache->forEachCache(^(const DyldSharedCache *cache, bool& stopCache) {
                 cache->forEachRegion(^(const void* content, uint64_t vmAddr, uint64_t size,
-                                       uint32_t initProt, uint32_t maxProt, uint64_t flags,
-                                       uint64_t fileOffset, bool& stopRegion) {
+                                            uint32_t initProt, uint32_t maxProt, uint64_t flags, bool& stopRegion) {
                     if ( (addr > content) && (((uint8_t*)addr + length) < ((uint8_t*)content + size)) ) {
                         // Note: in cache __DATA_CONST has initProt=1 and initProt=3
                         // we don't want __DATA_CONST to be considered immutable, so we check maxProt
                         bool writable = (maxProt & VM_PROT_WRITE);
                         if ( !writable )
                             result = true;
-                        stopRegion = true;
-                        stopCache  = true;
                     }
                 });
             });
@@ -1076,6 +1091,7 @@
             result = !writable;
         }
     }
+#endif // !TARGET_OS_EXCLAVEKIT
     if ( config.log.apis )
         log("_dyld_is_memory_immutable(%p, %lu) => %d\n", addr, length, result);
     return result;
@@ -1141,9 +1157,8 @@
         if ( (dyldStart <= targetAddr) && (targetAddr < dyldStart + 0x200000) ) {
             uint64_t     slide  = (uintptr_t)&__dso_handle; // dyld is always zero based
             __block bool inDyld = false;
-            const Header* mh = (const Header*)&__dso_handle;
-            mh->forEachSegment(^(const Header::SegmentInfo& segInfo, bool& stop) {
-                if ( ((segInfo.vmaddr + slide) <= targetAddr) && (targetAddr < (segInfo.vmaddr + slide + segInfo.vmsize)) ) {
+            __dso_handle.forEachSegment(^(const MachOAnalyzer::SegmentInfo& segInfo, bool& stop) {
+                if ( ((segInfo.vmAddr + slide) <= targetAddr) && (targetAddr < (segInfo.vmAddr + slide + segInfo.vmSize)) ) {
                     inDyld = true;
                     stop   = true;
                 }
@@ -1159,10 +1174,14 @@
                         info->dli_sname = nullptr;
                         info->dli_saddr = nullptr;
                     }
-                    else if ( info->dli_sname != nullptr ) {
-                        // strip off leading underscore
-                        if ( info->dli_sname[0] == '_')
-                            info->dli_sname = info->dli_sname + 1;
+                    // strip off leading underscore
+                    else if ( (info->dli_sname != nullptr) && (info->dli_sname[0] == '_') ) {
+                        info->dli_sname = info->dli_sname + 1;
+                    }
+                    if ( strcmp(info->dli_sname, "_ZN5dyld45startEPKNS_10KernelArgsE") == 0 ) {
+                        // start (which calls main()) is now in dyld, so be nice and allow dladdr() to return that
+                        info->dli_sname = "start";
+                        info->dli_saddr = (void*)addr;
                     }
                 }
             }
@@ -1176,30 +1195,24 @@
 
 struct PerThreadErrorMessage
 {
+    size_t sizeAllocated;
     bool   valid;
     char   message[1];
 };
 
-
-#if !TARGET_OS_DRIVERKIT
 void APIs::clearErrorString()
 {
+    if ( (dlerrorPthreadKey() == -1) || !libSystemInitialized() )
+        return;
+    PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)this->libSystemHelpers->pthread_getspecific(dlerrorPthreadKey());
+    if ( errorBuffer != nullptr )
+        errorBuffer->valid = false;
+}
+
+void APIs::setErrorString(const char* format, ...)
+{
     // if dlopen/dlsym called before libSystem initialized, dlerrorPthreadKey() won't be set, and malloc won't be available
-    if ( (dlerrorPthreadKey() == -1) || !libdyldInitialized() )
-        return;
-
-    if ( PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)this->libSystemHelpers.pthread_getspecific(dlerrorPthreadKey()) ) {
-        if ( this->libSystemHelpers.malloc_size(errorBuffer) == 0 )
-            this->libSystemHelpers.pthread_setspecific(dlerrorPthreadKey(), nullptr);
-        else
-            errorBuffer->valid = false;
-    }
-}
-
-void APIs::setErrorString(const char* format, ...)
-{
-    // if dlopen/dlsym called before libSystem initialized, dlerrorPthreadKey() won't be set, and malloc won't be available
-    if ( (dlerrorPthreadKey() == -1) || !libdyldInitialized() )
+    if ( (dlerrorPthreadKey() == -1) || !libSystemInitialized() )
         return;
 
 #if !TARGET_OS_EXCLAVEKIT
@@ -1221,34 +1234,29 @@
     size_t                 strLen      = strlen(buf) + 1;
 #endif // !TARGET_OS_EXCLAVEKIT
 
-    size_t                  sizeNeeded  = std::max(offsetof(PerThreadErrorMessage, message[strLen]), (size_t)256);
-    PerThreadErrorMessage*  errorBuffer = (PerThreadErrorMessage*)this->libSystemHelpers.pthread_getspecific(dlerrorPthreadKey());
-    bool                    doAllocate  = false;
+    size_t                 sizeNeeded  = sizeof(PerThreadErrorMessage) + strLen;
+    PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)this->libSystemHelpers->pthread_getspecific(dlerrorPthreadKey());
+    if ( errorBuffer != nullptr ) {
+        if ( errorBuffer->sizeAllocated < sizeNeeded ) {
+            this->libSystemHelpers->free(errorBuffer);
+            errorBuffer = nullptr;
+        }
+    }
     if ( errorBuffer == nullptr ) {
-        doAllocate = true;
-    }
-    else {
-        // check buffer is owned by malloc
-        size_t size = this->libSystemHelpers.malloc_size(errorBuffer);
-        if ( size == 0 ) {
-            doAllocate = true;
-        }
-        else if ( size < sizeNeeded ) {
-            // existing buffer too small, free and allocate new buffer
-            this->libSystemHelpers.free(errorBuffer);
-            doAllocate = true;
-        }
-    }
-    if ( doAllocate ) {
-        errorBuffer = (PerThreadErrorMessage*)this->libSystemHelpers.malloc(sizeNeeded);
-        this->libSystemHelpers.pthread_setspecific(dlerrorPthreadKey(), errorBuffer);
+        size_t allocSize                 = std::max(sizeNeeded, (size_t)256);
+        // dlerrorPthreadKey is set up to call libSystem's free() on thread destruction, so this has to use libSystem's malloc()
+        PerThreadErrorMessage* p         = (PerThreadErrorMessage*)this->libSystemHelpers->malloc(allocSize);
+        p->sizeAllocated                 = allocSize;
+        p->valid                         = false;
+        this->libSystemHelpers->pthread_setspecific(dlerrorPthreadKey(), p);
+        errorBuffer = p;
     }
 #if !TARGET_OS_EXCLAVEKIT
-    strlcpy(errorBuffer->message, _simple_string(buf), sizeNeeded-1);
+    strcpy(errorBuffer->message, _simple_string(buf));
     errorBuffer->valid = true;
     _simple_sfree(buf);
 #else
-    strlcpy(errorBuffer->message, buf, sizeNeeded-1);
+    strlcpy(errorBuffer->message, buf, errorBuffer->sizeAllocated);
     errorBuffer->valid = true;
 #endif // !TARGET_OS_EXCLAVEKIT
 }
@@ -1258,19 +1266,16 @@
     if ( config.log.apis )
         log("dlerror()");
 
-    if ( (dlerrorPthreadKey() == -1) || !libdyldInitialized() )
+    if ( (dlerrorPthreadKey() == -1) || !libSystemInitialized() )
         return nullptr; // if dlopen/dlsym called before libSystem initialized, dlerrorPthreadKey() won't be set
-
-    PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)this->libSystemHelpers.pthread_getspecific(dlerrorPthreadKey());
+    PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)this->libSystemHelpers->pthread_getspecific(dlerrorPthreadKey());
     if ( errorBuffer != nullptr ) {
-        if ( this->libSystemHelpers.malloc_size(errorBuffer) != 0 ) {
-            if ( errorBuffer->valid ) {
-                // you can only call dlerror() once, then the message is cleared
-                errorBuffer->valid = false;
-                if ( config.log.apis )
-                    log(" => '%s'\n", errorBuffer->message);
-                return errorBuffer->message;
-            }
+        if ( errorBuffer->valid ) {
+            // you can only call dlerror() once, then the message is cleared
+            errorBuffer->valid = false;
+            if ( config.log.apis )
+                log(" => '%s'\n", errorBuffer->message);
+            return errorBuffer->message;
         }
     }
     if ( config.log.apis )
@@ -1278,9 +1283,8 @@
 
     return nullptr;
 }
-#endif // !TARGET_OS_DRIVERKIT
-
-const Loader* APIs::findImageContaining(const void* addr)
+
+const Loader* APIs::findImageContaining(void* addr)
 {
     addr                         = (void*)stripPointer(addr);
     __block const Loader* result = nullptr;
@@ -1298,7 +1302,6 @@
     return result;
 }
 
-#if !TARGET_OS_DRIVERKIT
 void* APIs::dlopen(const char* path, int mode)
 {
     void* callerAddress = __builtin_return_address(0);
@@ -1311,14 +1314,13 @@
 void* APIs::dlopen_from(const char* path, int mode, void* addressInCaller)
 {
 #if SUPPPORT_PRE_LC_MAIN
-    if (!libdyldInitialized()) {
+    if (!libSystemInitialized()) {
         // Usually libSystem will already be initialized, but some legacy binaries can call dlopen() first. If they do then
         // we need to force initialization of libSystem at that time. The reason is any library will link to libSystem and
         // trigger its initializers anyway, but until libSystem is up unfair locks don't work. If we let that happen we will
         // skip taking the api lock on entry, but will try to unlock it on release triggering a lock assertion
         const_cast<Loader*>(this->libSystemLoader)->beginInitializers(*this);
         this->libSystemLoader->runInitializers(*this);
-        this->setLibSystemInitialized();
     }
 #endif
     dyld3::ScopedTimer timer(DBG_DYLD_TIMING_DLOPEN, path, mode, 0);
@@ -1367,341 +1369,242 @@
 
     void*           result    = nullptr;
     const Loader*   topLoader = nullptr;
-    MemoryManager::withWritableMemory([&] {
-        // Put these on the persistent allocator as we can't keep them on the regular stack
-        typedef Vector<const Loader*> LoaderVector;
-        typedef Vector<Loader::PseudoDylibSymbolToMaterialize> PseudoDylibSymbolsVector;
-        UniquePtr<LoaderVector> newlyNotDelayedResult;
-        UniquePtr<PseudoDylibSymbolsVector> pseudoDylibSymbolsToMaterializeResult;
-
-        locks.withLoadersWriteLockAndProtectedStack([&] {
-            // since we have the dyld lock, any appends to state.loaded will be from this dlopen
-            // so record the length now, and cut it back to that point if dlopen fails
-            const uint64_t startLoaderCount = loaded.size();
-            const uint64_t startPatchedObjCClassesCount = this->patchedObjCClasses.size();
-            const uint64_t startPatchedSingletonsCount = this->patchedSingletons.size();
-            Diagnostics     diag;
-
-            // try to load specified dylib
-            Loader::LoadChain   loadChainMain { nullptr, mainExecutableLoader };
-            Loader::LoadChain   loadChainCaller { &loadChainMain, caller };
-            Loader::LoadOptions options;
-            options.staticLinkage    = false;
-            options.launching        = false;
-            options.canBeMissing     = false;
-            options.rtldLocal        = (mode & RTLD_LOCAL);
-            options.rtldNoDelete     = (mode & RTLD_NODELETE);
-            options.rtldNoLoad       = (mode & RTLD_NOLOAD);
-            options.insertedDylib    = false;
-            options.canBeDylib       = true;
-            options.canBeBundle      = true;
-            // only allow dlopen() of main executables on macOS (eventually ban there too)
+    STACK_ALLOC_VECTOR(const Loader*, loadersToNotify, 32);
+    STACK_ALLOC_VECTOR(const Loader*, loadersUnDelayed, 32);
+    locks.withLoadersWriteLock(memoryManager, [&] {
+        // since we have the dyld lock, any appends to state.loaded will be from this dlopen
+        // so record the length now, and cut it back to that point if dlopen fails
+        const uint64_t startLoaderCount = loaded.size();
+        const uint64_t startPatchedObjCClassesCount = this->patchedObjCClasses.size();
+        const uint64_t startPatchedSingletonsCount = this->patchedSingletons.size();
+        Diagnostics     diag;
+
+        // try to load specified dylib
+        Loader::LoadChain   loadChainMain { nullptr, mainExecutableLoader };
+        Loader::LoadChain   loadChainCaller { &loadChainMain, caller };
+        Loader::LoadOptions options;
+        options.staticLinkage    = false;
+        options.launching        = false;
+        options.canBeMissing     = false;
+        options.rtldLocal        = (mode & RTLD_LOCAL);
+        options.rtldNoDelete     = (mode & RTLD_NODELETE);
+        options.rtldNoLoad       = (mode & RTLD_NOLOAD);
+        options.insertedDylib    = false;
+        options.canBeDylib       = true;
+        options.canBeBundle      = true;
+        // only allow dlopen() of main executables on macOS (eventually ban there too)
 #if TARGET_OS_SIMULATOR
-            options.canBeExecutable  = (strncmp(config.process.progname, "IBDesignablesAgent", 18) == 0);
-#else
-            options.canBeExecutable  = (config.process.platform == Platform::macOS);
+        options.canBeExecutable  = (strncmp(config.process.progname, "IBDesignablesAgent", 18) == 0);
+#else
+        options.canBeExecutable  = (config.process.platform == dyld3::Platform::macOS);
 #endif
-            options.forceUnloadable  = (mode & RTLD_UNLOADABLE);
-            options.requestorNeedsFallbacks = caller ? caller->pre2022Binary : false;
-            options.rpathStack       = (caller ? &loadChainCaller : &loadChainMain);
-            options.finder           = nullptr;
-            topLoader = Loader::getLoader(diag, *this, path, options);
-            if ( topLoader == nullptr ) {
-                setErrorString("dlopen(%s, 0x%04X): %s", path, mode, diag.errorMessageCStr());
-                return;
-            }
-
-            // if RTLD_LOCAL was *not* used, and image was already loaded hidden, then unhide it
-            if ( ((mode & RTLD_LOCAL) == 0) && topLoader->hiddenFromFlat() )
-                topLoader->hiddenFromFlat(true);
-
-            // RTLD_NOLOAD means don't load if not already loaded
-            if ( mode & RTLD_NOLOAD ) {
+        options.forceUnloadable  = (mode & RTLD_UNLOADABLE);
+        options.requestorNeedsFallbacks = caller ? caller->pre2022Binary : false;
+        options.rpathStack       = (caller ? &loadChainCaller : &loadChainMain);
+        options.finder           = nullptr;
+        topLoader = Loader::getLoader(diag, *this, path, options);
+        if ( topLoader == nullptr ) {
+            setErrorString("dlopen(%s, 0x%04X): %s", path, mode, diag.errorMessageCStr());
+            return;
+        }
+
+        // if RTLD_LOCAL was *not* used, and image was already loaded hidden, then unhide it
+        if ( ((mode & RTLD_LOCAL) == 0) && topLoader->hiddenFromFlat() )
+            topLoader->hiddenFromFlat(true);
+
+        // RTLD_NOLOAD means don't load if not already loaded
+        if ( mode & RTLD_NOLOAD ) {
 #if SUPPORT_IMAGE_UNLOADING
-                incDlRefCount(topLoader);
+            incDlRefCount(topLoader);
 #endif // SUPPORT_IMAGE_UNLOADING
-                result = handleFromLoader(topLoader, firstOnly);
-                return;
-            }
-
-            // if RTLD_NODELETE is used on any dlopen, it sets the leavedMapped bit
-            if ( mode & RTLD_NODELETE ) {
-                // dylibs in cache, or dylibs statically link will always remain, so RTLD_NODELETE is already in effect
-                if ( !topLoader->dylibInDyldCache && !topLoader->neverUnload && !topLoader->leaveMapped ) {
-                    // PrebuiltLoaders are never used for things that can be unloaded, so ignore
-                    if ( !topLoader->isPrebuilt ) {
-                        JustInTimeLoader* jitLoader = (JustInTimeLoader*)topLoader;
-                        jitLoader->setLateLeaveMapped();
-                    }
+            result = handleFromLoader(topLoader, firstOnly);
+            return;
+        }
+
+        // if RTLD_NODELETE is used on any dlopen, it sets the leavedMapped bit
+        if ( mode & RTLD_NODELETE ) {
+            // dylibs in cache, or dylibs statically link will always remain, so RTLD_NODELETE is already in effect
+            if ( !topLoader->dylibInDyldCache && !topLoader->neverUnload && !topLoader->leaveMapped ) {
+                // PrebuiltLoaders are never used for things that can be unloaded, so ignore
+                if ( !topLoader->isPrebuilt ) {
+                    JustInTimeLoader* jitLoader = (JustInTimeLoader*)topLoader;
+                    jitLoader->setLateLeaveMapped();
                 }
             }
-
-            STACK_ALLOC_VECTOR(const Loader*, newlyNotDelayed, 128);
-            STACK_ALLOC_VECTOR(Loader::PseudoDylibSymbolToMaterialize, pseudoDylibSymbolsToMaterialize, 8);
-
-            // load all dependents
-            Loader::LoadChain   loadChain { options.rpathStack, topLoader };
-            Loader::LoadOptions depOptions;
-            depOptions.staticLinkage   = true;
-            depOptions.rtldLocal       = false; // RTLD_LOCAL only effects top level dylib
-            depOptions.rtldNoDelete    = (mode & RTLD_NODELETE);
-            depOptions.canBeDylib      = true;
-            depOptions.requestorNeedsFallbacks = topLoader->pre2022Binary;
-            depOptions.rpathStack      = &loadChain;
-            ((Loader*)topLoader)->loadDependents(diag, *this, depOptions);
-            // only do fixups and notifications if new dylibs are loaded (could be dlopen that just bumps the ref count)
-            STACK_ALLOC_VECTOR(const Loader*, newLoaders, loaded.size() - startLoaderCount);
-            for (uint64_t i = startLoaderCount; i != loaded.size(); ++i)
-                newLoaders.push_back(loaded[i]);
-
-            DyldCacheDataConstLazyScopedWriter cacheDataConst(*this);
-            if ( diag.noError() && !newLoaders.empty() ) {
-                // proactive weakDefMap means we update the weakDefMap with everything just loaded before doing any binding
-                if ( config.process.proactivelyUseWeakDefMap ) {
-                    Loader::addWeakDefsToMap(*this, newLoaders);
+        }
+
+        // load all dependents
+        Loader::LoadChain   loadChain { options.rpathStack, topLoader };
+        Loader::LoadOptions depOptions;
+        depOptions.staticLinkage   = true;
+        depOptions.rtldLocal       = false; // RTLD_LOCAL only effects top level dylib
+        depOptions.rtldNoDelete    = (mode & RTLD_NODELETE);
+        depOptions.canBeDylib      = true;
+        depOptions.requestorNeedsFallbacks = topLoader->pre2022Binary;
+        depOptions.rpathStack      = &loadChain;
+        ((Loader*)topLoader)->loadDependents(diag, *this, depOptions);
+        // only do fixups and notifications if new dylibs are loaded (could be dlopen that just bumps the ref count)
+        STACK_ALLOC_VECTOR(const Loader*, newLoaders, loaded.size() - startLoaderCount);
+        for (uint64_t i = startLoaderCount; i != loaded.size(); ++i)
+            newLoaders.push_back(loaded[i]);
+
+        DyldCacheDataConstLazyScopedWriter cacheDataConst(*this);
+        if ( diag.noError() && !newLoaders.empty() ) {
+            // tell debugger about newly loaded images in case there is a crash during fixups
+            notifyDebuggerLoad(newLoaders);
+
+            // proactive weakDefMap means we update the weakDefMap with everything just loaded before doing any binding
+            if ( config.process.proactivelyUseWeakDefMap ) {
+                Loader::addWeakDefsToMap(*this, newLoaders);
+            }
+
+            // do fixups
+            {
+                dyld3::ScopedTimer fixupsTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
+
+                for ( const Loader* ldr : newLoaders ) {
+                    bool allowLazyBinds = ((mode & RTLD_NOW) == 0);
+                    ldr->applyFixups(diag, *this, cacheDataConst, allowLazyBinds);
+                    if ( diag.hasError() )
+                        break;
+#if BUILDING_DYLD && !TARGET_OS_EXCLAVEKIT
+                    // Roots need to patch the uniqued GOTs in the cache
+                    //FIXME: Is the right place to conditionalize this?
+                    ldr->applyCachePatches(*this, cacheDataConst);
+#endif // BUILDING_DYLD && !TARGET_OS_EXCLAVEKIT
                 }
-
-                // do fixups
-                {
-                    dyld3::ScopedTimer fixupsTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
-
-                    for ( const Loader* ldr : newLoaders ) {
-                        bool allowLazyBinds = ((mode & RTLD_NOW) == 0);
-                        ldr->applyFixups(diag, *this, cacheDataConst, allowLazyBinds, &pseudoDylibSymbolsToMaterialize);
-                        if ( diag.hasError() )
-                            break;
-#if BUILDING_DYLD
-                        // Roots need to patch the uniqued GOTs in the cache
-                        //FIXME: Is the right place to conditionalize this?
-                        ldr->applyCachePatches(*this, cacheDataConst);
-#endif // BUILDING_DYLD &&
-                    }
+            }
+
+            if ( diag.noError() ) {
+                // add to permanent ranges
+                STACK_ALLOC_ARRAY(const Loader*, nonCacheNeverUnloadLoaders, newLoaders.size());
+                for (const Loader* ldr : newLoaders) {
+                    if ( !ldr->dylibInDyldCache && ldr->neverUnload )
+                        nonCacheNeverUnloadLoaders.push_back(ldr);
                 }
-
-                if ( diag.noError() ) {
-                    // add to permanent ranges
-                    STACK_ALLOC_ARRAY(const Loader*, nonCacheNeverUnloadLoaders, newLoaders.size());
-                    for (const Loader* ldr : newLoaders) {
-                        if ( !ldr->dylibInDyldCache && ldr->neverUnload )
-                            nonCacheNeverUnloadLoaders.push_back(ldr);
-#if TARGET_OS_EXCLAVEKIT
-#ifdef XRT_PLATFORM_PREMAPPED_CACHE_MACHO_FINALIZE_MEMORY_STATE
-                        // Notify ExclavePlatform that it is safe to setup endpoints in Mach-O sections
-                        if ( ldr->dylibInDyldCache ) {
-                            const Header* hdr = ldr->header(*this);
-                            int64_t slide = hdr->getSlide();
-                            xrt_platform_premapped_cache_macho_finalize_memory_state((void*)hdr, slide);
-                        }
-#endif // XRT_PLATFORM_PREMAPPED_CACHE_MACHO_FINALIZE_MEMORY_STATE
+                if ( !nonCacheNeverUnloadLoaders.empty() )
+                    this->addPermanentRanges(nonCacheNeverUnloadLoaders);
+
+#if !TARGET_OS_EXCLAVEKIT
+                // notify kernel about new static user probes
+                notifyDtrace(newLoaders);
 #endif // !TARGET_OS_EXCLAVEKIT
-                    }
-                    if ( !nonCacheNeverUnloadLoaders.empty() )
-                        this->addPermanentRanges(nonCacheNeverUnloadLoaders);
-
-#if !TARGET_OS_EXCLAVEKIT
-                    // notify kernel about new static user probes
-                    notifyDtrace(newLoaders);
-#endif // !TARGET_OS_EXCLAVEKIT
-
-                    // If any previous images had missing flat lazy symbols, try bind them again now
-                    rebindMissingFlatLazySymbols(newLoaders);
+
+                // If any previous images had missing flat lazy symbols, try bind them again now
+                rebindMissingFlatLazySymbols(newLoaders);
+
+                // if image has thread locals, set them up
+                for ( const Loader* ldr : newLoaders ) {
+                    const MachOAnalyzer* ma = ldr->analyzer(*this);
+                    if ( ma->hasThreadLocalVariables() )
+                        setUpTLVs(ma);
                 }
-            }
+
+                // Store loaders to be notified later
+                loadersToNotify.reserve(newLoaders.size());
+                for (const Loader* ldr : newLoaders)
+                    loadersToNotify.push_back(ldr);
+            }
+        }
 
 #if SUPPORT_IMAGE_UNLOADING
-            // increment ref count before notifiers are called and before initializers are run,
-            // because either of those could call dlclose() and cause a garbage collection.
-            if ( diag.noError() )
-                incDlRefCount(topLoader);
+        // increment ref count before notifiers are called and before initializers are run,
+        // because either of those could call dlclose() and cause a garbage collection.
+        if ( diag.noError() )
+            incDlRefCount(topLoader);
 #endif // SUPPORT_IMAGE_UNLOADING
 
-            // If there was an error while loading or doing fixups, then unload everything added in this dlopen.
-            // This has to be done while we still have the LoadersLock
-            if ( diag.hasError() ) {
-                setErrorString("dlopen(%s, 0x%04X): %s", path, mode, diag.errorMessageCStr());
-
-                // Remove missing lazy symbols for the new loaders.  These were recorded eagerly during symbol binding
-                removeMissingFlatLazySymbols(newLoaders);
-
-                // remove any entries these temp dylibs may have map in the weak-def map
-                if ( this->weakDefMap != nullptr ) {
-                    for ( const Loader* incompleteLoader : newLoaders )
-                        this->removeDynamicDependencies(incompleteLoader);
-                }
+        // If there was an error while loading or doing fixups, then unload everything added in this dlopen.
+        // This has to be done while we still have the LoadersLock
+        if ( diag.hasError() ) {
+            setErrorString("dlopen(%s, 0x%04X): %s", path, mode, diag.errorMessageCStr());
+
+            // Remove missing lazy symbols for the new loaders.  These were recorded eagerly during symbol binding
+            removeMissingFlatLazySymbols(newLoaders);
+
+            // remove any entries these temp dylibs may have map in the weak-def map
+            if ( this->weakDefMap != nullptr ) {
+                for ( const Loader* incompleteLoader : newLoaders )
+                    this->removeDynamicDependencies(incompleteLoader);
+            }
+
+            // Remove the loaders from the image lists
+            notifyDebuggerUnload(newLoaders);
 
 #if SUPPORT_IMAGE_UNLOADING
-                // unmap everthing just loaded (note: unmap() does not unmap stuff in shared cache)
-                for ( const Loader* ldr : newLoaders )
-                    ldr->unmap(*this, true);
+            // unmap everthing just loaded (note: unmap() does not unmap stuff in shared cache)
+            for ( const Loader* ldr : newLoaders )
+                ldr->unmap(*this, true);
 #endif
-
-                // remove new loaders from runtime list
-                while ( loaded.size() > startLoaderCount ) {
-                    //const Loader* removeeLdr = loaded.back();
-                    //log("removing %p from state.loaded (%s)\n", removeeLdr, removeeLdr->path(*this));
-                    loaded.pop_back();
-                    // FIXME: free malloced JITLoaders
-                }
-                result    = nullptr;
-                topLoader = nullptr;
-
-                // Clear any potential objc patching entries from the lists.  We aren't going to do patching
-                // on these binaries as the dlopen failed
-                this->objcReplacementClasses.clear();
-                while ( this->patchedObjCClasses.size() > startPatchedObjCClassesCount ) {
-                    this->patchedObjCClasses.pop_back();
-                }
-                while ( this->patchedSingletons.size() > startPatchedSingletonsCount ) {
-                    this->patchedSingletons.pop_back();
-                }
-            }
-
-            // on success, run objc notifiers.  This has to be done while still in the write lock as
-            // the notifier mutates the list of objc classes
-            if ( (topLoader != nullptr) && ((mode & RTLD_NOLOAD) == 0) && diag.noError() ) {
-                const Loader* rootLoaders[1] = { topLoader };
-                std::span<const Loader*> rootLoadersSpan(rootLoaders, 1);
-#if BUILDING_DYLD
-                partitionDelayLoads(newLoaders, rootLoadersSpan, &newlyNotDelayed);
-                if ( !config.log.linksWith.empty() ) {
-                    char callerName[256];
-                    strlcpy(callerName, "dlopen", sizeof(callerName));
-                    if ( caller != nullptr ) {
-                        strlcpy(callerName, caller->leafName(*this), sizeof(callerName));
-                        strlcat(callerName, ": dlopen(", sizeof(callerName));
-                        strlcat(callerName, topLoader->leafName(*this), sizeof(callerName));
-                        strlcat(callerName, ")", sizeof(callerName));
-                    }
-                    topLoader->logChainToLinksWith(*this, callerName);
-                }
-#endif
-                // tell debugger about newly loaded images or newly undelayed images
-                if ( !newlyNotDelayed.empty() ) {
-                    std::span<const Loader*> ldrs(&newlyNotDelayed[0], (size_t)newlyNotDelayed.size());
-                    notifyDebuggerLoad(ldrs);
-                }
-
-#if BUILDING_DYLD
-                // if image has thread locals, set them up
-                for ( const Loader* ldr : newlyNotDelayed ) {
-                    if ( ldr->hasTLVs ) {
-                        if ( mach_o::Error err = this->libSystemHelpers.setUpThreadLocals(config.dyldCache.addr, ldr->header(*this)) ) {
-                            diag.error("failed to set up thread local variables for '%s': %s", ldr->path(*this), err.message());
-                        }
-                    }
-                }
-#endif
-                doSingletonPatching(cacheDataConst);
-                notifyObjCPatching();
-            }
-
-            // Copy the temporary vectors from the protected stack to the persistent allocator for safety
-            if ( !newlyNotDelayed.empty() ) {
-                newlyNotDelayedResult = persistentAllocator.makeUnique<LoaderVector>(newlyNotDelayed.begin(), newlyNotDelayed.end(),
-                                                                                     persistentAllocator);
-            }
-            if ( !pseudoDylibSymbolsToMaterialize.empty() ) {
-                pseudoDylibSymbolsToMaterializeResult = persistentAllocator.makeUnique<PseudoDylibSymbolsVector>(pseudoDylibSymbolsToMaterialize.begin(),
-                                                                                                                 pseudoDylibSymbolsToMaterialize.end(),
-                                                                                                                 persistentAllocator);
-            }
-        });
-
-        // do the initializers on the regular stack. We should never be on the protected stack at this point
-        // as it is not supported to re-enter dlopen (from an initializer doing a dlopen) while on the protected stack
-        // ie, withProtectedStack() asserts that no-one is using the protected stack, even this thread in an earlier frame
-        // Finalize requested symbols.
-        if ( pseudoDylibSymbolsToMaterializeResult && !pseudoDylibSymbolsToMaterializeResult->empty() ) {
-            const PseudoDylibSymbolsVector& pseudoDylibSymbolsToMaterialize = *pseudoDylibSymbolsToMaterializeResult;
-            bool foundError = false;
-
-            // n^2, but oh well, maybe there's not too many pseudo dylibs to worry about
-            STACK_ALLOC_VECTOR(const Loader*, seenLoaders, 8);
-            for ( uint64_t i = 0; i != pseudoDylibSymbolsToMaterialize.size(); ++i ) {
-                const Loader::PseudoDylibSymbolToMaterialize& ldrAndSymbol = pseudoDylibSymbolsToMaterialize[i];
-                if ( std::find(seenLoaders.begin(), seenLoaders.end(), ldrAndSymbol.first) != seenLoaders.end() )
-                    continue;
-
-                // Get all the symbols for this loader
-                STACK_ALLOC_VECTOR(const char *, symbols, 8);
-                symbols.push_back(ldrAndSymbol.second);
-                for ( uint64_t j = (i + 1); j != pseudoDylibSymbolsToMaterialize.size(); ++j ) {
-                    const Loader::PseudoDylibSymbolToMaterialize& ldrAndSymbol2 = pseudoDylibSymbolsToMaterialize[j];
-                    if ( ldrAndSymbol2.first == ldrAndSymbol.first )
-                        symbols.push_back(ldrAndSymbol2.second);
-                }
-
-                const PseudoDylib* pd = ldrAndSymbol.first->isJustInTimeLoader()->pseudoDylib();
-                if ( char *errMsg = pd->finalizeRequestedSymbols(symbols)) {
-                    // TODO: roll back image loads above on failure.
-                    setErrorString("dlopen(%s, 0x%04X): %s", path, mode, errMsg);
-                    pd->disposeString(errMsg);
-                    foundError = true;
-                    break;
-                }
-
-                seenLoaders.push_back(ldrAndSymbol.first);
-            }
-
-            if ( foundError ) {
-                result    = nullptr;
-                topLoader = nullptr;
-            }
-        }
-
-        // on success, run initializers
-        if ( (topLoader != nullptr) && ((mode & RTLD_NOLOAD) == 0) ) {
-            // Note: we have released the withLoadersWriteLock while running the notifiers/initializers
-            // This is intentional to avoid deadlocks with other framework locks, that might call dyld
-            // inquiry functions now (such as walking loaded images).
-            // It is safe, because we still have the API-lock, so no other thread can call dlclose() and remove
-            // the images that are having their notifiers/initializers run.  A initializer may call dlopen() again and
-            // add more images, but that will be on the same thread as this, so the ivar in Loaders about if
-            // its initializer has been run does not need to be thread safe.
-
-            // notify about any delay-init dylibs that just got moved to being needed
-            // as well as images loaded by this dlopen that are not delayed
-            if ( newlyNotDelayedResult && !newlyNotDelayedResult->empty() ) {
-                LoaderVector& vec = *newlyNotDelayedResult;
-                std::span<const Loader*> ldrs(&vec[0], (size_t)vec.size());
-                notifyLoad(ldrs);
-            }
-
-            // run initializers (don't run them if dlopen() call was within libSystem's initializer)
-            bool runInitializer = this->libSystemInitialized(); // lib system initialized
-    #if SUPPPORT_PRE_LC_MAIN
-            // if this is a pre-10.8 macOS main executable, do run initializer (rdar://130506337)
-            if ( !runInitializer && (this->config.process.mainExecutableHdr->unixThreadLoadCommand() != nullptr) )
-                runInitializer = true;
-    #endif
-            if ( runInitializer )
-                topLoader->runInitializersBottomUpPlusUpwardLinks(*this);
-            else if ( this->config.log.initializers )
-                log("dlopen() within libSystem's initializer, so skipping initialization of %s\n", topLoader->path(*this));
-
-            // make handle
-            result = handleFromLoader(topLoader, firstOnly);
-        }
-
-        // Clear the data on the persistent allocator. We do this with a lock for the allocator
-        if ( newlyNotDelayedResult || pseudoDylibSymbolsToMaterializeResult ) {
-            locks.withLoadersWriteLockAndProtectedStack([&] {
-                if ( newlyNotDelayedResult )
-                    newlyNotDelayedResult.release();
-                if ( pseudoDylibSymbolsToMaterializeResult )
-                    pseudoDylibSymbolsToMaterializeResult.release();
-            });
-        }
-
-        if ( config.log.apis ) {
-            PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)this->libSystemHelpers.pthread_getspecific(dlerrorPthreadKey());
-            if ( (errorBuffer != nullptr) && errorBuffer->valid )
-                log("      dlopen(%s) => NULL, '%s'\n", Loader::leafName(path), errorBuffer->message);
-            else
-                log("      dlopen(%s) => %p\n", Loader::leafName(path), result);
+            
+            // remove new loaders from runtime list
+            while ( loaded.size() > startLoaderCount ) {
+                //const Loader* removeeLdr = loaded.back();
+                //log("removing %p from state.loaded (%s)\n", removeeLdr, removeeLdr->path());
+                loaded.pop_back();
+                // FIXME: free malloced JITLoaders
+            }
+            result    = nullptr;
+            topLoader = nullptr;
+
+            // Clear any potential objc patching entries from the lists.  We aren't going to do patching
+            // on these binaries as the dlopen failed
+            this->objcReplacementClasses.clear();
+            while ( this->patchedObjCClasses.size() > startPatchedObjCClassesCount ) {
+                this->patchedObjCClasses.pop_back();
+            }
+            while ( this->patchedSingletons.size() > startPatchedSingletonsCount ) {
+                this->patchedSingletons.pop_back();
+            }
+        }
+
+        // on success, run objc notifiers.  This has to be done while still in the write lock as
+        // the notifier mutates the list of objc classes
+        if ( (topLoader != nullptr) && ((mode & RTLD_NOLOAD) == 0) && diag.noError() ) {
+            const Loader* rootLoaders[1] = { topLoader };
+            std::span<const Loader*> rootLoadersSpan(rootLoaders, 1);
+            partitionDelayLoads(newLoaders, rootLoadersSpan, loadersUnDelayed);
+            doSingletonPatching(cacheDataConst);
+            notifyObjCPatching();
         }
     });
 
+    // on success, run initializers
+    if ( (topLoader != nullptr) && ((mode & RTLD_NOLOAD) == 0) ) {
+        // Note: we have released the withLoadersWriteLock while running the notifiers/initializers
+        // This is intentional to avoid deadlocks with other framework locks, that might call dyld
+        // inquiry functions now (such as walking loaded images).
+        // It is safe, because we still have the API-lock, so no other thread can call dlclose() and remove
+        // the images that are having their notifiers/initializers run.  A initializer may call dlopen() again and
+        // add more images, but that will be on the same thread as this, so the ivar in Loaders about if
+        // its initializer has been run does not need to be thread safe.
+
+        // first notify about any delay-init dylibs that just got moved to being needed
+        if ( !loadersUnDelayed.empty() ) {
+            std::span<const Loader*> ldrs(&loadersUnDelayed[0], (size_t)loadersUnDelayed.size());
+            notifyLoad(ldrs);
+        }
+
+        // notify everyone else about all loaded images (do this late, so we don't have to undo incase of error).
+        if ( !loadersToNotify.empty() ) {
+            std::span<const Loader*> ldrs(&loadersToNotify[0], (size_t)loadersToNotify.size());
+            notifyLoad(ldrs);
+        }
+
+        // run initializers
+        topLoader->runInitializersBottomUpPlusUpwardLinks(*this);
+
+        // make handle
+        result = handleFromLoader(topLoader, firstOnly);
+    }
+
+    if ( config.log.apis ) {
+        PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)this->libSystemHelpers->pthread_getspecific(dlerrorPthreadKey());
+        if ( (errorBuffer != nullptr) && errorBuffer->valid )
+            log("      dlopen(%s) => NULL, '%s'\n", Loader::leafName(path), errorBuffer->message);
+        else
+            log("      dlopen(%s) => %p\n", Loader::leafName(path), result);
+    }
     timer.setData4(result);
     return result;
 }
@@ -1768,10 +1671,8 @@
     __block Diagnostics diag;
     config.pathOverrides.forEachPathVariant(path, config.process.platform, false, true, topStop, ^(const char* possiblePath, ProcessConfig::PathOverrides::Type type, bool& stop) {
         __block Diagnostics possiblePathDiag;
-        config.syscall.withReadOnlyMappedFile(possiblePathDiag, possiblePath, true, ^(const void* mapping, size_t mappedSize, bool isOSBinary, const FileID&, const char*, const int) {
-            uint64_t sliceOffset = 0;
-            uint64_t sliceSize = 0;
-            if ( MachOFile::compatibleSlice(possiblePathDiag, sliceOffset, sliceSize, mapping, mappedSize, path, config.process.platform, isOSBinary, *config.process.archs, config.security.internalInstall) != nullptr ) {
+        config.syscall.withReadOnlyMappedFile(possiblePathDiag, possiblePath, true, ^(const void* mapping, size_t mappedSize, bool isOSBinary, const FileID&, const char*) {
+            if ( MachOFile::compatibleSlice(possiblePathDiag, mapping, mappedSize, path, config.process.platform, isOSBinary, *config.process.archs, config.security.internalInstall) != nullptr ) {
                 result = true;
                 stop   = true;
             }
@@ -1796,10 +1697,12 @@
 #endif // !TARGET_OS_EXCLAVEKIT
 }
 
+#if !__i386__
 void* APIs::dlopen_audited(const char* path, int mode)
 {
     return dlopen(path, mode);
 }
+#endif
 
 void* APIs::dlsym(void* handle, const char* symbolName)
 {
@@ -1813,8 +1716,16 @@
 
 #if !TARGET_OS_EXCLAVEKIT
     // allow apps to disable dlsym()
-    if ( addressLookupsDisabled(symbolName) ) {
-        // dlsym() blocked is enabled and this symbol is not in the allow list
+    if ( config.security.dlsymBlocked ) {
+        // either abort
+        if ( config.security.dlsymAbort ) {
+#if BUILDING_DYLD
+            halt("dlsym() called");
+#elif BUILDING_UNIT_TESTS
+            abort();
+#endif
+        }
+        // or silently return NULL
         if ( config.log.apis )
             log("     dlsym(\"%s\") => NULL (blocked)\n", symbolName);
         return nullptr;
@@ -1834,8 +1745,7 @@
         __block bool found = false;
         locks.withLoadersReadLock(^{
             for ( const dyld4::Loader* image : loaded ) {
-                if ( image->header(*this)->noDynamicAccess() )
-                    continue;
+
                 if ( !image->hiddenFromFlat() && image->hasExportedSymbol(diag, *this, underscoredName, Loader::shallow, Loader::runResolver, &result) ) {
                     found = true;
                     break;
@@ -1898,6 +1808,15 @@
         // handle value was something returned by dlopen()
         bool          firstOnly;
         const Loader* image = loaderFromHandle(handle, firstOnly);
+#if TARGET_OS_OSX
+        // FIXME: temp work around for syspolicyd <rdar://73731400>
+        if ( (MachOAnalyzer*)handle == config.process.mainExecutable ) {
+            setErrorString("dlsym(%p, %s): invalid handle", handle, symbolName);
+            if ( config.log.apis )
+                log("     dlsym(\"%s\") => NULL\n", symbolName);
+             return nullptr;
+        }
+#endif
         // verify is a valid loader
         if ( !validLoader(image) ) {
             setErrorString("dlsym(%p, %s): invalid handle", handle, symbolName);
@@ -1917,25 +1836,12 @@
     }
 
     if ( result.targetLoader != nullptr ) {
-        if ( result.targetLoader->header(*this)->noDynamicAccess() ) {
-            if ( config.log.apis )
-                log("     dlsym(\"%s\") => NULL (no dynamic access)\n", symbolName);
-            return nullptr;
-        }
-        void* ptr = (void*)result.targetAddressForDlsym;
-
-        // Finalize the symbol if this is a pseudodylib loader.
-        if ( result.isMaterializing ) {
-            auto *pd = result.targetLoader->isJustInTimeLoader()->pseudoDylib();
-            if ( char *errMsg = pd->finalizeRequestedSymbols({&result.targetSymbolName, 1})) {
-                if ( config.log.apis )
-                    log("     dlsym(\"%s\") => NULL, error finalizing pseudo-dylib symbols: %s", symbolName, errMsg);
-                setErrorString("dlsym(%s): error finalizing pseudo-dylib symbols: %s", symbolName, errMsg);
-                pd->disposeString(errMsg);
-                return nullptr;
-            }
-        }
-
+        void* ptr = (void*)Loader::resolvedAddress(*this, result);
+        ptr       = (void*)Loader::interpose(*this, (uintptr_t)ptr);
+#if __has_feature(ptrauth_calls)
+        if ( result.isCode )
+            ptr = __builtin_ptrauth_sign_unauthenticated(ptr, ptrauth_key_asia, 0);
+#endif
         if ( config.log.apis )
             log("     dlsym(\"%s\") => %p\n", symbolName, ptr);
         timer.setData4((uint64_t)(stripPointer(ptr)));
@@ -1946,7 +1852,6 @@
     return nullptr;
 
 }
-#endif // !TARGET_OS_DRIVERKIT
 
 bool APIs::dyld_shared_cache_some_image_overridden()
 {
@@ -1960,12 +1865,16 @@
 {
     if ( config.log.apis )
         log("_dyld_get_shared_cache_uuid(%p)\n", uuid);
+#if !TARGET_OS_EXCLAVEKIT
     const DyldSharedCache* sharedCache = config.dyldCache.addr;
     if ( sharedCache != nullptr ) {
         sharedCache->getUUID(uuid);
         return true;
     }
     return false;
+#else
+    return false;
+#endif // !TARGET_OS_EXCLAVEKIT
 }
 
 const void* APIs::_dyld_get_shared_cache_range(size_t* mappedSize)
@@ -1974,10 +1883,12 @@
         log("_dyld_get_shared_cache_range(%p)", mappedSize);
     const void* result = nullptr;
     *mappedSize = 0;
+#if !TARGET_OS_EXCLAVEKIT
     if ( const DyldSharedCache* sharedCache = config.dyldCache.addr ) {
         *mappedSize = (size_t)sharedCache->mappedSize();
         result = sharedCache;
     }
+#endif // !TARGET_OS_EXCLAVEKIT
     if ( config.log.apis )
         log(" => %p,0x%lX\n", result, *mappedSize);
     return result;
@@ -2020,7 +1931,7 @@
         if ( ml != nullptr ) {
             infos[i].image         = ml;
             infos[i].offsetInImage = (uintptr_t)addr - (uintptr_t)ml;
-            ((Header*)ml)->getUuid(infos[i].uuid);
+            ml->getUuid(infos[i].uuid);
         }
     }
 }
@@ -2028,21 +1939,21 @@
 void APIs::_dyld_register_for_image_loads(LoadNotifyFunc func)
 {
     if ( config.log.apis )
-        log("_dyld_register_for_image_loads(%p)\n", func.raw());
+        log("_dyld_register_for_image_loads(%p)\n", func);
 #if !TARGET_OS_EXCLAVEKIT
     // callback about already loaded images
     locks.withLoadersReadLock(^{
         for ( const dyld4::Loader* image : loaded ) {
             const MachOLoaded* ml = image->loadAddress(*this);
             if ( config.log.notifications )
-                log("add notifier %p called with mh=%p\n", func.raw(), ml);
-            func(ml, image->path(*this), !image->neverUnload);
+                log("add notifier %p called with mh=%p\n", func, ml);
+            func(ml, image->path(), !image->neverUnload);
         }
     });
 
     // add to list of functions to call about future loads
-    const Loader* callbackLoader = this->findImageContaining(func.raw());
-    locks.withNotifiersWriteLock(^{
+    const Loader* callbackLoader = this->findImageContaining((void*)func);
+    locks.withNotifiersWriteLock(memoryManager, ^() {
         addNotifyLoadImage(callbackLoader, func);
     });
 #else
@@ -2050,10 +1961,10 @@
 #endif // !TARGET_OS_EXCLAVEKIT
 }
 
-void APIs::_dyld_register_for_bulk_image_loads(BulkLoadNotifier func)
-{
-    if ( config.log.apis )
-        log("_dyld_register_for_bulk_image_loads(%p)\n", func.raw());
+void APIs::_dyld_register_for_bulk_image_loads(void (*func)(unsigned imageCount, const mach_header* mhs[], const char* paths[]))
+{
+    if ( config.log.apis )
+        log("_dyld_register_for_bulk_image_loads(%p)\n", func);
 #if !TARGET_OS_EXCLAVEKIT
 
     // callback about already loaded images
@@ -2063,17 +1974,17 @@
         const char*        paths[count];
         for ( unsigned i = 0; i < count; ++i ) {
             mhs[i]   = loaded[i]->loadAddress(*this);
-            paths[i] = loaded[i]->path(*this);
+            paths[i] = loaded[i]->path();
         }
         //dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)mhs[0], (uint64_t)func, 0);
         if ( config.log.notifications )
-            log("add bulk notifier %p called with %d images\n", func.raw(), count);
-        func(count, (const mach_header**)mhs, (const char**)paths);
+            log("add bulk notifier %p called with %d images\n", func, count);
+        func(count, mhs, paths);
     });
 
     // add to list of functions to call about future loads
-    const Loader* callbackLoader = this->findImageContaining(func.raw());
-    locks.withNotifiersWriteLock(^{
+    const Loader* callbackLoader = this->findImageContaining((void*)func);
+    locks.withNotifiersWriteLock(memoryManager, ^() {
         addNotifyBulkLoadImage(callbackLoader, func);
     });
 #else
@@ -2212,7 +2123,7 @@
 }
 #endif // !TARGET_OS_EXCLAVEKIT
 
-int APIs::dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extraSearchDirs[], IterateCacheTextFunc callback)
+int APIs::dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extraSearchDirs[], void (^callback)(const dyld_shared_cache_dylib_text_info* info))
 {
     if ( config.log.apis )
         log("dyld_shared_cache_find_iterate_text()\n");
@@ -2297,8 +2208,7 @@
 
     // get base address of cache
     __block uint64_t cacheUnslidBaseAddress = 0;
-    sharedCache->forEachRegion(^(const void* content, uint64_t vmAddr, uint64_t size, uint32_t initProt,
-                                 uint32_t maxProt, uint64_t flags, uint64_t fileOffset, bool& stopRegion) {
+    sharedCache->forEachRegion(^(const void* content, uint64_t vmAddr, uint64_t size, uint32_t initProt, uint32_t maxProt, uint64_t flags, bool& stopRegion) {
         if ( cacheUnslidBaseAddress == 0 )
             cacheUnslidBaseAddress = vmAddr;
     });
@@ -2324,7 +2234,7 @@
 #endif // !TARGET_OS_EXCLAVEKIT
 }
 
-int APIs::dyld_shared_cache_iterate_text(const uuid_t cacheUuid, IterateCacheTextFunc callback)
+int APIs::dyld_shared_cache_iterate_text(const uuid_t cacheUuid, void (^callback)(const dyld_shared_cache_dylib_text_info* info))
 {
     if ( config.log.apis )
         log("dyld_shared_cache_iterate_text()\n");
@@ -2340,7 +2250,7 @@
     mach_task_self_ = task_self_trap();
 
 #if HAS_EXTERNAL_STATE
-    this->externallyViewable->fork_child();
+    this->externallyViewable.fork_child();
 #endif // !HAS_EXTERNAL_STATE
     locks.resetLockInForkChild();
 #else
@@ -2407,10 +2317,12 @@
 #if SUPPORT_PREBUILTLOADERS
     // If main program has PrebuiltLoader, check selector table in that
     if ( const PrebuiltLoaderSet* mainSet = this->processPrebuiltLoaderSet() ) {
-        const char* uniqueName = prebuilt_objc::findSelector(this, this->objcSelectorMap, selName);
-        if ( config.log.apis )
-            log("_dyld_get_objc_selector(%s) => %s\n", selName, uniqueName);
-        return uniqueName;
+        if ( const ObjCSelectorOpt* selectorHashTable = mainSet->objcSelectorOpt() ) {
+            const char* uniqueName = selectorHashTable->getString(selName, *this);
+            if ( config.log.apis )
+                log("_dyld_get_objc_selector(%s) => %s\n", selName, uniqueName);
+            return uniqueName;
+        }
     }
 #endif // SUPPORT_PREBUILTLOADERS
     if ( config.log.apis )
@@ -2422,7 +2334,7 @@
 }
 
 void APIs::_dyld_for_each_objc_class(const char* className,
-                                     ObjCClassFunc callback)
+                                     void (^callback)(void* classPtr, bool isLoaded, bool* stop))
 {
     if ( config.log.apis )
         log("_dyld_get_objc_class(%s)\n", className);
@@ -2430,18 +2342,12 @@
 #if SUPPORT_PREBUILTLOADERS
     // If main program has PrebuiltLoader, check classes table in that
     if ( const PrebuiltLoaderSet* mainSet = this->processPrebuiltLoaderSet() ) {
-        __block bool stop = false;
-        prebuilt_objc::forEachClass(this, this->objcClassMap, className,
-                                    ^(const dyld3::Array<const PrebuiltLoader::BindTargetRef*>& values) {
-            for ( const PrebuiltLoader::BindTargetRef* value : values ) {
-                callback((void*)value->value(*this), true, &stop);
-                if ( stop )
-                    break;
-            }
-        });
-        if ( stop ) {
-            // If we found the class here, then stop.  Otherwise fall through to looking in the shared cache
-            return;
+        if ( const ObjCDataStructOpt* classesHashTable = mainSet->objcClassOpt() ) {
+            bool stop = classesHashTable->forEachDataStruct(className, *this, callback);
+            if ( stop ) {
+                // If we found the class here, then stop.  Otherwise fall through to looking in the shared cache
+                return;
+            }
         }
     }
 #endif
@@ -2469,7 +2375,7 @@
 }
 
 void APIs::_dyld_for_each_objc_protocol(const char* protocolName,
-                                        ObjCProtocolFunc callback)
+                                        void (^callback)(void* protocolPtr, bool isLoaded, bool* stop))
 {
     if ( config.log.apis )
         log("_dyld_get_objc_protocol(%s)\n", protocolName);
@@ -2477,18 +2383,12 @@
 #if SUPPORT_PREBUILTLOADERS
     // If main program has PrebuiltLoader, check protocols table in that
     if ( const PrebuiltLoaderSet* mainSet = this->processPrebuiltLoaderSet() ) {
-        __block bool stop = false;
-        prebuilt_objc::forEachProtocol(this, this->objcProtocolMap, protocolName,
-                                       ^(const dyld3::Array<const PrebuiltLoader::BindTargetRef*>& values) {
-            for ( const PrebuiltLoader::BindTargetRef* value : values ) {
-                callback((void*)value->value(*this), true, &stop);
-                if ( stop )
-                    break;
-            }
-        });
-        if ( stop ) {
-            // If we found the protocol here, then stop.  Otherwise fall through to looking in the shared cache
-            return;
+        if ( const ObjCDataStructOpt* protocolsHashTable = mainSet->objcProtocolOpt() ) {
+            bool stop = protocolsHashTable->forEachDataStruct(protocolName, *this, callback);
+            if ( stop ) {
+                // If we found the class here, then stop.  Otherwise fall through to looking in the shared cache
+                return;
+            }
         }
     }
 #endif
@@ -2515,7 +2415,7 @@
 #endif // !TARGET_OS_EXCLAVEKIT
 }
 
-void APIs::_dyld_visit_objc_classes(ObjCVisitClassesFunc callback)
+void APIs::_dyld_visit_objc_classes(void (^callback)(const void* classPtr))
 {
     if ( config.log.apis )
         log("_dyld_visit_objc_classes()\n");
@@ -2678,7 +2578,7 @@
     const SwiftOptimizationHeader* swiftOptHeader = config.dyldCache.swiftCacheInfo;
 
     // We need objc, swift, and of the correct versions.  If anything isn't right, just bail out
-    if ( !objcHeaderInfoRW || !swiftOptHeader )
+    if ( !objcHeaderInfoRW || !swiftOptHeader || (swiftOptHeader->version != 1))
         return { _dyld_protocol_conformance_result_kind_not_found, nullptr };
 
     if ( (typeDescriptor != nullptr) && (swiftOptHeader->typeConformanceHashTableCacheOffset != 0) ) {
@@ -2761,7 +2661,7 @@
     const SwiftOptimizationHeader* swiftOptHeader = config.dyldCache.swiftCacheInfo;
 
     // We need objc, swift, and of the correct versions.  If anything isn't right, just bail out
-    if ( !objcHeaderInfoRW || !swiftOptHeader )
+    if ( !objcHeaderInfoRW || !swiftOptHeader || (swiftOptHeader->version != 1))
         return { _dyld_protocol_conformance_result_kind_not_found, nullptr };
 
     if ( swiftOptHeader->foreignTypeConformanceHashTableCacheOffset != 0 ) {
@@ -2959,7 +2859,7 @@
     return { _dyld_protocol_conformance_result_kind_not_found, nullptr };
 }
 
-static _dyld_section_info_result lookupObjCInfo(_dyld_section_location_kind kind, const Header* hdr,
+static _dyld_section_info_result lookupObjCInfo(_dyld_section_location_kind kind, const dyld3::MachOFile* mf,
                                                 const SectionLocations* metadata)
 {
     const uint64_t* sectionOffsets = metadata->offsets;
@@ -2968,7 +2868,7 @@
     uint64_t sectionOffset = sectionOffsets[kind];
     uint64_t sectionSize = sectionSizes[kind];
     if ( sectionOffset != 0 )
-        return { (uint8_t*)hdr + sectionOffset, (size_t)sectionSize };
+        return { (uint8_t*)mf + sectionOffset, (size_t)sectionSize };
 
     return { nullptr, 0 };
 }
@@ -2982,11 +2882,11 @@
     if ( kind >= _dyld_section_location_count )
         return { nullptr, (size_t)-1 };
 
-    const Header* hdr = (const Header*)mh;
+    const dyld3::MachOFile* mf = (const dyld3::MachOFile*)mh;
     if ( sectionLocations == nullptr ) {
         SectionLocations metadata;
-        JustInTimeLoader::parseSectionLocations(hdr, metadata);
-        return lookupObjCInfo(kind, hdr, &metadata);
+        JustInTimeLoader::parseSectionLocations(mf, metadata);
+        return lookupObjCInfo(kind, mf, &metadata);
     }
 
 #if !TARGET_OS_EXCLAVEKIT
@@ -3004,22 +2904,20 @@
     // We have metadata, but it might be the wrong version, ie, dyld root running with shared cache
     // metadata
     const Loader* ldr = (const Loader*)sectionLocations;
-    if ( !ldr->validMagic() || ldr->getSectionLocations()->version != 1 )
+    if ( ldr->getSectionLocations()->version != 1 )
         return this->_dyld_lookup_section_info(mh, nullptr, kind);
 
-    return lookupObjCInfo(kind, hdr, ldr->getSectionLocations());
-}
-
-#if !TARGET_OS_EXCLAVEKIT
+    return lookupObjCInfo(kind, mf, ldr->getSectionLocations());
+}
+
 static PseudoDylibCallbacks *createPseudoDylibCallbacks(Allocator &allocator,
-                                                        ReadOnlyCallback<_dyld_pseudodylib_dispose_string> dispose_string,
-                                                        ReadOnlyCallback<_dyld_pseudodylib_initialize> initialize,
-                                                        ReadOnlyCallback<_dyld_pseudodylib_deinitialize> deinitialize,
-                                                        ReadOnlyCallback<_dyld_pseudodylib_lookup_symbols> lookup_symbols,
-                                                        ReadOnlyCallback<_dyld_pseudodylib_lookup_address> lookup_address,
-                                                        ReadOnlyCallback<_dyld_pseudodylib_find_unwind_sections> find_unwind_sections,
-                                                        ReadOnlyCallback<_dyld_pseudodylib_loadable_at_path> loadable_at_path,
-                                                        ReadOnlyCallback<_dyld_pseudodylib_finalize_requested_symbols> finalize_requested_symbols) {
+                                                        _dyld_pseudodylib_dispose_string dispose_string,
+                                                        _dyld_pseudodylib_initialize initialize,
+                                                        _dyld_pseudodylib_deinitialize deinitialize,
+                                                        _dyld_pseudodylib_lookup_symbols lookup_symbols,
+                                                        _dyld_pseudodylib_lookup_address lookup_address,
+                                                        _dyld_pseudodylib_find_unwind_sections find_unwind_sections,
+                                                        _dyld_pseudodylib_loadable_at_path loadable_at_path) {
     PseudoDylibCallbacks* pd_cb =
          (PseudoDylibCallbacks*)allocator.aligned_alloc(alignof(PseudoDylibCallbacks),
                                                         sizeof(PseudoDylibCallbacks));
@@ -3030,42 +2928,25 @@
     pd_cb->lookupAddress = lookup_address;
     pd_cb->findUnwindSections = find_unwind_sections;
     pd_cb->loadableAtPath = loadable_at_path;
-    pd_cb->finalizeRequestedSymbols = finalize_requested_symbols;
     return pd_cb;
 }
-#endif // !TARGET_OS_EXCLAVEKIT
-
-_dyld_pseudodylib_callbacks_handle APIs::_dyld_pseudodylib_register_callbacks(const struct PseudoDylibRegisterCallbacks* callbacks) {
-#if !TARGET_OS_EXCLAVEKIT
-    if ( !this->config.security.allowDevelopmentVars ) {
-        if ( this->config.log.apis )
-            log("_dyld_pseudodylib_register_callbacks() => nullptr: blocked by security policy");
-        return nullptr;
-    }
-
+
+_dyld_pseudodylib_callbacks_handle APIs::_dyld_pseudodylib_register_callbacks(const struct _dyld_pseudodylib_callbacks* callbacks) {
     PseudoDylibCallbacks* pd_cb = nullptr;
-    locks.withLoadersWriteLock([&] {
+    locks.withLoadersWriteLock(memoryManager, [&] {
       if (callbacks->version == 1) {
-          const auto* callbacks_v1 = (const PseudoDylibRegisterCallbacksV1*)callbacks;
+          const auto* callbacks_v1 = (const _dyld_pseudodylib_callbacks_v1*)callbacks;
           pd_cb = createPseudoDylibCallbacks(persistentAllocator, callbacks_v1->dispose_error_message,
                                              callbacks_v1->initialize, callbacks_v1->deinitialize,
                                              callbacks_v1->lookup_symbols, callbacks_v1->lookup_address,
-                                             callbacks_v1->find_unwind_sections, nullptr, nullptr);
+                                             callbacks_v1->find_unwind_sections, nullptr);
       } else if (callbacks->version == 2) {
-          const auto* callbacks_v2 = (const PseudoDylibRegisterCallbacksV2*)callbacks;
+          const auto* callbacks_v2 = (const _dyld_pseudodylib_callbacks_v2*)callbacks;
           pd_cb = createPseudoDylibCallbacks(persistentAllocator, callbacks_v2->dispose_string,
                                              callbacks_v2->initialize, callbacks_v2->deinitialize,
                                              callbacks_v2->lookup_symbols, callbacks_v2->lookup_address,
                                              callbacks_v2->find_unwind_sections,
-                                             callbacks_v2->loadable_at_path, nullptr);
-      } else if (callbacks->version == 3) {
-          const auto* callbacks_v3 = (const PseudoDylibRegisterCallbacksV3*)callbacks;
-          pd_cb = createPseudoDylibCallbacks(persistentAllocator, callbacks_v3->dispose_string,
-                                             callbacks_v3->initialize, callbacks_v3->deinitialize,
-                                             callbacks_v3->lookup_symbols, callbacks_v3->lookup_address,
-                                             callbacks_v3->find_unwind_sections,
-                                             callbacks_v3->loadable_at_path,
-                                             callbacks_v3->finalize_requested_symbols);
+                                             callbacks_v2->loadable_at_path);
       }
     });
 
@@ -3075,40 +2956,20 @@
     }
 
     return (_dyld_pseudodylib_callbacks_handle)pd_cb;
-#else
-    unavailable_on_exclavekit();
-#endif // !TARGET_OS_EXCLAVEKIT
 }
 
 void APIs::_dyld_pseudodylib_deregister_callbacks(_dyld_pseudodylib_callbacks_handle callbacks_handle) {
-#if !TARGET_OS_EXCLAVEKIT
-    if ( !this->config.security.allowDevelopmentVars ) {
-        if ( this->config.log.apis )
-            log("_dyld_pseudodylib_deregister_callbacks(): blocked by security policy");
-        return;
-    }
-
     if (!callbacks_handle)
         return;
-    locks.withLoadersWriteLock([&] {
+    locks.withLoadersWriteLock(memoryManager, [&] {
         persistentAllocator.free((PseudoDylibCallbacks*)callbacks_handle);
     });
-#else
-    unavailable_on_exclavekit();
-#endif // !TARGET_OS_EXCLAVEKIT
 }
 
 _dyld_pseudodylib_handle APIs::_dyld_pseudodylib_register(
         void* addr, size_t size, _dyld_pseudodylib_callbacks_handle callbacks_handle, void* context) {
-#if !TARGET_OS_EXCLAVEKIT
-    if ( !this->config.security.allowDevelopmentVars ) {
-        if ( this->config.log.apis )
-            log("_dyld_pseudodylib_register() => nullptr: blocked by security policy");
-        return nullptr;
-    }
-
-    const Header* pseudoDylibHdr = (const Header*)addr;
-    const char* path = pseudoDylibHdr->installName();
+    const MachOFile* pseudoDylibMF = (const MachOFile*)addr;
+    const char* path = pseudoDylibMF->installName();
 
     if (!path) {
         if ( config.log.apis ) {
@@ -3124,7 +2985,7 @@
 
     _dyld_pseudodylib_handle result = nullptr;
     PseudoDylib* existingPD = nullptr;
-    locks.withLoadersWriteLock([&] {
+    locks.withLoadersWriteLock(memoryManager, [&] {
         for (auto &pd : pseudoDylibs)
             if (strcmp(pd->getIdentifier(), path) == 0) {
                 existingPD = pd;
@@ -3146,24 +3007,21 @@
     }
 
     return result;
-#else
-    unavailable_on_exclavekit();
-#endif // !TARGET_OS_EXCLAVEKIT
 }
 
 void APIs::_dyld_pseudodylib_deregister(_dyld_pseudodylib_handle pd_handle) {
-    const PseudoDylib* pd = (PseudoDylib*)pd_handle;
+    PseudoDylib* pd = (PseudoDylib*)pd_handle;
 
     if ( config.log.apis )
         log("_dyld_deregister_pseudodylib(<handle for \"%s\">)\n", pd->getIdentifier());
 
     bool found = false;
-    locks.withLoadersWriteLock([&] {
+    locks.withLoadersWriteLock(memoryManager, [&] {
         for (auto it = pseudoDylibs.begin(); it != pseudoDylibs.end(); ++it)
             if (*it == pd) {
                 found = true;
                 pseudoDylibs.erase(it);
-                persistentAllocator.free((void*)pd);
+                persistentAllocator.free(pd);
                 break;
             }
     });
@@ -3176,7 +3034,7 @@
 
 const mach_header* APIs::_dyld_get_prog_image_header()
 {
-    const mach_header* result = config.process.mainExecutableMF;
+    const mach_header* result = config.process.mainExecutable;
     if ( config.log.apis )
         log("_dyld_get_prog_image_header() => %p\n", result);
     return result;
@@ -3192,10 +3050,8 @@
 
 bool APIs::_dyld_is_objc_constant(DyldObjCConstantKind kind, const void* addr)
 {
-#if !TARGET_OS_EXCLAVEKIT
     if ( config.log.apis )
         log("_dyld_is_objc_constant(%d, %p)\n", kind, addr);
-#endif // !TARGET_OS_EXCLAVEKIT
     // FIXME
     return false;
 }
@@ -3236,7 +3092,7 @@
     if ( config.log.apis )
         log("_dyld_register_driverkit_main(%p)\n", mainFunc);
 
-    if ( config.process.platform == Platform::driverKit ) {
+    if ( config.process.platform == dyld3::Platform::driverKit ) {
 #if BUILDING_DYLD
         if ( this->mainFunc() != nullptr )
             halt("_dyld_register_driverkit_main() may only be called once");
@@ -3283,11 +3139,46 @@
     return false;
 }
 
-bool APIs::_dyld_dlsym_blocked()
-{
-    return config.security.dlsymBlocked;
-}
-
+void APIs::_dyld_missing_symbol_abort()
+{
+#if BUILDING_DYLD
+    halt("missing symbol called");
+#endif
+}
+
+void APIs::_tlv_atexit(void (*termFunc)(void* objAddr), void* objAddr)
+{
+#if !TARGET_OS_EXCLAVEKIT
+  #if __has_feature(tls)
+    addTLVTerminationFunc(termFunc, objAddr);
+  #endif
+#else
+    unavailable_on_exclavekit();
+#endif // !TARGET_OS_EXCLAVEKIT
+}
+
+// called by exit() before it calls cxa_finalize() so that thread_local
+// objects are destroyed before global objects.
+void APIs::_tlv_exit()
+{
+#if !TARGET_OS_EXCLAVEKIT
+  #if __has_feature(tls)
+    exitTLV();
+  #endif
+#else
+    unavailable_on_exclavekit();
+#endif // !TARGET_OS_EXCLAVEKIT
+}
+
+#if __has_feature(tls)
+// linked images with TLV have references to this symbol, but it is never used at runtime
+void APIs::_tlv_bootstrap()
+{
+#if BUILDING_DYLD
+    halt("_tlv_bootstrap called");
+#endif
+}
+#endif
 
 void APIs::obsolete()
 {
@@ -3317,9 +3208,9 @@
         return NSObjectFileImageFailure;
 
     // create ofi that just contains path. NSLinkModule does all the work (can't use operator new in dyld)
-    void*                storage = this->libSystemHelpers.malloc(sizeof(__NSObjectFileImage));
+    void*                storage = this->libSystemHelpers->malloc(sizeof(__NSObjectFileImage));
     __NSObjectFileImage* result  = new (storage) __NSObjectFileImage();
-    result->path                 = (char*)this->libSystemHelpers.malloc(strlen(path)+1);
+    result->path                 = (char*)this->libSystemHelpers->malloc(strlen(path)+1);
     strcpy((char*)(result->path), path);
     *ofi                        = result;
 
@@ -3335,35 +3226,41 @@
     if ( config.log.apis )
         log("NSCreateObjectFileImageFromMemory(%p, 0x%08lX)\n", memImage, memImageSize);
     // sanity check the buffer is a mach-o file
-    std::span<const uint8_t> content = { (const uint8_t*)memImage, memImageSize };
-    const Header* hdr = nullptr;
-    
+    __block Diagnostics diag;
+
     // check if it is current arch mach-o or fat with slice for current arch
-    if ( const Universal* uni = Universal::isUniversal(content) ) {
-        Universal::Slice slice;
-        if ( uni->bestSlice(*config.process.archs, false, slice) )
-            hdr = Header::isMachO(slice.buffer);
-        
-    } else {
-        hdr = Header::isMachO(content);
-    }
-    
-    if ( hdr == nullptr )
+    bool             usable = false;
+    const MachOFile* mf     = (MachOFile*)memImage;
+    if ( mf->hasMachOMagic() && mf->isMachO(diag, memImageSize) ) {
+        usable = (config.process.archs->grade(mf->cputype, mf->cpusubtype, false) != 0);
+    }
+    else if ( const dyld3::FatFile* ff = dyld3::FatFile::isFatFile(memImage) ) {
+        uint64_t sliceOffset;
+        uint64_t sliceLen;
+        bool     missingSlice;
+        if ( ff->isFatFileWithSlice(diag, memImageSize, *config.process.archs, false, sliceOffset, sliceLen, missingSlice) ) {
+            mf = (MachOFile*)((long)memImage + sliceOffset);
+            if ( mf->isMachO(diag, sliceLen) ) {
+                usable = true;
+            }
+        }
+    }
+    if ( usable ) {
+        if ( !mf->loadableIntoProcess(config.process.platform, "OFI", config.security.isInternalOS) )
+            usable = false;
+    }
+    if ( !usable ) {
         return NSObjectFileImageFailure;
-    
-    if ( ! config.process.archs->isCompatible(hdr->arch()) )
-        return NSObjectFileImageFailure;
-    
-    if ( !hdr->loadableIntoProcess(config.process.platform, "OFI", config.security.isInternalOS) )
-        return NSObjectFileImageFailure;
-    
+    }
+
     // this API can only be used with bundles
-    if ( !hdr->isBundle() )
+    if ( !mf->isBundle() ) {
         return NSObjectFileImageInappropriateFile;
+    }
 
     // some apps deallocate the buffer right after calling NSCreateObjectFileImageFromMemory(), so we need to copy the buffer
     vm_address_t newAddr = 0;
-    kern_return_t r = this->libSystemHelpers.vm_allocate(mach_task_self(), &newAddr, memImageSize, VM_FLAGS_ANYWHERE);
+    kern_return_t r = this->libSystemHelpers->vm_allocate(mach_task_self(), &newAddr, memImageSize, VM_FLAGS_ANYWHERE);
     if ( r == KERN_SUCCESS ) {
         ::memcpy((void*)newAddr, memImage, memImageSize);
         if ( config.log.apis )
@@ -3372,7 +3269,7 @@
     }
 
     // allocate ofi that just lists the memory range
-    void*                storage = this->libSystemHelpers.malloc(sizeof(__NSObjectFileImage));
+    void*                storage = this->libSystemHelpers->malloc(sizeof(__NSObjectFileImage));
     __NSObjectFileImage* result  = new (storage) __NSObjectFileImage();
     result->memSource            = memImage;
     result->memLength            = memImageSize;
@@ -3395,8 +3292,7 @@
         // make temp file with content of memory buffer
         ofi->path = nullptr;
         char        tempFileName[PATH_MAX];
-        // <rdar://115105325> prevent setuid programs from being exploited by a rogue $TMPDIR, they always use /tmp
-        const char* tmpDir = (config.security.allowClassicFallbackPaths ? this->libSystemHelpers.getenv("TMPDIR") : "/tmp/");
+        const char* tmpDir = this->libSystemHelpers->getenv("TMPDIR");
         if ( (tmpDir != nullptr) && (strlen(tmpDir) > 2) ) {
             strlcpy(tempFileName, tmpDir, PATH_MAX);
             if ( tmpDir[strlen(tmpDir) - 1] != '/' )
@@ -3405,11 +3301,11 @@
         else
             strlcpy(tempFileName, "/tmp/", PATH_MAX);
         strlcat(tempFileName, "NSCreateObjectFileImageFromMemory-XXXXXXXX", PATH_MAX);
-        int fd = this->libSystemHelpers.mkstemp(tempFileName);
+        int fd = this->libSystemHelpers->mkstemp(tempFileName);
         if ( fd != -1 ) {
             ssize_t writtenSize = ::pwrite(fd, ofi->memSource, ofi->memLength, 0);
             if ( writtenSize == ofi->memLength ) {
-                ofi->path = (char*)this->libSystemHelpers.malloc(strlen(tempFileName)+1);
+                ofi->path = (char*)this->libSystemHelpers->malloc(strlen(tempFileName)+1);
                 ::strcpy((char*)(ofi->path), tempFileName);
             }
             else {
@@ -3475,18 +3371,18 @@
         // if object was created from a memory, release that memory
         // NOTE: this is the way dyld has always done this. NSCreateObjectFileImageFromMemory() hands ownership of the memory to dyld
         // we don't know if memory came from malloc or vm_allocate, so ask malloc
-        if ( this->libSystemHelpers.malloc_size(ofi->memSource) != 0 )
-            this->libSystemHelpers.free((void*)(ofi->memSource));
+        if ( this->libSystemHelpers->malloc_size(ofi->memSource) != 0 )
+            this->libSystemHelpers->free((void*)(ofi->memSource));
         else
-            this->libSystemHelpers.vm_deallocate(mach_task_self(), (vm_address_t)ofi->memSource, ofi->memLength);
+            this->libSystemHelpers->vm_deallocate(mach_task_self(), (vm_address_t)ofi->memSource, ofi->memLength);
     }
 
     // ofi always owns the path
     if ( ofi->path != nullptr )
-        this->libSystemHelpers.free((void*)(ofi->path));
+        this->libSystemHelpers->free((void*)(ofi->path));
 
     // free object
-    this->libSystemHelpers.free((void*)ofi);
+    this->libSystemHelpers->free((void*)ofi);
 
     return true;
 #else
@@ -3550,7 +3446,7 @@
         log("NSNameOfModule(%p)\n", m);
     bool firstOnly;
     if ( const Loader* ldr = loaderFromHandle(m, firstOnly) )
-        return ldr->path(*this);
+        return ldr->path();
     return nullptr;
 #else
     obsolete();
@@ -3564,7 +3460,7 @@
         log("NSLibraryNameForModule(%p)\n", m);
     bool firstOnly;
     if ( const Loader* ldr = loaderFromHandle(m, firstOnly) )
-        return ldr->path(*this);
+        return ldr->path();
     return nullptr;
 #else
     obsolete();
@@ -3626,9 +3522,6 @@
 NSSymbol APIs::NSLookupAndBindSymbol(const char* symbolName)
 {
 #if TARGET_OS_OSX && !TARGET_OS_EXCLAVEKIT
-    // allow apps to disable dlsym()
-    if ( addressLookupsDisabled() )
-        return nullptr;
     const mach_header* foundInImageAtLoadAddress;
     void*              symbolAddress;
     if ( flatFindSymbol(symbolName, &symbolAddress, &foundInImageAtLoadAddress) ) {
@@ -3643,9 +3536,6 @@
 NSSymbol APIs::NSLookupAndBindSymbolWithHint(const char* symbolName, const char* libraryNameHint)
 {
 #if TARGET_OS_OSX && !TARGET_OS_EXCLAVEKIT
-    // allow apps to disable dlsym()
-    if ( addressLookupsDisabled() )
-        return nullptr;
     const mach_header* foundInImageAtLoadAddress;
     void*              symbolAddress;
     if ( flatFindSymbol(symbolName, &symbolAddress, &foundInImageAtLoadAddress) ) {
@@ -3662,9 +3552,6 @@
 #if TARGET_OS_OSX && !TARGET_OS_EXCLAVEKIT
     if ( config.log.apis )
         log("NSLookupSymbolInModule(%p, %s)\n", module, symbolName);
-    // allow apps to disable dlsym()
-    if ( addressLookupsDisabled() )
-        return nullptr;
     bool firstOnly;
     if ( const Loader* ldr = loaderFromHandle(module, firstOnly) ) {
         if ( validLoader(ldr) ) {
@@ -3705,9 +3592,6 @@
 NSSymbol APIs::NSLookupSymbolInImage(const mach_header* mh, const char* symbolName, uint32_t options)
 {
 #if TARGET_OS_OSX && !TARGET_OS_EXCLAVEKIT
-    // allow apps to disable dlsym()
-    if ( addressLookupsDisabled() )
-        return nullptr;
     void* addr;
     bool  resultPointsToInstructions = false;
     if ( ((MachOLoaded*)mh)->hasExportedSymbol(symbolName, nullptr, &addr, &resultPointsToInstructions) ) {
@@ -3723,63 +3607,9 @@
 #endif // TARGET_OS_OSX
 }
 
-// symbolName=nullptr blocks all uses
-bool APIs::addressLookupsDisabled(const char* symbolName) const
-{
-    // check if there is an allow-list and symbol is in it
-    // note: performance is not important. dlsym is already slow
-    if ( (config.security.dlsymAllowList != nullptr) && (symbolName != nullptr) ) {
-        // coarse check that symbolName appears anywhere in list
-        if ( const char* inList = ::strstr(config.security.dlsymAllowList, symbolName) ) {
-            // symbolName string is in list, verify it has delimiters on both sides
-            char pre  = inList[-1];
-            char post = inList[strlen(symbolName)];
-            if ( (post == '\0') || (post == ':') ) {
-                if ( pre == ':' ) {
-                    // allow this symbol to be looked up
-                    return false;
-                }
-            }
-        }
-        // have an allow-list, and symbolName not in it
-        if ( DlsymNotify notify = this->dlsymNotifier() ) {
-            notify(symbolName);
-        }
-    }
-    else if ( !config.security.dlsymBlocked ) {
-        // program is not using dlsym-blocking or allow-list, so it will be notified about all dlsym usages
-        if ( DlsymNotify notify = this->dlsymNotifier() )
-            notify(symbolName);
-    }
-
-    // allow apps to disable dlsym()
-    if ( config.security.dlsymBlocked ) {
-         // either abort
-        if ( config.security.dlsymAbort ) {
-#if BUILDING_DYLD
-            StructuredError errInfo;
-            errInfo.kind              = DYLD_EXIT_REASON_DLSYM_BLOCKED;
-            errInfo.clientOfDylibPath = nullptr;
-            errInfo.targetDylibPath   = nullptr;
-            errInfo.symbolName        = symbolName;
-            halt("symbol address lookup (dlsym) disabled in process", &errInfo);
-#elif BUILDING_UNIT_TESTS
-            abort();
-#endif
-        }
-        return true;
-    }
-    return false;
-}
-
-
 void* APIs::NSAddressOfSymbol(NSSymbol symbol)
 {
 #if TARGET_OS_OSX && !TARGET_OS_EXCLAVEKIT
-    // allow apps to disable dlsym()
-    if ( addressLookupsDisabled() )
-        return nullptr;
-
     // special case NULL
     if ( symbol == nullptr )
         return nullptr;
@@ -3790,14 +3620,13 @@
     #if __has_feature(ptrauth_calls)
     const MachOLoaded* ml;
     if ( findImageMappedAt(result, &ml) ) {
-        const Header* hdr = (const Header*)ml;
-        int64_t      slide                      = hdr->getSlide();
+        int64_t      slide                      = ml->getSlide();
         __block bool resultPointsToInstructions = false;
-        hdr->forEachSection(^(const Header::SectionInfo& sectInfo, bool& stop) {
-            uint64_t sectStartAddr = sectInfo.address + slide;
-            uint64_t sectEndAddr   = sectStartAddr + sectInfo.size;
+        ml->forEachSection(^(const MachOAnalyzer::SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
+            uint64_t sectStartAddr = sectInfo.sectAddr + slide;
+            uint64_t sectEndAddr   = sectStartAddr + sectInfo.sectSize;
             if ( ((uint64_t)result >= sectStartAddr) && ((uint64_t)result < sectEndAddr) ) {
-                resultPointsToInstructions = (sectInfo.flags & S_ATTR_PURE_INSTRUCTIONS) || (sectInfo.flags & S_ATTR_SOME_INSTRUCTIONS);
+                resultPointsToInstructions = (sectInfo.sectFlags & S_ATTR_PURE_INSTRUCTIONS) || (sectInfo.sectFlags & S_ATTR_SOME_INSTRUCTIONS);
                 stop                       = true;
             }
         });
@@ -3999,11 +3828,14 @@
     if (!libSystemInitialized()) {
         const_cast<Loader*>(this->libSystemLoader)->beginInitializers(*this);
         this->libSystemLoader->runInitializers(*this);
-        this->setLibSystemInitialized();
     }
 
 #if HAS_EXTERNAL_STATE
-    this->externallyViewable->setLibSystemInitialized();
+#if TARGET_OS_EXCLAVEKIT
+    this->externallyViewable.setLibSystemInitializedOld();
+#else
+    this->externallyViewable.setLibSystemInitialized();
+#endif // TARGET_OS_EXCLAVEKIT
 #endif /* HAS_EXTERNAL_STATE */
 
     // after running libSystem's initializer, tell objc to run any +load methods on libSystem sub-dylibs
@@ -4012,7 +3844,7 @@
     // Iterate using indices so that the array doesn't grow underneath us if a +load dloopen's
     for ( uint32_t i = 0; i != this->loaded.size(); ++i ) {
         const Loader* ldr = this->loaded[i];
-        if ( ldr->belowLibSystem ) {
+        if ( (ldr->dylibInDyldCache || ldr->analyzer(*this)->isDylib()) && (strncmp(ldr->analyzer(*this)->installName(), "/usr/lib/system/lib", 19) == 0) ) {
             // check install name instead of path, to handle DYLD_LIBRARY_PATH overrides of libsystem sub-dylibs
             const_cast<Loader*>(ldr)->beginInitializers(*this);
             this->notifyObjCInit(ldr);
@@ -4022,8 +3854,8 @@
 
 #if TARGET_OS_OSX && !TARGET_OS_EXCLAVEKIT
     // If we're PID 1, scan for roots
-    if ( (this->config.process.pid == 1) && (this->libSystemHelpers.version() >= 5) ) {
-        this->libSystemHelpers.run_async(&ProcessConfig::scanForRoots, (void*)&this->config);
+    if ( (this->config.process.pid == 1) && (this->libSystemHelpers->version() >= 5) ) {
+        this->libSystemHelpers->run_async(&ProcessConfig::scanForRoots, (void*)&this->config);
     }
 #endif // TARGET_OS_OSX
 
@@ -4040,203 +3872,4 @@
 }
 
 
-void APIs::_dyld_register_dlsym_notifier(DlsymNotify callback)
-{
-#if !TARGET_OS_EXCLAVEKIT
-    locks.withNotifiersWriteLock(^{
-        // only support one notifier being registered
-        this->setDlsymNotifier(callback);
-    });
-#endif
-}
-
-const void* APIs::_dyld_get_swift_prespecialized_data()
-{
-#if TARGET_OS_EXCLAVEKIT
-    return nullptr;
-#else
-    if ( config.process.commPage.disableSwiftPrespecData )
-        return nullptr;
-
-    if ( config.log.apis )
-        log("_dyld_get_swift_prespecialized_data()\n");
-
-    if ( !config.dyldCache.addr )
-        return nullptr;
-
-    const SwiftOptimizationHeader* swiftOpt = config.dyldCache.addr->swiftOpt();
-    if ( !swiftOpt || (swiftOpt->version < 2) )
-        return nullptr;
-
-    if ( swiftOpt->prespecializationDataCacheOffset == 0 )
-        return nullptr;
-
-    void* result = (char*)config.dyldCache.addr + swiftOpt->prespecializationDataCacheOffset;
-    if ( config.log.apis )
-        log("_dyld_get_swift_prespecialized_data() => %p\n", result);
-    return result;
-#endif
-}
-
-bool APIs::_dyld_is_pseudodylib(void* handle)
-{
-#if TARGET_OS_EXCLAVEKIT
-    return false;
-#else
-    if ( config.log.apis )
-        log("_dyld_is_pseudodylib(%p)\n", handle);
-
-    bool          firstOnly;
-    const Loader* ldr = loaderFromHandle(handle, firstOnly);
-    if ( !validLoader(ldr) ) {
-        // if an invalid 'handle` passed in, return false
-        return false;
-    }
-
-    bool result = false;
-    if ( const JustInTimeLoader* jitLoader = ldr->isJustInTimeLoader() )
-        result = (jitLoader->pseudoDylib() != nullptr);
-
-    return result;
-#endif // TARGET_OS_EXCLAVEKIT
-}
-
-const void* APIs::_dyld_find_pointer_hash_table_entry(const void *table, const void *key1,
-                                                      size_t restKeysCount, const void **restKeys)
-{
-#if TARGET_OS_EXCLAVEKIT
-    return nullptr;
-#else
-
-    if ( config.log.apis )
-        log("_dyld_find_pointer_hash_table_entry(%p, %p, %lu, %p)\n", table, key1, restKeysCount, restKeys);
-
-    if ( !config.dyldCache.addr )
-        return nullptr;
-
-    const SwiftOptimizationHeader* swiftOpt = config.dyldCache.addr->swiftOpt();
-    if ( !swiftOpt || (swiftOpt->version < 3) )
-        return nullptr;
-
-    const SwiftHashTable* ptrTable = nullptr;
-    for ( uint64_t ptrTableOffset : swiftOpt->prespecializedMetadataHashTableCacheOffsets ) {
-        // end of tables
-        if ( ptrTableOffset == 0 )
-            break;
-
-        const SwiftHashTable* thisTable = (const SwiftHashTable*)((uint8_t*)config.dyldCache.addr + ptrTableOffset);
-        if ( (void*)thisTable == table ) {
-            ptrTable = thisTable;
-            break;
-        }
-    }
-
-    if ( ptrTable == nullptr ) {
-        if ( config.log.apis )
-            log("_dyld_find_pointer_hash_table_entry() invalid table pointer %p\n", table);
-        return nullptr;
-
-    }
-    if ( swiftOpt->prespecializationDataCacheOffset == 0 )
-        return nullptr;
-
-    // fixed limit for the number key pointers
-    uint32_t nextKeyIndex = 0;
-    uint64_t tableKeys[PointerHashTableKeyMaxPointers];
-
-    // rest keys + key1
-    if ( restKeysCount >= PointerHashTableKeyMaxPointers ) {
-        if ( config.log.apis )
-            log("_dyld_find_pointer_hash_table_entry() exceeded key pointers limit: %lu\n", restKeysCount + 1);
-        return nullptr;
-    }
-
-    const DyldSharedCache* dyldCache = config.dyldCache.addr;
-    const uint8_t* dyldCacheStart = (uint8_t*)dyldCache;
-    const uint8_t* dyldCacheEnd = (uint8_t*)dyldCacheStart + dyldCache->mappedSize();
-
-    if ( key1 >= dyldCacheStart && key1 < dyldCacheEnd ) {
-        tableKeys[nextKeyIndex++] = (uint8_t*)key1-(uint8_t*)config.dyldCache.addr;
-    } else {
-        if ( config.log.apis )
-            log("_dyld_find_pointer_hash_table_entry() key %p not in shared cache\n", key1);
-        return nullptr;
-    }
-
-    for ( size_t i = 0; i < restKeysCount; ++i ) {
-        uint8_t* nextKey = (uint8_t*)restKeys[i];
-        if ( nextKey >= dyldCacheStart && nextKey < dyldCacheEnd ) {
-            tableKeys[nextKeyIndex++] = (uint8_t*)nextKey-(uint8_t*)config.dyldCache.addr;
-        } else {
-            if ( config.log.apis )
-                log("_dyld_find_pointer_hash_table_entry() key %p not in shared cache\n", nextKey);
-            return nullptr;
-        }
-    }
-
-    const void* result = nullptr;
-
-    PointerHashTableBuilderKey key;
-    key.cacheOffsets = tableKeys;
-    key.numOffsets   = nextKeyIndex;
-    const PointerHashTableValue* val = ptrTable->getValue<PointerHashTableBuilderKey, PointerHashTableValue>(key, nullptr);
-    if ( val )
-        result = dyldCacheStart + val->cacheOffset;
-
-    if ( config.log.apis )
-        log("_dyld_find_pointer_hash_table_entry() => %p\n", result);
-    return result;
-#endif
-}
-
-dyld_all_image_infos* APIs::_dyld_all_image_infos_TEMP()
-{
-#if HAS_EXTERNAL_STATE
-    return this->externallyViewable->getProcessInfo();
-#else
-    return nullptr;
-#endif
-}
-
-#if !TARGET_OS_EXCLAVEKIT
-DyldCommPage APIs::_dyld_commpage()
-{
-    return this->config.process.commPage;
-}
-#endif
-
-#if SUPPPORT_PRE_LC_MAIN
-MainFunc APIs::_dyld_get_main_func() {
-    return this->mainFunc();
-}
-#endif
-
-
-void APIs::_dyld_stack_range(const void** stackBottom, const void** stackTop)
-{
-    this->protectedStack().getRange(*stackBottom, *stackTop);
-}
-
-void APIs::_dyld_for_each_prewarming_range(PrewarmingDataFunc callback)
-{
-#if !TARGET_OS_EXCLAVEKIT
-    const DyldSharedCache* dyldCache = config.dyldCache.addr;
-    if ( dyldCache == nullptr )
-        return;
-
-    dyldCache->forEachPrewarmingEntry(^(const void *content, uint64_t unslidVMAddr, uint64_t vmSize) {
-        callback(content, (size_t)vmSize);
-    });
-#endif
-}
-
-void APIs::_dyld_call_with_writable_tpro_memory(void (*func)(void*), void* ctx) {
-    if (libSystemInitialized()) {
-        halt("Illegal TPRO memory access");
-    }
-    MemoryManager::withWritableMemory([&] {
-        func(ctx);
-    });
-}
-
 } // namespace