Loading...
common/MachOFile.cpp /dev/null dyld-941.4
--- /dev/null
+++ dyld/dyld-941.4/common/MachOFile.cpp
@@ -0,0 +1,1937 @@
+/*
+ * Copyright (c) 2017 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/fcntl.h>
+#include <unistd.h>
+#include <TargetConditionals.h>
+#include <mach/host_info.h>
+#include <mach/mach.h>
+#include <mach/mach_host.h>
+
+#include "Array.h"
+#include "MachOFile.h"
+#include "SupportedArchs.h"
+
+#if BUILDING_DYLD || BUILDING_LIBDYLD
+    // define away restrict until rdar://60166935 is fixed
+    #define restrict
+    #include <subsystem.h>
+#endif
+
+namespace dyld3 {
+
+////////////////////////////  posix wrappers ////////////////////////////////////////
+
+// <rdar://problem/10111032> wrap calls to stat() with check for EAGAIN
+int stat(const char* path, struct stat* buf)
+{
+    int result;
+    do {
+#if BUILDING_DYLD
+        result = ::stat_with_subsystem(path, buf);
+#else
+        result = ::stat(path, buf);
+#endif
+    } while ((result == -1) && ((errno == EAGAIN) || (errno == EINTR)));
+
+    return result;
+}
+
+// <rdar://problem/13805025> dyld should retry open() if it gets an EGAIN
+int open(const char* path, int flag, int other)
+{
+    int result;
+    do {
+#if BUILDING_DYLD
+        if (flag & O_CREAT)
+            result = ::open(path, flag, other);
+        else
+            result = ::open_with_subsystem(path, flag);
+#else
+        result = ::open(path, flag, other);
+#endif
+    } while ((result == -1) && ((errno == EAGAIN) || (errno == EINTR)));
+
+    return result;
+}
+
+
+////////////////////////////  FatFile ////////////////////////////////////////
+
+const FatFile* FatFile::isFatFile(const void* fileStart)
+{
+    const FatFile* fileStartAsFat = (FatFile*)fileStart;
+    if ( (fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC)) || (fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC_64)) )
+        return fileStartAsFat;
+    else
+        return nullptr;
+}
+
+bool FatFile::isValidSlice(Diagnostics& diag, uint64_t fileLen, uint32_t sliceIndex,
+                           uint32_t sliceCpuType, uint32_t sliceCpuSubType, uint64_t sliceOffset, uint64_t sliceLen) const {
+    if ( greaterThanAddOrOverflow(sliceOffset, sliceLen, fileLen) ) {
+        diag.error("slice %d extends beyond end of file", sliceIndex);
+        return false;
+    }
+    const dyld3::MachOFile* mf = (const dyld3::MachOFile*)((uint8_t*)this+sliceOffset);
+    if (!mf->isMachO(diag, sliceLen))
+        return false;
+    if ( mf->cputype != (cpu_type_t)sliceCpuType ) {
+        diag.error("cpu type in slice (0x%08X) does not match fat header (0x%08X)", mf->cputype, sliceCpuType);
+        return false;
+    }
+    else if ( (mf->cpusubtype & ~CPU_SUBTYPE_MASK) != (sliceCpuSubType & ~CPU_SUBTYPE_MASK) ) {
+        diag.error("cpu subtype in slice (0x%08X) does not match fat header (0x%08X)", mf->cpusubtype, sliceCpuSubType);
+        return false;
+    }
+    uint32_t pageSizeMask = mf->uses16KPages() ? 0x3FFF : 0xFFF;
+    if ( (sliceOffset & pageSizeMask) != 0 ) {
+        // slice not page aligned
+        if ( strncmp((char*)this+sliceOffset, "!<arch>", 7) == 0 )
+            diag.error("file is static library");
+        else
+            diag.error("slice is not page aligned");
+        return false;
+    }
+    return true;
+}
+
+void FatFile::forEachSlice(Diagnostics& diag, uint64_t fileLen, bool validate,
+                           void (^callback)(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop)) const
+{
+	if ( this->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) {
+        const uint64_t maxArchs = ((4096 - sizeof(fat_header)) / sizeof(fat_arch));
+        const uint32_t numArchs = OSSwapBigToHostInt32(nfat_arch);
+        if ( numArchs > maxArchs ) {
+            diag.error("fat header too large: %u entries", numArchs);
+            return;
+        }
+        bool stop = false;
+        const fat_arch* const archs = (fat_arch*)(((char*)this)+sizeof(fat_header));
+        for (uint32_t i=0; i < numArchs; ++i) {
+            uint32_t cpuType    = OSSwapBigToHostInt32(archs[i].cputype);
+            uint32_t cpuSubType = OSSwapBigToHostInt32(archs[i].cpusubtype);
+            uint32_t offset     = OSSwapBigToHostInt32(archs[i].offset);
+            uint32_t len        = OSSwapBigToHostInt32(archs[i].size);
+            Diagnostics sliceDiag;
+            if ( !validate || isValidSlice(sliceDiag, fileLen, i, cpuType, cpuSubType, offset, len) )
+                callback(cpuType, cpuSubType, (uint8_t*)this+offset, len, stop);
+            if ( stop )
+                break;
+            if ( sliceDiag.hasError() )
+                diag.appendError("%s, ", sliceDiag.errorMessageCStr());
+        }
+
+        // Look for one more slice
+        if ( numArchs != maxArchs ) {
+            uint32_t cpuType    = OSSwapBigToHostInt32(archs[numArchs].cputype);
+            uint32_t cpuSubType = OSSwapBigToHostInt32(archs[numArchs].cpusubtype);
+            uint32_t offset     = OSSwapBigToHostInt32(archs[numArchs].offset);
+            uint32_t len        = OSSwapBigToHostInt32(archs[numArchs].size);
+            if ((cpuType == CPU_TYPE_ARM64) && ((cpuSubType == CPU_SUBTYPE_ARM64_ALL || cpuSubType == CPU_SUBTYPE_ARM64_V8))) {
+                if ( !validate || isValidSlice(diag, fileLen, numArchs, cpuType, cpuSubType, offset, len) )
+                    callback(cpuType, cpuSubType, (uint8_t*)this+offset, len, stop);
+            }
+        }
+    }
+    else if ( this->magic == OSSwapBigToHostInt32(FAT_MAGIC_64) ) {
+        if ( OSSwapBigToHostInt32(nfat_arch) > ((4096 - sizeof(fat_header)) / sizeof(fat_arch)) ) {
+            diag.error("fat header too large: %u entries", OSSwapBigToHostInt32(nfat_arch));
+            return;
+        }
+        bool stop = false;
+        const fat_arch_64* const archs = (fat_arch_64*)(((char*)this)+sizeof(fat_header));
+        for (uint32_t i=0; i < OSSwapBigToHostInt32(nfat_arch); ++i) {
+            uint32_t cpuType    = OSSwapBigToHostInt32(archs[i].cputype);
+            uint32_t cpuSubType = OSSwapBigToHostInt32(archs[i].cpusubtype);
+            uint64_t offset     = OSSwapBigToHostInt64(archs[i].offset);
+            uint64_t len        = OSSwapBigToHostInt64(archs[i].size);
+            if ( !validate || isValidSlice(diag, fileLen, i, cpuType, cpuSubType, offset, len) )
+                callback(cpuType, cpuSubType, (uint8_t*)this+offset, len, stop);
+            if ( stop )
+                break;
+        }
+    }
+    else {
+        diag.error("not a fat file");
+    }
+}
+
+void FatFile::forEachSlice(Diagnostics& diag, uint64_t fileLen, void (^callback)(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop)) const
+{
+    forEachSlice(diag, fileLen, true, callback);
+}
+
+const char* FatFile::archNames(char strBuf[256]) const
+{
+    strBuf[0] = '\0';
+    Diagnostics   diag;
+    __block bool  needComma = false;
+    this->forEachSlice(diag, 0xFFFFFFFF, false, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop) {
+        if ( needComma )
+            strlcat(strBuf, ",", 256);
+        strlcat(strBuf, MachOFile::archName(sliceCpuType, sliceCpuSubType), 256);
+        needComma = true;
+    });
+    return strBuf;
+}
+
+bool FatFile::isFatFileWithSlice(Diagnostics& diag, uint64_t fileLen, const GradedArchs& archs, bool isOSBinary,
+                                 uint64_t& sliceOffset, uint64_t& sliceLen, bool& missingSlice) const
+{
+    missingSlice = false;
+    if ( (this->magic != OSSwapBigToHostInt32(FAT_MAGIC)) && (this->magic != OSSwapBigToHostInt32(FAT_MAGIC_64)) )
+        return false;
+
+    __block int bestGrade = 0;
+    forEachSlice(diag, fileLen, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop) {
+        if (int sliceGrade = archs.grade(sliceCpuType, sliceCpuSubType, isOSBinary)) {
+            if ( sliceGrade > bestGrade ) {
+                sliceOffset = (char*)sliceStart - (char*)this;
+                sliceLen    = sliceSize;
+                bestGrade   = sliceGrade;
+            }
+        }
+    });
+    if ( diag.hasError() )
+        return false;
+
+    if ( bestGrade == 0 )
+        missingSlice = true;
+
+    return (bestGrade != 0);
+}
+
+
+////////////////////////////  GradedArchs ////////////////////////////////////////
+
+
+#define GRADE_i386        CPU_TYPE_I386,       CPU_SUBTYPE_I386_ALL,    false
+#define GRADE_x86_64      CPU_TYPE_X86_64,     CPU_SUBTYPE_X86_64_ALL,  false
+#define GRADE_x86_64h     CPU_TYPE_X86_64,     CPU_SUBTYPE_X86_64_H,    false
+#define GRADE_armv7       CPU_TYPE_ARM,        CPU_SUBTYPE_ARM_V7,      false
+#define GRADE_armv7s      CPU_TYPE_ARM,        CPU_SUBTYPE_ARM_V7S,     false
+#define GRADE_armv7k      CPU_TYPE_ARM,        CPU_SUBTYPE_ARM_V7K,     false
+#define GRADE_arm64       CPU_TYPE_ARM64,      CPU_SUBTYPE_ARM64_ALL,   false
+#define GRADE_arm64e      CPU_TYPE_ARM64,      CPU_SUBTYPE_ARM64E,      false
+#define GRADE_arm64e_pb   CPU_TYPE_ARM64,      CPU_SUBTYPE_ARM64E,      true
+#define GRADE_arm64_32    CPU_TYPE_ARM64_32,   CPU_SUBTYPE_ARM64_32_V8, false
+
+const GradedArchs GradedArchs::i386              = { {{GRADE_i386,    1}} };
+const GradedArchs GradedArchs::x86_64            = { {{GRADE_x86_64,  1}} };
+const GradedArchs GradedArchs::x86_64h           = { {{GRADE_x86_64h, 2}, {GRADE_x86_64, 1}} };
+const GradedArchs GradedArchs::arm64             = { {{GRADE_arm64,   1}} };
+#if SUPPORT_ARCH_arm64e
+const GradedArchs GradedArchs::arm64e_keysoff    = { {{GRADE_arm64e,    2}, {GRADE_arm64, 1}} };
+const GradedArchs GradedArchs::arm64e_keysoff_pb = { {{GRADE_arm64e_pb, 2}, {GRADE_arm64, 1}} };
+const GradedArchs GradedArchs::arm64e            = { {{GRADE_arm64e,    1}} };
+const GradedArchs GradedArchs::arm64e_pb         = { {{GRADE_arm64e_pb, 1}} };
+#endif
+const GradedArchs GradedArchs::armv7             = { {{GRADE_armv7,   1}} };
+const GradedArchs GradedArchs::armv7s            = { {{GRADE_armv7s,  2}, {GRADE_armv7, 1}} };
+const GradedArchs GradedArchs::armv7k            = { {{GRADE_armv7k,  1}} };
+#if SUPPORT_ARCH_arm64_32
+const GradedArchs GradedArchs::arm64_32          = { {{GRADE_arm64_32, 1}} };
+#endif
+
+int GradedArchs::grade(uint32_t cputype, uint32_t cpusubtype, bool isOSBinary) const
+{
+    for (const CpuGrade* p = _orderedCpuTypes; p->type != 0; ++p) {
+        if ( (p->type == cputype) && (p->subtype == (cpusubtype & ~CPU_SUBTYPE_MASK)) ) {
+            if ( p->osBinary ) {
+                if ( isOSBinary )
+                    return p->grade;
+            }
+            else {
+                return p->grade;
+            }
+        }
+    }
+    return 0;
+}
+
+const char* GradedArchs::name() const
+{
+    return MachOFile::archName(_orderedCpuTypes[0].type, _orderedCpuTypes[0].subtype);
+}
+
+bool GradedArchs::checksOSBinary() const
+{
+    for (const CpuGrade* p = _orderedCpuTypes; p->type != 0; ++p) {
+        if ( p->osBinary )
+            return true;
+    }
+    return false;
+}
+
+#if __x86_64__
+static bool isHaswell()
+{
+    // FIXME: figure out a commpage way to check this
+    struct host_basic_info info;
+    mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
+    mach_port_t hostPort = mach_host_self();
+    kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
+    mach_port_deallocate(mach_task_self(), hostPort);
+    return (result == KERN_SUCCESS) && (info.cpu_subtype == CPU_SUBTYPE_X86_64_H);
+}
+#endif
+
+const GradedArchs& GradedArchs::forCurrentOS(bool keysOff, bool osBinariesOnly)
+{
+#if __arm64e__
+    if ( osBinariesOnly )
+        return (keysOff ? arm64e_keysoff_pb : arm64e_pb);
+    else
+        return (keysOff ? arm64e_keysoff : arm64e);
+#elif __ARM64_ARCH_8_32__
+    return arm64_32;
+#elif __arm64__
+    return arm64;
+#elif __ARM_ARCH_7K__
+    return armv7k;
+#elif __ARM_ARCH_7S__
+    return armv7s;
+#elif __ARM_ARCH_7A__
+    return armv7;
+#elif __x86_64__
+ #if TARGET_OS_SIMULATOR
+    return x86_64;
+  #else
+    return isHaswell() ? x86_64h : x86_64;
+  #endif
+#elif __i386__
+    return i386;
+#else
+    #error unknown platform
+#endif
+}
+
+const GradedArchs& GradedArchs::forName(const char* archName, bool keysOff)
+{
+    if (strcmp(archName, "x86_64h") == 0 )
+        return x86_64h;
+    else if (strcmp(archName, "x86_64") == 0 )
+        return x86_64;
+#if SUPPORT_ARCH_arm64e
+    else if (strcmp(archName, "arm64e") == 0 )
+        return keysOff ? arm64e_keysoff : arm64e;
+#endif
+    else if (strcmp(archName, "arm64") == 0 )
+        return arm64;
+    else if (strcmp(archName, "armv7k") == 0 )
+        return armv7k;
+    else if (strcmp(archName, "armv7s") == 0 )
+        return armv7s;
+    else if (strcmp(archName, "armv7") == 0 )
+        return armv7;
+#if SUPPORT_ARCH_arm64_32
+    else if (strcmp(archName, "arm64_32") == 0 )
+        return arm64_32;
+#endif
+    else if (strcmp(archName, "i386") == 0 )
+        return i386;
+    assert(0 && "unknown arch name");
+}
+
+
+
+////////////////////////////  MachOFile ////////////////////////////////////////
+
+
+const MachOFile::ArchInfo MachOFile::_s_archInfos[] = {
+    { "x86_64",   CPU_TYPE_X86_64,   CPU_SUBTYPE_X86_64_ALL  },
+    { "x86_64h",  CPU_TYPE_X86_64,   CPU_SUBTYPE_X86_64_H    },
+    { "i386",     CPU_TYPE_I386,     CPU_SUBTYPE_I386_ALL    },
+    { "arm64",    CPU_TYPE_ARM64,    CPU_SUBTYPE_ARM64_ALL   },
+#if SUPPORT_ARCH_arm64e
+    { "arm64e",   CPU_TYPE_ARM64,    CPU_SUBTYPE_ARM64E     },
+#endif
+#if SUPPORT_ARCH_arm64_32
+    { "arm64_32", CPU_TYPE_ARM64_32, CPU_SUBTYPE_ARM64_32_V8 },
+#endif
+    { "armv7k",   CPU_TYPE_ARM,      CPU_SUBTYPE_ARM_V7K     },
+    { "armv7s",   CPU_TYPE_ARM,      CPU_SUBTYPE_ARM_V7S     },
+    { "armv7",    CPU_TYPE_ARM,      CPU_SUBTYPE_ARM_V7      }
+};
+
+const MachOFile::PlatformInfo MachOFile::_s_platformInfos[] = {
+    { "macOS",       Platform::macOS,             LC_VERSION_MIN_MACOSX   },
+    { "iOS",         Platform::iOS,               LC_VERSION_MIN_IPHONEOS },
+    { "tvOS",        Platform::tvOS,              LC_VERSION_MIN_TVOS     },
+    { "watchOS",     Platform::watchOS,           LC_VERSION_MIN_WATCHOS  },
+    { "bridgeOS",    Platform::bridgeOS,          LC_BUILD_VERSION        },
+    { "MacCatalyst", Platform::iOSMac,            LC_BUILD_VERSION        },
+    { "iOS-sim",     Platform::iOS_simulator,     LC_BUILD_VERSION        },
+    { "tvOS-sim",    Platform::tvOS_simulator,    LC_BUILD_VERSION        },
+    { "watchOS-sim", Platform::watchOS_simulator, LC_BUILD_VERSION        },
+    { "driverKit",   Platform::driverKit,         LC_BUILD_VERSION        },
+};
+
+
+
+bool MachOFile::is64() const
+{
+    return (this->magic == MH_MAGIC_64);
+}
+
+size_t MachOFile::machHeaderSize() const
+{
+    return is64() ? sizeof(mach_header_64) : sizeof(mach_header);
+}
+
+uint32_t MachOFile::maskedCpuSubtype() const
+{
+    return (this->cpusubtype & ~CPU_SUBTYPE_MASK);
+}
+
+uint32_t MachOFile::pointerSize() const
+{
+    if (this->magic == MH_MAGIC_64)
+        return 8;
+    else
+        return 4;
+}
+
+bool MachOFile::uses16KPages() const
+{
+    switch (this->cputype) {
+        case CPU_TYPE_ARM64:
+        case CPU_TYPE_ARM64_32:
+            return true;
+        case CPU_TYPE_ARM:
+            // iOS is 16k aligned for armv7/armv7s and watchOS armv7k is 16k aligned
+            return this->cpusubtype == CPU_SUBTYPE_ARM_V7K;
+        default:
+            return false;
+    }
+}
+
+bool MachOFile::isArch(const char* aName) const
+{
+    return (strcmp(aName, archName(this->cputype, this->cpusubtype)) == 0);
+}
+
+const char* MachOFile::archName(uint32_t cputype, uint32_t cpusubtype)
+{
+    for (const ArchInfo& info : _s_archInfos) {
+        if ( (cputype == info.cputype) && ((cpusubtype & ~CPU_SUBTYPE_MASK) == info.cpusubtype) ) {
+            return info.name;
+        }
+    }
+    return "unknown";
+}
+
+uint32_t MachOFile::cpuTypeFromArchName(const char* archName)
+{
+    for (const ArchInfo& info : _s_archInfos) {
+        if ( strcmp(archName, info.name) == 0 ) {
+            return info.cputype;
+        }
+    }
+    return 0;
+}
+
+uint32_t MachOFile::cpuSubtypeFromArchName(const char* archName)
+{
+    for (const ArchInfo& info : _s_archInfos) {
+        if ( strcmp(archName, info.name) == 0 ) {
+            return info.cpusubtype;
+        }
+    }
+    return 0;
+}
+
+const char* MachOFile::archName() const
+{
+    return archName(this->cputype, this->cpusubtype);
+}
+
+static void appendDigit(char*& s, unsigned& num, unsigned place, bool& startedPrinting)
+{
+    if ( num >= place ) {
+        unsigned dig = (num/place);
+        *s++ = '0' + dig;
+        num -= (dig*place);
+        startedPrinting = true;
+    }
+    else if ( startedPrinting ) {
+        *s++ = '0';
+    }
+}
+
+static void appendNumber(char*& s, unsigned num)
+{
+    assert(num < 99999);
+    bool startedPrinting = false;
+    appendDigit(s, num, 10000, startedPrinting);
+    appendDigit(s, num,  1000, startedPrinting);
+    appendDigit(s, num,   100, startedPrinting);
+    appendDigit(s, num,    10, startedPrinting);
+    appendDigit(s, num,     1, startedPrinting);
+    if ( !startedPrinting )
+        *s++ = '0';
+}
+
+void MachOFile::packedVersionToString(uint32_t packedVersion, char versionString[32])
+{
+    // sprintf(versionString, "%d.%d.%d", (packedVersion >> 16), ((packedVersion >> 8) & 0xFF), (packedVersion & 0xFF));
+    char* s = versionString;
+    appendNumber(s, (packedVersion >> 16));
+    *s++ = '.';
+    appendNumber(s, (packedVersion >> 8) & 0xFF);
+    *s++ = '.';
+    appendNumber(s, (packedVersion & 0xFF));
+    *s++ = '\0';
+}
+
+bool MachOFile::builtForPlatform(Platform reqPlatform, bool onlyOnePlatform) const
+{
+    __block bool foundRequestedPlatform = false;
+    __block bool foundOtherPlatform     = false;
+    forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
+        if ( platform == reqPlatform )
+            foundRequestedPlatform = true;
+        else
+            foundOtherPlatform = true;
+    });
+    // if checking that this binary is built for exactly one platform, fail if more
+    if ( foundOtherPlatform && onlyOnePlatform )
+        return false;
+    if ( foundRequestedPlatform )
+        return true;
+
+    // binary has no explict load command to mark platform
+    // could be an old macOS binary, look at arch
+    if  ( !foundOtherPlatform && (reqPlatform == Platform::macOS) ) {
+        if ( this->cputype == CPU_TYPE_X86_64 )
+            return true;
+        if ( this->cputype == CPU_TYPE_I386 )
+            return true;
+    }
+
+#if BUILDING_DYLDINFO
+    // Allow offline tools to analyze binaries dyld doesn't load, ie, those with platforms
+    if ( !foundOtherPlatform && (reqPlatform == Platform::unknown) )
+        return true;
+#endif
+
+    return false;
+}
+
+bool MachOFile::loadableIntoProcess(Platform processPlatform, const char* path) const
+{
+    if ( this->builtForPlatform(processPlatform) )
+        return true;
+
+    // Some host macOS dylibs can be loaded into simulator processes
+    if ( MachOFile::isSimulatorPlatform(processPlatform) && this->builtForPlatform(Platform::macOS)) {
+        static const char* const macOSHost[] = {
+            "/usr/lib/system/libsystem_kernel.dylib",
+            "/usr/lib/system/libsystem_platform.dylib",
+            "/usr/lib/system/libsystem_pthread.dylib",
+            "/usr/lib/system/libsystem_platform_debug.dylib",
+            "/usr/lib/system/libsystem_pthread_debug.dylib",
+            "/usr/lib/system/host/liblaunch_sim.dylib",
+        };
+        for (const char* libPath : macOSHost) {
+            if (strcmp(libPath, path) == 0)
+                return true;
+        }
+    }
+
+    // If this is being called on main executable where we expect a macOS program, Catalyst programs are also runnable
+    if ( (this->filetype == MH_EXECUTE) && (processPlatform == Platform::macOS) && this->builtForPlatform(Platform::iOSMac, true) )
+        return true;
+#if (TARGET_OS_OSX && TARGET_CPU_ARM64)
+    if ( (this->filetype == MH_EXECUTE) && (processPlatform == Platform::macOS) && this->builtForPlatform(Platform::iOS, true) )
+        return true;
+#endif
+
+    bool iOSonMac = (processPlatform == Platform::iOSMac);
+#if (TARGET_OS_OSX && TARGET_CPU_ARM64)
+    // allow iOS binaries in iOSApp
+    if ( processPlatform == Platform::iOS ) {
+        // can load Catalyst binaries into iOS process
+        if ( this->builtForPlatform(Platform::iOSMac) )
+            return true;
+        iOSonMac = true;
+    }
+#endif
+    // macOS dylibs can be loaded into iOSMac processes
+    if ( (iOSonMac) && this->builtForPlatform(Platform::macOS, true) )
+        return true;
+
+
+    return false;
+}
+
+bool MachOFile::isZippered() const
+{
+    __block bool macOS = false;
+    __block bool iOSMac = false;
+    forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
+        if ( platform == Platform::macOS )
+            macOS = true;
+        else if ( platform == Platform::iOSMac )
+            iOSMac = true;
+    });
+    return macOS && iOSMac;
+}
+
+bool MachOFile::inDyldCache() const {
+    return (this->flags & MH_DYLIB_IN_CACHE);
+}
+
+Platform MachOFile::currentPlatform()
+{
+#if TARGET_OS_SIMULATOR
+  #if TARGET_OS_WATCH
+    return Platform::watchOS_simulator;
+  #elif TARGET_OS_TV
+    return Platform::tvOS_simulator;
+  #else
+    return Platform::iOS_simulator;
+  #endif
+#elif TARGET_OS_BRIDGE
+    return Platform::bridgeOS;
+#elif TARGET_OS_WATCH
+    return Platform::watchOS;
+#elif TARGET_OS_TV
+    return Platform::tvOS;
+#elif TARGET_OS_IOS
+    return Platform::iOS;
+#elif TARGET_OS_OSX
+    return Platform::macOS;
+#elif TARGET_OS_DRIVERKIT
+    return Platform::driverKit;
+#else
+    #error unknown platform
+#endif
+}
+
+Platform MachOFile::basePlatform(dyld3::Platform reqPlatform) {
+    switch(reqPlatform) {
+        case Platform::unknown:               return Platform::unknown;
+        case Platform::macOS:                 return Platform::macOS;
+        case Platform::iOS:                   return Platform::iOS;
+        case Platform::tvOS:                  return Platform::tvOS;
+        case Platform::watchOS:               return Platform::watchOS;
+        case Platform::bridgeOS:              return Platform::bridgeOS;
+        case Platform::iOSMac:                return Platform::iOS;
+        case Platform::iOS_simulator:         return Platform::iOS;
+        case Platform::tvOS_simulator:        return Platform::tvOS;
+        case Platform::watchOS_simulator:     return Platform::watchOS;
+        case Platform::driverKit:             return Platform::driverKit;
+        default:                              return Platform::unknown;
+    }
+}
+
+
+const char* MachOFile::currentArchName()
+{
+#if __ARM_ARCH_7K__
+    return "armv7k";
+#elif __ARM_ARCH_7A__
+    return "armv7";
+#elif __ARM_ARCH_7S__
+    return "armv7s";
+#elif __arm64e__
+    return "arm64e";
+#elif __arm64__
+#if __LP64__
+    return "arm64";
+#else
+    return "arm64_32";
+#endif
+#elif __x86_64__
+    return isHaswell() ? "x86_64h" : "x86_64";
+#elif __i386__
+    return "i386";
+#else
+    #error unknown arch
+#endif
+}
+
+bool MachOFile::isSimulatorPlatform(Platform platform, Platform* basePlatform)
+{
+    switch ( platform ) {
+        case Platform::iOS_simulator:
+            if ( basePlatform )
+                *basePlatform = Platform::iOS;
+            return true;
+        case Platform::watchOS_simulator:
+            if ( basePlatform )
+                *basePlatform = Platform::watchOS;
+            return true;
+        case Platform::tvOS_simulator:
+            if ( basePlatform )
+                *basePlatform = Platform::tvOS;
+            return true;
+       default:
+            return false;
+    }
+}
+
+bool MachOFile::isDyldManaged() const {
+    switch ( this->filetype ) {
+        case MH_BUNDLE:
+        case MH_EXECUTE:
+        case MH_DYLIB:
+            return true;
+        default:
+            break;
+    }
+    return false;
+}
+
+bool MachOFile::isDylib() const
+{
+    return (this->filetype == MH_DYLIB);
+}
+
+bool MachOFile::isBundle() const
+{
+    return (this->filetype == MH_BUNDLE);
+}
+
+bool MachOFile::isMainExecutable() const
+{
+    return (this->filetype == MH_EXECUTE);
+}
+
+bool MachOFile::isDynamicExecutable() const
+{
+    if ( this->filetype != MH_EXECUTE )
+        return false;
+
+    // static executables do not have dyld load command
+    return hasLoadCommand(LC_LOAD_DYLINKER);
+}
+
+bool MachOFile::isStaticExecutable() const
+{
+    if ( this->filetype != MH_EXECUTE )
+        return false;
+
+    // static executables do not have dyld load command
+    return !hasLoadCommand(LC_LOAD_DYLINKER);
+}
+
+bool MachOFile::isKextBundle() const
+{
+    return (this->filetype == MH_KEXT_BUNDLE);
+}
+
+bool MachOFile::isFileSet() const
+{
+    return (this->filetype == MH_FILESET);
+}
+
+bool MachOFile::isPIE() const
+{
+    return (this->flags & MH_PIE);
+}
+
+bool MachOFile::isPreload() const
+{
+    return (this->filetype == MH_PRELOAD);
+}
+
+const char* MachOFile::platformName(Platform reqPlatform)
+{
+    for (const PlatformInfo& info : _s_platformInfos) {
+        if ( info.platform == reqPlatform )
+            return info.name;
+    }
+    return "unknown";
+}
+
+void MachOFile::forEachSupportedPlatform(void (^handler)(Platform platform, uint32_t minOS, uint32_t sdk)) const
+{
+    Diagnostics diag;
+    __block bool foundPlatform = false;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+        const build_version_command* buildCmd = (build_version_command *)cmd;
+        const version_min_command*   versCmd  = (version_min_command*)cmd;
+        uint32_t                     sdk;
+        switch ( cmd->cmd ) {
+            case LC_BUILD_VERSION:
+                handler((Platform)(buildCmd->platform), buildCmd->minos, buildCmd->sdk);
+                foundPlatform = true;
+                break;
+            case LC_VERSION_MIN_MACOSX:
+                sdk = versCmd->sdk;
+                // The original LC_VERSION_MIN_MACOSX did not have an sdk field, assume sdk is same as minOS for those old binaries
+                if ( sdk == 0 )
+                    sdk = versCmd->version;
+                handler(Platform::macOS, versCmd->version, sdk);
+                foundPlatform = true;
+                break;
+            case LC_VERSION_MIN_IPHONEOS:
+                if ( (this->cputype == CPU_TYPE_X86_64) || (this->cputype == CPU_TYPE_I386) )
+                    handler(Platform::iOS_simulator, versCmd->version, versCmd->sdk); // old sim binary
+                else
+                    handler(Platform::iOS, versCmd->version, versCmd->sdk);
+                foundPlatform = true;
+                break;
+            case LC_VERSION_MIN_TVOS:
+                if ( this->cputype == CPU_TYPE_X86_64 )
+                    handler(Platform::tvOS_simulator, versCmd->version, versCmd->sdk); // old sim binary
+                else
+                    handler(Platform::tvOS, versCmd->version, versCmd->sdk);
+                foundPlatform = true;
+                break;
+            case LC_VERSION_MIN_WATCHOS:
+                if ( (this->cputype == CPU_TYPE_X86_64) || (this->cputype == CPU_TYPE_I386) )
+                    handler(Platform::watchOS_simulator, versCmd->version, versCmd->sdk); // old sim binary
+                else
+                    handler(Platform::watchOS, versCmd->version, versCmd->sdk);
+                foundPlatform = true;
+                break;
+        }
+    });
+    if ( !foundPlatform ) {
+        // old binary with no explicit platform
+#if (BUILDING_DYLD || BUILDING_CLOSURE_UTIL) && TARGET_OS_OSX
+        if ( this->cputype == CPU_TYPE_X86_64 )
+            handler(Platform::macOS, 0x000A0500, 0x000A0500); // guess it is a macOS 10.5 binary
+        // <rdar://problem/75343399>
+        // The Go linker emits non-standard binaries without a platform and we have to live with it.
+        if ( this->cputype == CPU_TYPE_ARM64 )
+            handler(Platform::macOS, 0x000B0000, 0x000B0000); // guess it is a macOS 11.0 binary
+#endif
+    }
+    diag.assertNoError();   // any malformations in the file should have been caught by earlier validate() call
+}
+
+
+bool MachOFile::isMachO(Diagnostics& diag, uint64_t fileSize) const
+{
+    if ( !hasMachOMagic() ) {
+        // old PPC slices are not currently valid "mach-o" but should not cause an error
+        if ( !hasMachOBigEndianMagic() )
+            diag.error("file does not start with MH_MAGIC[_64]");
+        return false;
+    }
+    if ( this->sizeofcmds + machHeaderSize() > fileSize ) {
+        diag.error("load commands exceed length of first segment");
+        return false;
+    }
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { });
+    return diag.noError();
+}
+
+bool MachOFile::hasMachOMagic() const
+{
+    return ( (this->magic == MH_MAGIC) || (this->magic == MH_MAGIC_64) );
+}
+
+bool MachOFile::hasMachOBigEndianMagic() const
+{
+    return ( (this->magic == MH_CIGAM) || (this->magic == MH_CIGAM_64) );
+}
+
+
+void MachOFile::forEachLoadCommand(Diagnostics& diag, void (^callback)(const load_command* cmd, bool& stop)) const
+{
+    bool stop = false;
+    const load_command* startCmds = nullptr;
+    if ( this->magic == MH_MAGIC_64 )
+        startCmds = (load_command*)((char *)this + sizeof(mach_header_64));
+    else if ( this->magic == MH_MAGIC )
+        startCmds = (load_command*)((char *)this + sizeof(mach_header));
+    else if ( hasMachOBigEndianMagic() )
+        return;  // can't process big endian mach-o
+    else {
+        const uint32_t* h = (uint32_t*)this;
+        diag.error("file does not start with MH_MAGIC[_64]: 0x%08X 0x%08X", h[0], h [1]);
+        return;  // not a mach-o file
+    }
+    const load_command* const cmdsEnd = (load_command*)((char*)startCmds + this->sizeofcmds);
+    const load_command* cmd = startCmds;
+    for (uint32_t i = 0; i < this->ncmds; ++i) {
+        const load_command* nextCmd = (load_command*)((char *)cmd + cmd->cmdsize);
+        if ( cmd->cmdsize < 8 ) {
+            diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) too small", i, this->ncmds, cmd, this, cmd->cmdsize);
+            return;
+        }
+        // FIXME: add check the cmdsize is pointer aligned (might reveal bin compat issues)
+        if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) {
+            diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) is too large, load commands end at %p", i, this->ncmds, cmd, this, cmd->cmdsize, cmdsEnd);
+            return;
+        }
+        callback(cmd, stop);
+        if ( stop )
+            return;
+        cmd = nextCmd;
+    }
+}
+
+void MachOFile::removeLoadCommand(Diagnostics& diag, void (^callback)(const load_command* cmd, bool& remove, bool& stop))
+{
+    bool stop = false;
+    const load_command* startCmds = nullptr;
+    if ( this->magic == MH_MAGIC_64 )
+        startCmds = (load_command*)((char *)this + sizeof(mach_header_64));
+    else if ( this->magic == MH_MAGIC )
+        startCmds = (load_command*)((char *)this + sizeof(mach_header));
+    else if ( hasMachOBigEndianMagic() )
+        return;  // can't process big endian mach-o
+    else {
+        const uint32_t* h = (uint32_t*)this;
+        diag.error("file does not start with MH_MAGIC[_64]: 0x%08X 0x%08X", h[0], h [1]);
+        return;  // not a mach-o file
+    }
+    const load_command* const cmdsEnd = (load_command*)((char*)startCmds + this->sizeofcmds);
+    auto cmd = (load_command*)startCmds;
+    const uint32_t origNcmds = this->ncmds;
+    unsigned bytesRemaining = this->sizeofcmds;
+    for (uint32_t i = 0; i < origNcmds; ++i) {
+        bool remove = false;
+        auto nextCmd = (load_command*)((char *)cmd + cmd->cmdsize);
+        if ( cmd->cmdsize < 8 ) {
+            diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) too small", i, this->ncmds, cmd, this, cmd->cmdsize);
+            return;
+        }
+        if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) {
+            diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) is too large, load commands end at %p", i, this->ncmds, cmd, this, cmd->cmdsize, cmdsEnd);
+            return;
+        }
+        callback(cmd, remove, stop);
+        if ( remove ) {
+            this->sizeofcmds -= cmd->cmdsize;
+            ::memmove((void*)cmd, (void*)nextCmd, bytesRemaining);
+            this->ncmds--;
+        } else {
+            bytesRemaining -= cmd->cmdsize;
+            cmd = nextCmd;
+        }
+        if ( stop )
+            break;
+    }
+    if ( cmd )
+     ::bzero(cmd, bytesRemaining);
+}
+
+const char* MachOFile::installName() const
+{
+    const char*  name;
+    uint32_t     compatVersion;
+    uint32_t     currentVersion;
+    if ( getDylibInstallName(&name, &compatVersion, &currentVersion) )
+        return name;
+    return nullptr;
+}
+
+bool MachOFile::getDylibInstallName(const char** installName, uint32_t* compatVersion, uint32_t* currentVersion) const
+{
+    Diagnostics diag;
+    __block bool found = false;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+        if ( cmd->cmd == LC_ID_DYLIB ) {
+            const dylib_command*  dylibCmd = (dylib_command*)cmd;
+            *compatVersion  = dylibCmd->dylib.compatibility_version;
+            *currentVersion = dylibCmd->dylib.current_version;
+            *installName    = (char*)dylibCmd + dylibCmd->dylib.name.offset;
+            found = true;
+            stop = true;
+        }
+    });
+    diag.assertNoError();   // any malformations in the file should have been caught by earlier validate() call
+    return found;
+}
+
+bool MachOFile::getUuid(uuid_t uuid) const
+{
+    Diagnostics diag;
+    __block bool found = false;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+        if ( cmd->cmd == LC_UUID ) {
+            const uuid_command* uc = (const uuid_command*)cmd;
+            memcpy(uuid, uc->uuid, sizeof(uuid_t));
+            found = true;
+            stop = true;
+        }
+    });
+    diag.assertNoError();   // any malformations in the file should have been caught by earlier validate() call
+    if ( !found )
+        bzero(uuid, sizeof(uuid_t));
+    return found;
+}
+
+void MachOFile::forEachDependentDylib(void (^callback)(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop)) const
+{
+    Diagnostics       diag;
+    __block unsigned  count   = 0;
+    __block bool      stopped = false;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+         switch ( cmd->cmd ) {
+            case LC_LOAD_DYLIB:
+            case LC_LOAD_WEAK_DYLIB:
+            case LC_REEXPORT_DYLIB:
+            case LC_LOAD_UPWARD_DYLIB: {
+                const dylib_command* dylibCmd = (dylib_command*)cmd;
+                const char* loadPath = (char*)dylibCmd + dylibCmd->dylib.name.offset;
+                callback(loadPath, (cmd->cmd == LC_LOAD_WEAK_DYLIB), (cmd->cmd == LC_REEXPORT_DYLIB), (cmd->cmd == LC_LOAD_UPWARD_DYLIB),
+                                    dylibCmd->dylib.compatibility_version, dylibCmd->dylib.current_version, stop);
+                ++count;
+                if ( stop )
+                    stopped = true;
+            }
+            break;
+        }
+    });
+    // everything must link with something
+    if ( (count == 0) && !stopped ) {
+        // The dylibs that make up libSystem can link with nothing
+        // except for dylibs in libSystem.dylib which are ok to link with nothing (they are on bottom)
+        if ( this->builtForPlatform(Platform::driverKit, true) ) {
+            if ( !this->isDylib() || (strncmp(this->installName(), "/System/DriverKit/usr/lib/system/", 33) != 0) )
+                callback("/System/DriverKit/usr/lib/libSystem.B.dylib", false, false, false, 0x00010000, 0x00010000, stopped);
+        }
+        else {
+            if ( !this->isDylib() || (strncmp(this->installName(), "/usr/lib/system/", 16) != 0) )
+                callback("/usr/lib/libSystem.B.dylib", false, false, false, 0x00010000, 0x00010000, stopped);
+        }
+    }
+    diag.assertNoError();   // any malformations in the file should have been caught by earlier validate() call
+}
+
+void MachOFile::forDyldEnv(void (^callback)(const char* envVar, bool& stop)) const
+{
+    Diagnostics diag;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+         if ( cmd->cmd == LC_DYLD_ENVIRONMENT ) {
+            const dylinker_command* envCmd = (dylinker_command*)cmd;
+            const char* keyEqualsValue = (char*)envCmd + envCmd->name.offset;
+            // only process variables that start with DYLD_ and end in _PATH
+            if ( (strncmp(keyEqualsValue, "DYLD_", 5) == 0) ) {
+                const char* equals = strchr(keyEqualsValue, '=');
+                if ( equals != NULL ) {
+                    if ( strncmp(&equals[-5], "_PATH", 5) == 0 ) {
+                        callback(keyEqualsValue, stop);
+                    }
+                }
+            }
+        }
+    });
+    diag.assertNoError();   // any malformations in the file should have been caught by earlier validate() call
+}
+
+bool MachOFile::enforceCompatVersion() const
+{
+    __block bool result = true;
+    forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
+        switch ( platform ) {
+            case Platform::macOS:
+                if ( minOS >= 0x000A0E00 )  // macOS 10.14
+                    result = false;
+                break;
+            case Platform::iOS:
+            case Platform::tvOS:
+            case Platform::iOS_simulator:
+            case Platform::tvOS_simulator:
+                if ( minOS >= 0x000C0000 )  // iOS 12.0
+                    result = false;
+                break;
+            case Platform::watchOS:
+            case Platform::watchOS_simulator:
+                if ( minOS >= 0x00050000 )  // watchOS 5.0
+                    result = false;
+                break;
+            case Platform::bridgeOS:
+                if ( minOS >= 0x00030000 )  // bridgeOS 3.0
+                    result = false;
+                break;
+            case Platform::driverKit:
+            case Platform::iOSMac:
+                result = false;
+                break;
+            case Platform::unknown:
+                break;
+        }
+    });
+    return result;
+}
+
+const thread_command* MachOFile::unixThreadLoadCommand() const {
+    Diagnostics diag;
+    __block const thread_command* command = nullptr;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+        if ( cmd->cmd == LC_UNIXTHREAD ) {
+            command = (const thread_command*)cmd;
+            stop = true;
+        }
+    });
+    return command;
+}
+
+
+uint32_t MachOFile::entryAddrRegisterIndexForThreadCmd() const
+{
+    switch ( this->cputype ) {
+        case CPU_TYPE_I386:
+            return 10; // i386_thread_state_t.eip
+        case CPU_TYPE_X86_64:
+            return 16; // x86_thread_state64_t.rip
+        case CPU_TYPE_ARM:
+            return 15; // arm_thread_state_t.pc
+        case CPU_TYPE_ARM64:
+            return 32; // arm_thread_state64_t.__pc
+    }
+    return ~0U;
+}
+
+
+uint64_t MachOFile::entryAddrFromThreadCmd(const thread_command* cmd) const
+{
+    assert(cmd->cmd == LC_UNIXTHREAD);
+    const uint32_t* regs32 = (uint32_t*)(((char*)cmd) + 16);
+    const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16);
+
+    uint32_t index = entryAddrRegisterIndexForThreadCmd();
+    if (index == ~0U)
+        return 0;
+
+    return is64() ? regs64[index] : regs32[index];
+}
+
+
+void MachOFile::forEachSegment(void (^callback)(const SegmentInfo& info, bool& stop)) const
+{
+    Diagnostics diag;
+    const bool  intel32  = (this->cputype == CPU_TYPE_I386);
+    __block uint32_t segIndex = 0;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+        if ( cmd->cmd == LC_SEGMENT_64 ) {
+            const segment_command_64* segCmd = (segment_command_64*)cmd;
+            uint64_t sizeOfSections = segCmd->vmsize;
+            uint8_t p2align = 0;
+            const section_64* const sectionsStart = (section_64*)((char*)segCmd + sizeof(struct segment_command_64));
+            const section_64* const sectionsEnd   = &sectionsStart[segCmd->nsects];
+            for (const section_64* sect=sectionsStart; sect < sectionsEnd; ++sect) {
+                sizeOfSections = sect->addr + sect->size - segCmd->vmaddr;
+                if ( sect->align > p2align )
+                    p2align = sect->align;
+            }
+            SegmentInfo info;
+            info.fileOffset        = segCmd->fileoff;
+            info.fileSize          = segCmd->filesize;
+            info.vmAddr            = segCmd->vmaddr;
+            info.vmSize            = segCmd->vmsize;
+            info.sizeOfSections    = sizeOfSections;
+            info.segName           = segCmd->segname;
+            info.loadCommandOffset = (uint32_t)((uint8_t*)segCmd - (uint8_t*)this);
+            info.protections       = segCmd->initprot;
+            info.textRelocs        = false;
+            info.readOnlyData      = ((segCmd->flags & SG_READ_ONLY) != 0);
+            info.isProtected       = (segCmd->flags & SG_PROTECTED_VERSION_1) ? 1 : 0;
+            info.hasZeroFill       = (segCmd->initprot == 3) && (segCmd->filesize < segCmd->vmsize);
+            info.p2align           = p2align;
+            info.segIndex          = segIndex;
+            callback(info, stop);
+            ++segIndex;
+        }
+        else if ( cmd->cmd == LC_SEGMENT ) {
+            const segment_command* segCmd = (segment_command*)cmd;
+            uint64_t sizeOfSections = segCmd->vmsize;
+            uint8_t p2align = 0;
+            bool  hasTextRelocs = false;
+            const section* const sectionsStart = (section*)((char*)segCmd + sizeof(struct segment_command));
+            const section* const sectionsEnd   = &sectionsStart[segCmd->nsects];
+            for (const section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
+                sizeOfSections = sect->addr + sect->size - segCmd->vmaddr;
+                if ( sect->align > p2align )
+                    p2align = sect->align;
+                if ( sect->flags & (S_ATTR_EXT_RELOC|S_ATTR_LOC_RELOC) )
+                    hasTextRelocs = true;
+           }
+            SegmentInfo info;
+            info.fileOffset        = segCmd->fileoff;
+            info.fileSize          = segCmd->filesize;
+            info.vmAddr            = segCmd->vmaddr;
+            info.vmSize            = segCmd->vmsize;
+            info.sizeOfSections    = sizeOfSections;
+            info.segName           = segCmd->segname;
+            info.loadCommandOffset = (uint32_t)((uint8_t*)segCmd - (uint8_t*)this);
+            info.protections       = segCmd->initprot;
+            info.textRelocs        = intel32 && !info.writable() && hasTextRelocs;
+            info.readOnlyData      = ((segCmd->flags & SG_READ_ONLY) != 0);
+            info.isProtected       = (segCmd->flags & SG_PROTECTED_VERSION_1) ? 1 : 0;
+            info.hasZeroFill       = (segCmd->initprot == 3) && (segCmd->filesize < segCmd->vmsize);
+            info.p2align           = p2align;
+            info.segIndex          = segIndex;
+            callback(info, stop);
+            ++segIndex;
+        }
+    });
+    diag.assertNoError();   // any malformations in the file should have been caught by earlier validate() call
+}
+
+uint64_t MachOFile::preferredLoadAddress() const
+{
+    __block uint64_t textVmAddr = 0;
+    forEachSegment(^(const SegmentInfo& info, bool& stop) {
+        if ( strcmp(info.segName, "__TEXT") == 0 ) {
+            textVmAddr = info.vmAddr;
+            stop = true;
+        }
+    });
+    return textVmAddr;
+}
+
+void MachOFile::forEachSection(void (^callback)(const SectionInfo& sectInfo, bool malformedSectionRange, bool& stop)) const
+{
+    Diagnostics diag;
+    BLOCK_ACCCESSIBLE_ARRAY(char, sectNameCopy, 20);  // read as:  char sectNameCopy[20];
+    const bool intel32 = (this->cputype == CPU_TYPE_I386);
+    __block uint32_t segIndex = 0;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+        SectionInfo sectInfo;
+        if ( cmd->cmd == LC_SEGMENT_64 ) {
+            const segment_command_64* segCmd = (segment_command_64*)cmd;
+            uint64_t sizeOfSections = segCmd->vmsize;
+            uint8_t p2align = 0;
+            const section_64* const sectionsStart = (section_64*)((char*)segCmd + sizeof(struct segment_command_64));
+            const section_64* const sectionsEnd   = &sectionsStart[segCmd->nsects];
+            for (const section_64* sect=sectionsStart; sect < sectionsEnd; ++sect) {
+                sizeOfSections = sect->addr + sect->size - segCmd->vmaddr;
+                if ( sect->align > p2align )
+                    p2align = sect->align;
+            }
+            sectInfo.segInfo.fileOffset        = segCmd->fileoff;
+            sectInfo.segInfo.fileSize          = segCmd->filesize;
+            sectInfo.segInfo.vmAddr            = segCmd->vmaddr;
+            sectInfo.segInfo.vmSize            = segCmd->vmsize;
+            sectInfo.segInfo.sizeOfSections    = sizeOfSections;
+            sectInfo.segInfo.segName           = segCmd->segname;
+            sectInfo.segInfo.loadCommandOffset = (uint32_t)((uint8_t*)segCmd - (uint8_t*)this);
+            sectInfo.segInfo.protections       = segCmd->initprot;
+            sectInfo.segInfo.textRelocs        = false;
+            sectInfo.segInfo.readOnlyData      = ((segCmd->flags & SG_READ_ONLY) != 0);
+            sectInfo.segInfo.isProtected       = (segCmd->flags & SG_PROTECTED_VERSION_1) ? 1 : 0;
+            sectInfo.segInfo.p2align           = p2align;
+            sectInfo.segInfo.segIndex          = segIndex;
+            for (const section_64* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) {
+                const char* sectName = sect->sectname;
+                if ( sectName[15] != '\0' ) {
+                    strlcpy(sectNameCopy, sectName, 17);
+                    sectName = sectNameCopy;
+                }
+                bool malformedSectionRange = (sect->addr < segCmd->vmaddr) || greaterThanAddOrOverflow(sect->addr, sect->size, segCmd->vmaddr + segCmd->filesize);
+                sectInfo.sectName       = sectName;
+                sectInfo.sectFileOffset = sect->offset;
+                sectInfo.sectFlags      = sect->flags;
+                sectInfo.sectAddr       = sect->addr;
+                sectInfo.sectSize       = sect->size;
+                sectInfo.sectAlignP2    = sect->align;
+                sectInfo.reserved1      = sect->reserved1;
+                sectInfo.reserved2      = sect->reserved2;
+                callback(sectInfo, malformedSectionRange, stop);
+            }
+            ++segIndex;
+        }
+        else if ( cmd->cmd == LC_SEGMENT ) {
+            const segment_command* segCmd = (segment_command*)cmd;
+            uint64_t sizeOfSections = segCmd->vmsize;
+            uint8_t p2align = 0;
+            bool  hasTextRelocs = false;
+            const section* const sectionsStart = (section*)((char*)segCmd + sizeof(struct segment_command));
+            const section* const sectionsEnd   = &sectionsStart[segCmd->nsects];
+            for (const section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
+                sizeOfSections = sect->addr + sect->size - segCmd->vmaddr;
+                if ( sect->align > p2align )
+                    p2align = sect->align;
+                if ( sect->flags & (S_ATTR_EXT_RELOC|S_ATTR_LOC_RELOC) )
+                    hasTextRelocs = true;
+            }
+            sectInfo.segInfo.fileOffset        = segCmd->fileoff;
+            sectInfo.segInfo.fileSize          = segCmd->filesize;
+            sectInfo.segInfo.vmAddr            = segCmd->vmaddr;
+            sectInfo.segInfo.vmSize            = segCmd->vmsize;
+            sectInfo.segInfo.sizeOfSections    = sizeOfSections;
+            sectInfo.segInfo.segName           = segCmd->segname;
+            sectInfo.segInfo.loadCommandOffset = (uint32_t)((uint8_t*)segCmd - (uint8_t*)this);
+            sectInfo.segInfo.protections       = segCmd->initprot;
+            sectInfo.segInfo.textRelocs        = intel32 && !sectInfo.segInfo.writable() && hasTextRelocs;
+            sectInfo.segInfo.readOnlyData      = ((segCmd->flags & SG_READ_ONLY) != 0);
+            sectInfo.segInfo.isProtected       = (segCmd->flags & SG_PROTECTED_VERSION_1) ? 1 : 0;
+            sectInfo.segInfo.p2align           = p2align;
+            sectInfo.segInfo.segIndex          = segIndex;
+            for (const section* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) {
+                const char* sectName = sect->sectname;
+                if ( sectName[15] != '\0' ) {
+                    strlcpy(sectNameCopy, sectName, 17);
+                    sectName = sectNameCopy;
+                }
+                bool malformedSectionRange = (sect->addr < segCmd->vmaddr) || greaterThanAddOrOverflow(sect->addr, sect->size, segCmd->vmaddr + segCmd->filesize);
+                sectInfo.sectName       = sectName;
+                sectInfo.sectFileOffset = sect->offset;
+                sectInfo.sectFlags      = sect->flags;
+                sectInfo.sectAddr       = sect->addr;
+                sectInfo.sectSize       = sect->size;
+                sectInfo.sectAlignP2    = sect->align;
+                sectInfo.reserved1      = sect->reserved1;
+                sectInfo.reserved2      = sect->reserved2;
+                callback(sectInfo, malformedSectionRange, stop);
+            }
+            ++segIndex;
+        }
+    });
+    diag.assertNoError();   // any malformations in the file should have been caught by earlier validate() call
+}
+
+void MachOFile::forEachInterposingSection(Diagnostics& diag, void (^handler)(uint64_t vmOffset, uint64_t vmSize, bool& stop)) const
+{
+    const unsigned ptrSize   = pointerSize();
+    const unsigned entrySize = 2 * ptrSize;
+    forEachSection(^(const MachOFile::SectionInfo& info, bool malformedSectionRange, bool &stop) {
+        if ( ((info.sectFlags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(info.sectName, "__interpose") == 0) && ((strncmp(info.segInfo.segName, "__DATA", 6) == 0) || strncmp(info.segInfo.segName, "__AUTH", 6) == 0)) ) {
+            if ( info.sectSize % entrySize != 0 ) {
+                diag.error("interposing section %s/%s has bad size", info.segInfo.segName, info.sectName);
+                stop = true;
+                return;
+            }
+            if ( malformedSectionRange ) {
+                diag.error("interposing section %s/%s extends beyond the end of the segment", info.segInfo.segName, info.sectName);
+                stop = true;
+                return;
+            }
+            if ( (info.sectAddr % ptrSize) != 0 ) {
+                diag.error("interposing section %s/%s is not pointer aligned", info.segInfo.segName, info.sectName);
+                stop = true;
+                return;
+            }
+            handler(info.sectAddr - preferredLoadAddress(), info.sectSize, stop);
+        }
+    });
+}
+
+bool MachOFile::isRestricted() const
+{
+    __block bool result = false;
+    forEachSection(^(const MachOFile::SectionInfo& info, bool malformedSectionRange, bool &stop) {
+        if ( (strcmp(info.segInfo.segName, "__RESTRICT") == 0) && (strcmp(info.sectName, "__restrict") == 0) ) {
+            result = true;
+            stop = true;
+        }
+    });
+    return result;
+}
+
+bool MachOFile::hasWeakDefs() const
+{
+    return (this->flags & MH_WEAK_DEFINES);
+}
+
+bool MachOFile::usesWeakDefs() const
+{
+    return (this->flags & MH_BINDS_TO_WEAK);
+}
+
+bool MachOFile::hasThreadLocalVariables() const
+{
+    return (this->flags & MH_HAS_TLV_DESCRIPTORS);
+}
+
+static bool endsWith(const char* str, const char* suffix)
+{
+    size_t strLen    = strlen(str);
+    size_t suffixLen = strlen(suffix);
+    if ( strLen < suffixLen )
+        return false;
+    return (strcmp(&str[strLen-suffixLen], suffix) == 0);
+}
+
+bool MachOFile::isSharedCacheEligiblePath(const char* dylibName) {
+    return (   (strncmp(dylibName, "/usr/lib/", 9) == 0)
+            || (strncmp(dylibName, "/System/Library/", 16) == 0)
+            || (strncmp(dylibName, "/System/iOSSupport/usr/lib/", 27) == 0)
+            || (strncmp(dylibName, "/System/iOSSupport/System/Library/", 34) == 0)
+            || (strncmp(dylibName, "/Library/Apple/usr/lib/", 23) == 0)
+            || (strncmp(dylibName, "/Library/Apple/System/Library/", 30) == 0)
+            || (strncmp(dylibName, "/System/DriverKit/", 18) == 0));
+}
+
+static bool startsWith(const char* buffer, const char* valueToFind) {
+    return strncmp(buffer, valueToFind, strlen(valueToFind)) == 0;
+}
+
+static bool platformExcludesSharedCache_macOS(const char* installName) {
+    // Note: This function basically matches dontCache() from update dyld shared cache
+
+    if ( startsWith(installName, "/usr/lib/system/introspection/") )
+        return true;
+    if ( startsWith(installName, "/System/Library/QuickTime/") )
+        return true;
+    if ( startsWith(installName, "/System/Library/Tcl/") )
+        return true;
+    if ( startsWith(installName, "/System/Library/Perl/") )
+        return true;
+    if ( startsWith(installName, "/System/Library/MonitorPanels/") )
+        return true;
+    if ( startsWith(installName, "/System/Library/Accessibility/") )
+        return true;
+    if ( startsWith(installName, "/usr/local/") )
+        return true;
+    if ( startsWith(installName, "/usr/lib/pam/") )
+        return true;
+    // We no longer support ROSP, so skip all paths which start with the special prefix
+    if ( startsWith(installName, "/System/Library/Templates/Data/") )
+        return true;
+
+    // anything inside a .app bundle is specific to app, so should not be in shared cache
+    if ( strstr(installName, ".app/") != NULL )
+        return true;
+
+    return false;
+}
+
+static bool platformExcludesSharedCache_iOS(const char* installName) {
+    if ( strcmp(installName, "/System/Library/Caches/com.apple.xpc/sdk.dylib") == 0 )
+        return true;
+    if ( strcmp(installName, "/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib") == 0 )
+        return true;
+    return false;
+}
+
+// Returns true if the current platform requires that this install name be excluded from the shared cache
+// Note that this overrides any exclusion from anywhere else.
+static bool platformExcludesSharedCache(Platform platform, const char* installName) {
+    if ( MachOFile::isSimulatorPlatform(platform) )
+        return false;
+    if ( (platform == dyld3::Platform::macOS) || (platform == dyld3::Platform::iOSMac) )
+        return platformExcludesSharedCache_macOS(installName);
+    // Everything else is based on iOS so just use that value
+    return platformExcludesSharedCache_iOS(installName);
+}
+
+
+bool MachOFile::canBePlacedInDyldCache(const char* path, void (^failureReason)(const char*)) const
+{
+
+    if ( !isSharedCacheEligiblePath(path) ) {
+        // Dont spam the user with an error about paths when we know these are never eligible.
+        return false;
+    }
+
+    // only dylibs can go in cache
+    if ( this->filetype != MH_DYLIB ) {
+        failureReason("Not MH_DYLIB");
+        return false; // cannot continue, installName() will assert() if not a dylib
+    }
+
+
+    const char* dylibName = installName();
+    if ( dylibName[0] != '/' ) {
+        failureReason("install name not an absolute path");
+        // Don't continue as we don't want to spam the log with errors we don't need.
+        return false;
+    }
+    else if ( strcmp(dylibName, path) != 0 ) {
+        failureReason("install path does not match install name");
+        return false;
+    }
+    else if ( strstr(dylibName, "//") != 0 ) {
+        failureReason("install name should not include //");
+        return false;
+    }
+    else if ( strstr(dylibName, "./") != 0 ) {
+        failureReason("install name should not include ./");
+        return false;
+    }
+
+    __block bool platformExcludedFile = false;
+    forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
+        if ( platformExcludedFile )
+            return;
+        if ( platformExcludesSharedCache(platform, dylibName) ) {
+            platformExcludedFile = true;
+            return;
+        }
+    });
+    if ( platformExcludedFile ) {
+        failureReason("install name is not shared cache eligible on platform");
+        return false;
+    }
+
+    bool retval = true;
+
+    // flat namespace files cannot go in cache
+    if ( (this->flags & MH_TWOLEVEL) == 0 ) {
+        retval = false;
+        failureReason("Not built with two level namespaces");
+    }
+
+    // don't put debug variants into dyld cache
+    if ( endsWith(path, "_profile.dylib") || endsWith(path, "_debug.dylib") || endsWith(path, "_profile") || endsWith(path, "_debug") || endsWith(path, "/CoreADI") ) {
+        retval = false;
+        failureReason("Variant image");
+    }
+
+    // dylib must have extra info for moving DATA and TEXT segments apart
+    __block bool hasExtraInfo = false;
+    __block bool hasDyldInfo = false;
+    __block bool hasExportTrie = false;
+    Diagnostics diag;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+        if ( cmd->cmd == LC_SEGMENT_SPLIT_INFO )
+            hasExtraInfo = true;
+        if ( cmd->cmd == LC_DYLD_INFO_ONLY )
+            hasDyldInfo = true;
+        if ( cmd->cmd == LC_DYLD_EXPORTS_TRIE )
+            hasExportTrie = true;
+    });
+    if ( !hasExtraInfo ) {
+        retval = false;
+        failureReason("Missing split seg info");
+    }
+    if ( !hasDyldInfo && !hasExportTrie ) {
+        retval = false;
+        failureReason("Old binary, missing dyld info or export trie");
+    }
+
+    // dylib can only depend on other dylibs in the shared cache
+    __block bool allDepPathsAreGood = true;
+    forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
+        // Skip weak links.  They are allowed to be missing
+        if ( isWeak )
+            return;
+        if ( !isSharedCacheEligiblePath(loadPath) ) {
+            allDepPathsAreGood = false;
+            stop = true;
+        }
+    });
+    if ( !allDepPathsAreGood ) {
+        retval = false;
+        failureReason("Depends on dylibs ineligable for dyld cache");
+    }
+
+    // dylibs with interposing info cannot be in cache
+    if ( hasInterposingTuples() ) {
+        retval = false;
+        failureReason("Has interposing tuples");
+    }
+
+    // Temporarily kick out swift binaries out of dyld cache on watchOS simulators as they have missing split seg
+    if ( (this->cputype == CPU_TYPE_I386) && builtForPlatform(Platform::watchOS_simulator) ) {
+        if ( strncmp(dylibName, "/usr/lib/swift/", 15) == 0 ) {
+            retval = false;
+            failureReason("i386 swift binary");
+        }
+    }
+
+    return retval;
+}
+
+#if BUILDING_APP_CACHE_UTIL
+bool MachOFile::canBePlacedInKernelCollection(const char* path, void (^failureReason)(const char*)) const
+{
+    // only dylibs and the kernel itself can go in cache
+    if ( this->filetype == MH_EXECUTE ) {
+        // xnu
+    } else if ( this->isKextBundle() ) {
+        // kext's
+    } else {
+        failureReason("Not MH_KEXT_BUNDLE");
+        return false;
+    }
+
+    if ( this->filetype == MH_EXECUTE ) {
+        // xnu
+
+        // two-level namespace binaries cannot go in cache
+        if ( (this->flags & MH_TWOLEVEL) != 0 ) {
+            failureReason("Built with two level namespaces");
+            return false;
+        }
+
+        // xnu kernel cannot have a page zero
+        __block bool foundPageZero = false;
+        forEachSegment(^(const SegmentInfo &segmentInfo, bool &stop) {
+            if ( strcmp(segmentInfo.segName, "__PAGEZERO") == 0 ) {
+                foundPageZero = true;
+                stop = true;
+            }
+        });
+        if (foundPageZero) {
+            failureReason("Has __PAGEZERO");
+            return false;
+        }
+
+        // xnu must have an LC_UNIXTHREAD to point to the entry point
+        __block bool foundMainLC = false;
+        __block bool foundUnixThreadLC = false;
+        Diagnostics diag;
+        forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+            if ( cmd->cmd == LC_MAIN ) {
+                foundMainLC = true;
+                stop = true;
+            }
+            else if ( cmd->cmd == LC_UNIXTHREAD ) {
+                foundUnixThreadLC = true;
+            }
+        });
+        if (foundMainLC) {
+            failureReason("Found LC_MAIN");
+            return false;
+        }
+        if (!foundUnixThreadLC) {
+            failureReason("Expected LC_UNIXTHREAD");
+            return false;
+        }
+
+        if (diag.hasError()) {
+            failureReason("Error parsing load commands");
+            return false;
+        }
+
+        // The kernel should be a static executable, not a dynamic one
+        if ( !isStaticExecutable() ) {
+            failureReason("Expected static executable");
+            return false;
+        }
+
+        // The kernel must be built with -pie
+        if ( !isPIE() ) {
+            failureReason("Expected pie");
+            return false;
+        }
+    }
+
+    if ( isArch("arm64e") && isKextBundle() && !hasChainedFixups() ) {
+        failureReason("Missing fixup information");
+        return false;
+    }
+
+    // dylibs with interposing info cannot be in cache
+    if ( hasInterposingTuples() ) {
+        failureReason("Has interposing tuples");
+        return false;
+    }
+
+    // Only x86_64 is allowed to have RWX segments
+    if ( !isArch("x86_64") && !isArch("x86_64h") ) {
+        __block bool foundBadSegment = false;
+        forEachSegment(^(const SegmentInfo &info, bool &stop) {
+            if ( (info.protections & (VM_PROT_WRITE | VM_PROT_EXECUTE)) == (VM_PROT_WRITE | VM_PROT_EXECUTE) ) {
+                failureReason("Segments are not allowed to be both writable and executable");
+                foundBadSegment = true;
+                stop = true;
+            }
+        });
+        if ( foundBadSegment )
+            return false;
+    }
+
+    return true;
+}
+#endif
+
+static bool platformExcludesPrebuiltClosure_macOS(const char* path) {
+    // We no longer support ROSP, so skip all paths which start with the special prefix
+    if ( startsWith(path, "/System/Library/Templates/Data/") )
+        return true;
+
+    // anything inside a .app bundle is specific to app, so should not get a prebuilt closure
+    if ( strstr(path, ".app/") != NULL )
+        return true;
+
+    return false;
+}
+
+static bool platformExcludesPrebuiltClosure_iOS(const char* path) {
+    if ( strcmp(path, "/System/Library/Caches/com.apple.xpc/sdk.dylib") == 0 )
+        return true;
+    if ( strcmp(path, "/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib") == 0 )
+        return true;
+    return false;
+}
+
+// Returns true if the current platform requires that this install name be excluded from the shared cache
+// Note that this overrides any exclusion from anywhere else.
+static bool platformExcludesPrebuiltClosure(Platform platform, const char* path) {
+    if ( MachOFile::isSimulatorPlatform(platform) )
+        return false;
+    if ( (platform == dyld3::Platform::macOS) || (platform == dyld3::Platform::iOSMac) )
+        return platformExcludesPrebuiltClosure_macOS(path);
+    // Everything else is based on iOS so just use that value
+    return platformExcludesPrebuiltClosure_iOS(path);
+}
+
+bool MachOFile::canHavePrecomputedDlopenClosure(const char* path, void (^failureReason)(const char*)) const
+{
+    __block bool retval = true;
+
+    // only dylibs can go in cache
+    if ( (this->filetype != MH_DYLIB) && (this->filetype != MH_BUNDLE) ) {
+        retval = false;
+        failureReason("not MH_DYLIB or MH_BUNDLE");
+    }
+
+    // flat namespace files cannot go in cache
+    if ( (this->flags & MH_TWOLEVEL) == 0 ) {
+        retval = false;
+        failureReason("not built with two level namespaces");
+    }
+
+    // can only depend on other dylibs with absolute paths
+    __block bool allDepPathsAreGood = true;
+    forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
+        if ( loadPath[0] != '/' ) {
+            allDepPathsAreGood = false;
+            stop = true;
+        }
+    });
+    if ( !allDepPathsAreGood ) {
+        retval = false;
+        failureReason("depends on dylibs that are not absolute paths");
+    }
+
+    __block bool platformExcludedFile = false;
+    forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
+        if ( platformExcludedFile )
+            return;
+        if ( platformExcludesPrebuiltClosure(platform, path) ) {
+            platformExcludedFile = true;
+            return;
+        }
+    });
+    if ( platformExcludedFile ) {
+        failureReason("file cannot get a prebuilt closure on this platform");
+        return false;
+    }
+
+    // dylibs with interposing info cannot have dlopen closure pre-computed
+    if ( hasInterposingTuples() ) {
+        retval = false;
+        failureReason("has interposing tuples");
+    }
+
+    // special system dylib overrides cannot have closure pre-computed
+    if ( strncmp(path, "/usr/lib/system/introspection/", 30) == 0 ) {
+        retval = false;
+        failureReason("override of OS dylib");
+    }
+
+    return retval;
+}
+
+bool MachOFile::hasInterposingTuples() const
+{
+    __block bool hasInterposing = false;
+    Diagnostics diag;
+    forEachInterposingSection(diag, ^(uint64_t vmOffset, uint64_t vmSize, bool &stop) {
+        hasInterposing = true;
+        stop = true;
+    });
+    return hasInterposing;
+}
+
+bool MachOFile::isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) const
+{
+    if ( const encryption_info_command* encCmd = findFairPlayEncryptionLoadCommand() ) {
+       if ( encCmd->cryptid == 1 ) {
+            // Note: cryptid is 0 in just-built apps.  The AppStore sets cryptid to 1
+            textOffset = encCmd->cryptoff;
+            size       = encCmd->cryptsize;
+            return true;
+        }
+    }
+    textOffset = 0;
+    size = 0;
+    return false;
+}
+
+bool MachOFile::canBeFairPlayEncrypted() const
+{
+    return (findFairPlayEncryptionLoadCommand() != nullptr);
+}
+
+const encryption_info_command* MachOFile::findFairPlayEncryptionLoadCommand() const
+{
+    __block const encryption_info_command* result = nullptr;
+    Diagnostics diag;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+         if ( (cmd->cmd == LC_ENCRYPTION_INFO) || (cmd->cmd == LC_ENCRYPTION_INFO_64) ) {
+            result = (encryption_info_command*)cmd;
+            stop = true;
+        }
+    });
+    if ( diag.noError() )
+        return result;
+    else
+        return nullptr;
+}
+
+
+bool MachOFile::hasLoadCommand(uint32_t cmdNum) const
+{
+    __block bool hasLC = false;
+    Diagnostics diag;
+    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
+        if ( cmd->cmd == cmdNum ) {
+            hasLC = true;
+            stop = true;
+        }
+    });
+    return hasLC;
+}
+
+bool MachOFile::allowsAlternatePlatform() const
+{
+    __block bool result = false;
+    forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& stop) {
+        if ( (strcmp(info.sectName, "__allow_alt_plat") == 0) && (strncmp(info.segInfo.segName, "__DATA", 6) == 0) ) {
+            result = true;
+            stop = true;
+        }
+    });
+    return result;
+}
+
+bool MachOFile::hasChainedFixups() const
+{
+#if SUPPORT_ARCH_arm64e
+    // arm64e always uses chained fixups
+    if ( (this->cputype == CPU_TYPE_ARM64) && (this->maskedCpuSubtype() == CPU_SUBTYPE_ARM64E) ) {
+        // Not all binaries have fixups at all so check for the load commands
+        return hasLoadCommand(LC_DYLD_INFO_ONLY) || hasLoadCommand(LC_DYLD_CHAINED_FIXUPS);
+    }
+#endif
+    return hasLoadCommand(LC_DYLD_CHAINED_FIXUPS);
+}
+
+bool MachOFile::hasChainedFixupsLoadCommand() const
+{
+    return hasLoadCommand(LC_DYLD_CHAINED_FIXUPS);
+}
+
+bool MachOFile::hasOpcodeFixups() const
+{
+    return hasLoadCommand(LC_DYLD_INFO_ONLY) || hasLoadCommand(LC_DYLD_INFO) ;
+}
+
+uint64_t MachOFile::read_uleb128(Diagnostics& diag, const uint8_t*& p, const uint8_t* end)
+{
+    uint64_t result = 0;
+    int         bit = 0;
+    do {
+        if ( p == end ) {
+            diag.error("malformed uleb128");
+            break;
+        }
+        uint64_t slice = *p & 0x7f;
+
+        if ( bit > 63 ) {
+            diag.error("uleb128 too big for uint64");
+            break;
+        }
+        else {
+            result |= (slice << bit);
+            bit += 7;
+        }
+    }
+    while (*p++ & 0x80);
+    return result;
+}
+
+
+int64_t MachOFile::read_sleb128(Diagnostics& diag, const uint8_t*& p, const uint8_t* end)
+{
+    int64_t  result = 0;
+    int      bit = 0;
+    uint8_t  byte = 0;
+    do {
+        if ( p == end ) {
+            diag.error("malformed sleb128");
+            break;
+        }
+        byte = *p++;
+        result |= (((int64_t)(byte & 0x7f)) << bit);
+        bit += 7;
+    } while (byte & 0x80);
+    // sign extend negative numbers
+    if ( ((byte & 0x40) != 0) && (bit < 64) )
+        result |= (~0ULL) << bit;
+    return result;
+}
+
+
+const MachOFile* MachOFile::compatibleSlice(Diagnostics& diag, const void* fileContent, size_t contentSize, const char* path, Platform platform, bool isOSBinary, const GradedArchs& archs)
+{
+    const MachOFile* mf = nullptr;
+    if ( const dyld3::FatFile* ff = dyld3::FatFile::isFatFile(fileContent) ) {
+        uint64_t  sliceOffset;
+        uint64_t  sliceLen;
+        bool      missingSlice;
+        if ( ff->isFatFileWithSlice(diag, contentSize, archs, isOSBinary, sliceOffset, sliceLen, missingSlice) ) {
+            mf = (MachOFile*)((long)fileContent + sliceOffset);
+        }
+        else {
+            char strBuf[256];
+            diag.error("fat file, but missing compatible architecture (have '%s', need '%s')", ff->archNames(strBuf), archs.name());
+            return nullptr;
+        }
+    }
+    else {
+        mf = (MachOFile*)fileContent;
+    }
+
+    if ( !mf->hasMachOMagic() || !mf->isMachO(diag, contentSize) ) {
+        diag.error("not a mach-o file");
+        return nullptr;
+    }
+
+    if ( archs.grade(mf->cputype, mf->cpusubtype, isOSBinary) == 0 ) {
+        diag.error("mach-o file, but is an incompatible architecture (have '%s', need '%s')", mf->archName(), archs.name());
+        return nullptr;
+    }
+
+    if ( !mf->loadableIntoProcess(platform, path) ) {
+        __block Platform havePlatform = Platform::unknown;
+        mf->forEachSupportedPlatform(^(Platform aPlat, uint32_t minOS, uint32_t sdk) {
+            havePlatform = aPlat;
+        });
+        diag.error("mach-o file (%s), but incompatible platform (have '%s', need '%s')", path, MachOFile::platformName(havePlatform), MachOFile::platformName(platform));
+        return nullptr;
+    }
+
+    return mf;
+}
+
+
+} // namespace dyld3
+
+
+
+
+