Loading...
common/MachOLayout.cpp /dev/null dyld-1330
--- /dev/null
+++ dyld/dyld-1330/common/MachOLayout.cpp
@@ -0,0 +1,2395 @@
+/*
+ * 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 "Array.h"
+#include "Header.h"
+#include "MachOLayout.h"
+#include "MachOFile.h"
+
+#include <TargetConditionals.h>
+#include "Defines.h"
+#if SUPPORT_CLASSIC_RELOCS
+  #include <mach-o/reloc.h>
+  #include <mach-o/x86_64/reloc.h>
+#endif
+#include <mach-o/nlist.h>
+
+// FIXME: We should get this from cctools
+#define DYLD_CACHE_ADJ_V2_FORMAT 0x7F
+
+namespace mach_o
+{
+
+// MARK: --- Layout methods ---
+
+Layout::Layout(MachOFileRef mf, std::span<SegmentLayout> segments, const LinkeditLayout& linkedit)
+    : mf(std::move(mf)), segments(segments), linkedit(linkedit)
+{
+}
+
+uint64_t Layout::textUnslidVMAddr() const
+{
+    for ( const SegmentLayout& segment : this->segments ) {
+        if ( segment.kind == SegmentLayout::Kind::text )
+            return segment.vmAddr;
+    }
+
+    // MachOFile::preferredLoadAddress seems to return 0 if we didn't find __TEXT, so match it
+    return 0;
+}
+
+bool Layout::isSwiftLibrary() const
+{
+    if ( std::optional<uint32_t> flags = this->getObjcInfoFlags(); flags.has_value() ) {
+        uint32_t swiftVersion = ((flags.value() >> 8) & 0xFF);
+        return (swiftVersion != 0);
+    }
+    return false;
+}
+
+std::optional<uint32_t> Layout::getObjcInfoFlags() const
+{
+    struct objc_image_info {
+        int32_t version;
+        uint32_t flags;
+    };
+
+    __block std::optional<uint32_t> flags;
+    ((const Header*)this->mf)->forEachSection(^(const Header::SegmentInfo& segInfo, const Header::SectionInfo& sectInfo, bool& stop) {
+        if ( (sectInfo.sectionName.starts_with("__objc_imageinfo")) && sectInfo.segmentName.starts_with("__DATA") ) {
+            uint64_t segmentOffset = sectInfo.fileOffset - segInfo.fileOffset;
+            objc_image_info* info =  (objc_image_info*)(this->segments[sectInfo.segIndex].buffer + segmentOffset);
+            flags = info->flags;
+            stop = true;
+        }
+    });
+    return flags;
+}
+
+bool Layout::hasSection(std::string_view segmentName, std::string_view sectionName) const
+{
+    __block bool result = false;
+    ((const Header*)this->mf)->forEachSection(^(const Header::SectionInfo& sectInfo, bool& stop) {
+        if ( (sectInfo.segmentName == segmentName) && (sectInfo.sectionName == sectionName) ) {
+            result = true;
+            stop = true;
+        }
+    });
+    return result;
+}
+
+namespace {
+    struct LinkEditContentChunk
+    {
+        const char* name;
+        uint32_t    alignment;
+        uint32_t    fileOffsetStart;
+        uint32_t    size;
+
+        // only have a few chunks, so bubble sort is ok.  Don't use libc's qsort because it may call malloc
+        static void sort(LinkEditContentChunk array[], unsigned long count)
+        {
+            for (unsigned i=0; i < count-1; ++i) {
+                bool done = true;
+                for (unsigned j=0; j < count-i-1; ++j) {
+                    if ( array[j].fileOffsetStart > array[j+1].fileOffsetStart ) {
+                        LinkEditContentChunk temp = array[j];
+                        array[j]   = array[j+1];
+                        array[j+1] = temp;
+                        done = false;
+                    }
+                }
+                if ( done )
+                    break;
+            }
+        }
+    };
+} // anonymous namespace
+
+bool Layout::isValidLinkeditLayout(Diagnostics &diag, const char *path) const
+{
+    typedef dyld3::MachOFile::Malformed Malformed;
+
+    const uint32_t ptrSize = this->mf->pointerSize();
+
+    // build vector of all blobs in LINKEDIT
+    LinkEditContentChunk blobs[32];
+    LinkEditContentChunk* bp = blobs;
+    if ( const Linkedit& blob = this->linkedit.rebaseOpcodes; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"rebase opcodes",          ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+    if ( const Linkedit& blob = this->linkedit.regularBindOpcodes; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"bind opcodes",            ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+    if ( const Linkedit& blob = this->linkedit.weakBindOpcodes; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"weak bind opcodes",       ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+    if ( const Linkedit& blob = this->linkedit.lazyBindOpcodes; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"lazy bind opcodes",       ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+    if ( const Linkedit& blob = this->linkedit.exportsTrie; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"exports trie",            ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+    if ( const Linkedit& blob = this->linkedit.chainedFixups; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"chained fixups",          ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+
+#if SUPPORT_CLASSIC_RELOCS
+    if ( const Linkedit& blob = this->linkedit.localRelocs; blob.hasValue() ) {
+        if ( blob.entryCount != 0 ) {
+            uint32_t bufferSize = (uint32_t)(blob.entryCount * sizeof(relocation_info));
+            *bp++ = {"local relocations",       ptrSize, blob.fileOffset,   bufferSize };
+        }
+    }
+    if ( const Linkedit& blob = this->linkedit.externRelocs; blob.hasValue() ) {
+        if ( blob.entryCount != 0 ) {
+            uint32_t bufferSize = (uint32_t)(blob.entryCount * sizeof(relocation_info));
+            *bp++ = {"external relocations",    ptrSize, blob.fileOffset,   bufferSize };
+        }
+    }
+#endif
+    if ( const Linkedit& blob = this->linkedit.indirectSymbolTable; blob.hasValue() ) {
+        if ( blob.entryCount != 0 ) {
+            uint32_t bufferSize = (uint32_t)(blob.entryCount * sizeof(uint32_t));
+            *bp++ = {"indirect symbol table",   4,       blob.fileOffset,   bufferSize };
+        }
+    }
+
+    if ( const Linkedit& blob = this->linkedit.splitSegInfo; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"shared cache info",       ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+    if ( const Linkedit& blob = this->linkedit.functionStarts; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"function starts",         ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+    if ( const Linkedit& blob = this->linkedit.dataInCode; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"data in code",            ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+    if ( const Linkedit& blob = this->linkedit.symbolTable; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"symbol table",            ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+    if ( const Linkedit& blob = this->linkedit.symbolStrings; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"symbol table strings",    1,       blob.fileOffset,   blob.bufferSize };
+    }
+    if ( const Linkedit& blob = this->linkedit.codeSignature; blob.hasValue() ) {
+        if ( blob.bufferSize != 0 )
+            *bp++ = {"code signature",          ptrSize, blob.fileOffset,   blob.bufferSize };
+    }
+
+    // check for bad combinations
+    if ( (this->linkedit.dyldInfoCmd == LC_DYLD_INFO_ONLY) ) {
+        if ( (this->linkedit.localRelocs.entryCount != 0) && this->mf->enforceFormat(Malformed::dyldInfoAndlocalRelocs) ) {
+            diag.error("in '%s' malformed mach-o contains LC_DYLD_INFO_ONLY and local relocations", path);
+            return false;
+        }
+        if ( this->linkedit.externRelocs.entryCount != 0 ) {
+            diag.error("in '%s' malformed mach-o contains LC_DYLD_INFO_ONLY and external relocations", path);
+            return false;
+        }
+    }
+
+    bool checkMissingDyldInfo = true;
+#if BUILDING_DYLDINFO || BUILDING_APP_CACHE_UTIL
+    checkMissingDyldInfo = this->mf->isDyldManaged() && !this->mf->isStaticExecutable();
+#endif
+    if ( (this->linkedit.dyldInfoCmd == 0 ) && !this->linkedit.hasDynSymTab && checkMissingDyldInfo ) {
+        diag.error("in '%s' malformed mach-o misssing LC_DYLD_INFO and LC_DYSYMTAB", path);
+        return false;
+    }
+
+    // FIXME: Remove this hack
+#if BUILDING_APP_CACHE_UTIL
+    if ( this->mf->isFileSet() )
+        return true;
+#endif
+
+    const unsigned long blobCount = bp - blobs;
+    if ( blobCount == 0 ) {
+        diag.error("in '%s' malformed mach-o missing LINKEDIT", path);
+        return false;
+    }
+
+    // Find the linkedit
+    uint32_t linkeditFileOffset = ~0U;
+    uint32_t linkeditFileSize   = ~0U;
+    for ( const SegmentLayout& segment : this->segments ) {
+        if ( segment.kind == SegmentLayout::Kind::linkedit ) {
+            linkeditFileOffset = (uint32_t)segment.fileOffset;
+            linkeditFileSize   = (uint32_t)segment.fileSize;
+            break;
+        }
+    }
+
+    uint32_t linkeditFileEnd = linkeditFileOffset + linkeditFileSize;
+
+
+    // sort blobs by file-offset and error on overlaps
+    LinkEditContentChunk::sort(blobs, blobCount);
+    uint32_t     prevEnd = linkeditFileOffset;
+    const char*  prevName = "start of LINKEDIT";
+    for (unsigned long i=0; i < blobCount; ++i) {
+        const LinkEditContentChunk& blob = blobs[i];
+        if ( blob.fileOffsetStart < prevEnd ) {
+            diag.error("in '%s' LINKEDIT overlap of %s and %s", path, prevName, blob.name);
+            return false;
+        }
+        if (dyld3::greaterThanAddOrOverflow(blob.fileOffsetStart, blob.size, linkeditFileEnd)) {
+            diag.error("in '%s' LINKEDIT content '%s' extends beyond end of segment", path, blob.name);
+            return false;
+        }
+        if ( (blob.fileOffsetStart & (blob.alignment-1)) != 0 ) {
+            // <rdar://problem/51115705> relax code sig alignment for pre iOS13
+            Malformed kind = (strcmp(blob.name, "code signature") == 0) ? Malformed::codeSigAlignment : Malformed::linkeditAlignment;
+            if ( this->mf->enforceFormat(kind) )
+                diag.error("in '%s' mis-aligned LINKEDIT content '%s'", path, blob.name);
+        }
+        prevEnd  = blob.fileOffsetStart + blob.size;
+        prevName = blob.name;
+    }
+
+    // Check for invalid symbol table sizes
+    if ( this->linkedit.hasSymTab ) {
+        const Linkedit& symbolTable = this->linkedit.symbolTable;
+        if ( symbolTable.entryCount > 0x10000000 ) {
+            diag.error("in '%s' malformed mach-o image: symbol table too large", path);
+            return false;
+        }
+        if ( this->linkedit.hasDynSymTab ) {
+            // validate indirect symbol table
+            const Linkedit& localSymbolTable = this->linkedit.localSymbolTable;
+            const Linkedit& globalSymbolTable = this->linkedit.globalSymbolTable;
+            const Linkedit& undefSymbolTable = this->linkedit.undefSymbolTable;
+            const Linkedit& indirectSymbolTable = this->linkedit.indirectSymbolTable;
+            if ( indirectSymbolTable.entryCount != 0 ) {
+                if ( indirectSymbolTable.entryCount > 0x10000000 ) {
+                    diag.error("in '%s' malformed mach-o image: indirect symbol table too large", path);
+                    return false;
+                }
+            }
+            if ( (localSymbolTable.entryCount > symbolTable.entryCount) || (localSymbolTable.entryIndex > symbolTable.entryCount) ) {
+                diag.error("in '%s' malformed mach-o image: indirect symbol table local symbol count exceeds total symbols", path);
+                return false;
+            }
+            if ( (localSymbolTable.entryIndex + localSymbolTable.entryCount) < localSymbolTable.entryIndex  ) {
+                diag.error("in '%s' malformed mach-o image: indirect symbol table local symbol count wraps", path);
+                return false;
+            }
+            if ( (globalSymbolTable.entryCount > symbolTable.entryCount)
+                || (globalSymbolTable.entryIndex > symbolTable.entryCount) ) {
+                diag.error("in '%s' malformed mach-o image: indirect symbol table extern symbol count exceeds total symbols", path);
+                return false;
+            }
+            if ( (globalSymbolTable.entryIndex + globalSymbolTable.entryCount) < globalSymbolTable.entryIndex  ) {
+                diag.error("in '%s' malformed mach-o image: indirect symbol table extern symbol count wraps", path);
+                return false;
+            }
+            if ( (undefSymbolTable.entryCount > symbolTable.entryCount) || (undefSymbolTable.entryIndex > symbolTable.entryCount) ) {
+                diag.error("in '%s' malformed mach-o image: indirect symbol table undefined symbol count exceeds total symbols", path);
+                return false;
+            }
+            if ( (undefSymbolTable.entryIndex + undefSymbolTable.entryCount) < undefSymbolTable.entryIndex  ) {
+                diag.error("in '%s' malformed mach-o image: indirect symbol table undefined symbol count wraps", path);
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+bool Layout::findExportedSymbol(Diagnostics& diag, const char* symbolName, bool weakImport,
+                                FoundSymbol& foundInfo) const
+{
+    if ( this->linkedit.exportsTrie.hasValue() ) {
+        // FIXME: Move all this to the ExportTrie class
+        const uint8_t* trieStart = this->linkedit.exportsTrie.buffer;
+        const uint8_t* trieEnd   = trieStart + this->linkedit.exportsTrie.bufferSize;
+        const uint8_t* node      = dyld3::MachOFile::trieWalk(diag, trieStart, trieEnd, symbolName);
+        if ( node == nullptr ) {
+            // symbol not exported from this image. Seach any re-exported dylibs
+            // FIXME: Implement this
+#if 0
+            __block unsigned        depIndex = 0;
+            __block bool            foundInReExportedDylib = false;
+            forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
+                if ( isReExport && findDependent ) {
+                    if ( const MachOLoaded* depMH = findDependent(this, depIndex) ) {
+                       if ( depMH->findExportedSymbol(diag, symbolName, weakImport, foundInfo, findDependent) ) {
+                            stop = true;
+                            foundInReExportedDylib = true;
+                        }
+                    }
+                }
+                ++depIndex;
+            });
+            return foundInReExportedDylib;
+#endif
+            return false;
+        }
+        const uint8_t* p = node;
+        const uint64_t flags = dyld3::MachOFile::read_uleb128(diag, p, trieEnd);
+        if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) {
+            // FIXME: Implement this
+#if 0
+            if ( !findDependent )
+                return false;
+            // re-export from another dylib, lookup there
+            const uint64_t ordinal = dyld3::MachOFile::read_uleb128(diag, p, trieEnd);
+            const char* importedName = (char*)p;
+            if ( importedName[0] == '\0' )
+                importedName = symbolName;
+            if ( (ordinal == 0) || (ordinal > dependentDylibCount()) ) {
+                diag.error("re-export ordinal %lld out of range for %s", ordinal, symbolName);
+                return false;
+            }
+            uint32_t depIndex = (uint32_t)(ordinal-1);
+            if ( const MachOLoaded* depMH = findDependent(this, depIndex) ) {
+                return depMH->findExportedSymbol(diag, importedName, weakImport, foundInfo, findDependent);
+            }
+            else if (weakImport) {
+                return false;
+            }
+            else {
+                diag.error("dependent dylib %lld not found for re-exported symbol %s", ordinal, symbolName);
+                return false;
+            }
+#endif
+            return false;
+        }
+        foundInfo.kind               = FoundSymbol::Kind::headerOffset;
+        foundInfo.isThreadLocal      = false;
+        foundInfo.isWeakDef          = false;
+        foundInfo.foundInDylib       = this->mf;
+        foundInfo.value              = dyld3::MachOFile::read_uleb128(diag, p, trieEnd);
+        foundInfo.resolverFuncOffset = 0;
+        foundInfo.foundSymbolName    = symbolName;
+        if ( diag.hasError() )
+            return false;
+        switch ( flags & EXPORT_SYMBOL_FLAGS_KIND_MASK ) {
+            case EXPORT_SYMBOL_FLAGS_KIND_REGULAR:
+                if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) {
+                    foundInfo.kind = FoundSymbol::Kind::headerOffset;
+                    foundInfo.resolverFuncOffset = (uint32_t)dyld3::MachOFile::read_uleb128(diag, p, trieEnd);
+                }
+                else {
+                    foundInfo.kind = FoundSymbol::Kind::headerOffset;
+                }
+                if ( flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION )
+                    foundInfo.isWeakDef = true;
+                break;
+            case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL:
+                foundInfo.isThreadLocal = true;
+                break;
+            case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE:
+                foundInfo.kind = FoundSymbol::Kind::absolute;
+                break;
+            default:
+                diag.error("unsupported exported symbol kind. flags=%llu at node offset=0x%0lX", flags, (long)(node-trieStart));
+                return false;
+        }
+        return true;
+    }
+    else {
+        // this is an old binary (before macOS 10.6), scan the symbol table
+        foundInfo.foundInDylib.reset();
+
+        SymbolTable symbolTable(*this);
+        symbolTable.forEachGlobalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type,
+                                                uint8_t n_sect, uint16_t n_desc, bool& stop) {
+            if ( strcmp(aSymbolName, symbolName) == 0 ) {
+                foundInfo.kind               = FoundSymbol::Kind::headerOffset;
+                foundInfo.isThreadLocal      = false;
+                foundInfo.foundInDylib       = this->mf;
+                foundInfo.value              = n_value - this->textUnslidVMAddr();
+                foundInfo.resolverFuncOffset = 0;
+                foundInfo.foundSymbolName    = symbolName;
+                stop = true;
+            }
+        });
+
+        // FIXME: Implement this
+#if 0
+        if ( !foundInfo.foundInDylib.has_value() ) {
+            // symbol not exported from this image. Search any re-exported dylibs
+            __block unsigned depIndex = 0;
+            forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
+                if ( isReExport && findDependent ) {
+                    if ( const MachOLoaded* depMH = findDependent(this, depIndex) ) {
+                        if ( depMH->findExportedSymbol(diag, symbolName, weakImport, foundInfo, findDependent) ) {
+                            stop = true;
+                        }
+                    }
+                }
+                ++depIndex;
+            });
+        }
+#endif
+        return foundInfo.foundInDylib.has_value();
+    }
+}
+
+// MARK: --- Fixups methods ---
+
+Fixups::Fixups(const Layout& layout)
+    : layout(layout)
+{
+}
+
+void Fixups::forEachBindTarget(Diagnostics& diag, bool allowLazyBinds, intptr_t slide,
+                               void (^handler)(const BindTargetInfo& info, bool& stop),
+                               void (^overrideHandler)(const BindTargetInfo& info, bool& stop)) const
+{
+    if ( this->layout.mf->isPreload() )
+        return;
+    if ( this->layout.mf->hasChainedFixups() )
+        this->forEachBindTarget_ChainedFixups(diag, handler);
+    else if ( this->layout.mf->hasOpcodeFixups() )
+        this->forEachBindTarget_Opcodes(diag, allowLazyBinds, handler, overrideHandler);
+#if SUPPORT_CLASSIC_RELOCS
+    else
+        this->forEachBindTarget_Relocations(diag, slide, handler);
+#endif
+}
+
+void Fixups::forEachBindTarget_ChainedFixups(Diagnostics& diag, void (^handler)(const BindTargetInfo& info, bool& stop)) const
+{
+    __block unsigned targetIndex = 0;
+    this->forEachChainedFixupTarget(diag, ^(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop)  {
+        BindTargetInfo info;
+        info.targetIndex = targetIndex;
+        info.libOrdinal  = libOrdinal;
+        info.symbolName  = symbolName;
+        info.addend      = addend;
+        info.weakImport  = weakImport;
+        info.lazyBind    = false;
+        handler(info, stop);
+       ++targetIndex;
+    });
+
+    // The C++ spec says main executables can define non-weak functions which override weak-defs in dylibs
+    // This happens automatically for anything bound at launch, but the dyld cache is pre-bound so we need
+    // to patch any binds that are overridden by this non-weak in the main executable.
+    if ( diag.noError() && this->layout.mf->isMainExecutable() && this->layout.mf->hasWeakDefs() ) {
+        dyld3::MachOFile::forEachTreatAsWeakDef(^(const char* symbolName) {
+            BindTargetInfo info;
+            info.targetIndex = targetIndex;
+            info.libOrdinal  = BIND_SPECIAL_DYLIB_WEAK_LOOKUP;
+            info.symbolName  = symbolName;
+            info.addend      = 0;
+            info.weakImport  = false;
+            info.lazyBind    = false;
+            bool stop = false;
+            handler(info, stop);
+           ++targetIndex;
+        });
+    }
+}
+
+void Fixups::parseOrgArm64eChainedFixups(Diagnostics& diag,
+                                         void (^targetCount)(uint32_t totalTargets, bool& stop),
+                                         void (^addTarget)(bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal, uint8_t type, const char* symbolName, uint64_t addend, bool weakImport, bool& stop),
+                                         void (^addChainStart)(uint32_t segmentIndex, bool segIndexSet, uint64_t segmentOffset, uint16_t format, bool& stop)) const
+{
+    bool            stop    = false;
+
+    const uint32_t dylibCount = this->layout.mf->dependentDylibCount();
+
+    if ( this->layout.linkedit.regularBindOpcodes.hasValue() ) {
+        // process bind opcodes
+        const uint8_t*  p    = this->layout.linkedit.regularBindOpcodes.buffer;
+        const uint8_t*  end  = p + this->layout.linkedit.regularBindOpcodes.bufferSize;
+        uint8_t         type = 0;
+        uint64_t        segmentOffset = 0;
+        uint8_t         segmentIndex = 0;
+        const char*     symbolName = NULL;
+        int             libraryOrdinal = 0;
+        bool            segIndexSet = false;
+        bool            libraryOrdinalSet = false;
+        uint64_t        targetTableCount;
+        uint64_t        addend = 0;
+        bool            weakImport = false;
+        while ( !stop && diag.noError() && (p < end) ) {
+            uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
+            uint8_t opcode = *p & BIND_OPCODE_MASK;
+            ++p;
+            switch (opcode) {
+                case BIND_OPCODE_DONE:
+                    stop = true;
+                    break;
+                case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
+                    libraryOrdinal = immediate;
+                    libraryOrdinalSet = true;
+                    break;
+                case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
+                    libraryOrdinal = (int)dyld3::MachOFile::read_uleb128(diag, p, end);
+                    libraryOrdinalSet = true;
+                    break;
+                case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
+                    // the special ordinals are negative numbers
+                    if ( immediate == 0 )
+                        libraryOrdinal = 0;
+                    else {
+                        int8_t signExtended = BIND_OPCODE_MASK | immediate;
+                        libraryOrdinal = signExtended;
+                    }
+                    libraryOrdinalSet = true;
+                    break;
+                case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
+                    weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 );
+                    symbolName = (char*)p;
+                    while (*p != '\0')
+                        ++p;
+                    ++p;
+                    break;
+                case BIND_OPCODE_SET_TYPE_IMM:
+                    type = immediate;
+                    break;
+                case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+                    segmentIndex = immediate;
+                    segmentOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+                    segIndexSet = true;
+                    break;
+                case BIND_OPCODE_SET_ADDEND_SLEB:
+                    addend = dyld3::MachOFile::read_sleb128(diag, p, end);
+                    break;
+                case BIND_OPCODE_DO_BIND:
+                    if ( addTarget )
+                        addTarget(libraryOrdinalSet, dylibCount, libraryOrdinal, type, symbolName, addend, weakImport, stop);
+                    break;
+                case BIND_OPCODE_THREADED:
+                    switch (immediate) {
+                        case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB:
+                            targetTableCount = dyld3::MachOFile::read_uleb128(diag, p, end);
+                            if ( targetTableCount > 65535 ) {
+                                diag.error("BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB size too large");
+                                stop = true;
+                            }
+                            else {
+                                if ( targetCount )
+                                    targetCount((uint32_t)targetTableCount, stop);
+                            }
+                            break;
+                        case BIND_SUBOPCODE_THREADED_APPLY:
+                            if ( addChainStart )
+                                addChainStart(segmentIndex, segIndexSet, segmentOffset, DYLD_CHAINED_PTR_ARM64E, stop);
+                            break;
+                        default:
+                            diag.error("bad BIND_OPCODE_THREADED sub-opcode 0x%02X", immediate);
+                    }
+                    break;
+                default:
+                    diag.error("bad bind opcode 0x%02X", immediate);
+            }
+        }
+        if ( diag.hasError() )
+            return;
+    }
+}
+
+void Fixups::forEachChainedFixupTarget(Diagnostics& diag, void (^callback)(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop)) const
+{
+    if ( this->layout.linkedit.regularBindOpcodes.hasValue() ) {
+        parseOrgArm64eChainedFixups(diag, nullptr, ^(bool libraryOrdinalSet, uint32_t dylibCount,
+                                                    int libOrdinal, uint8_t type, const char* symbolName, uint64_t fixAddend, bool weakImport, bool& stopChain) {
+            callback(libOrdinal, symbolName, fixAddend, weakImport, stopChain);
+        }, nullptr);
+    }
+    else if ( this->layout.linkedit.chainedFixups.hasValue() ) {
+        const dyld_chained_fixups_header* header = (dyld_chained_fixups_header*)this->layout.linkedit.chainedFixups.buffer;
+        dyld3::MachOFile::forEachChainedFixupTarget(diag, header, this->layout.linkedit.chainedFixups.cmd, callback);
+    }
+}
+
+#if (BUILDING_DYLD || BUILDING_LIBDYLD) && !__arm64e__
+  #define SUPPORT_OLD_ARM64E_FORMAT 0
+#else
+  #define SUPPORT_OLD_ARM64E_FORMAT 1
+#endif
+
+// find dyld_chained_starts_in_image* in image
+// if old arm64e binary, synthesize dyld_chained_starts_in_image*
+void Fixups::withThreadedRebaseAsChainStarts(Diagnostics& diag, void (^callback)(const dyld_chained_fixups_header* header, uint64_t fixupsSize)) const
+{
+#if SUPPORT_OLD_ARM64E_FORMAT
+    // don't want this code in non-arm64e dyld because it causes a stack protector which dereferences a GOT pointer before GOT is set up
+    // old arm64e binary, create a dyld_chained_starts_in_image for caller
+    uint64_t baseAddress = ((const Header*)this->layout.mf)->preferredLoadAddress();
+    uint64_t imagePageCount = this->layout.mf->mappedSize()/0x4000;
+    size_t bufferSize = this->layout.linkedit.regularBindOpcodes.bufferSize + (size_t)imagePageCount*sizeof(uint16_t) + 512;
+    BLOCK_ACCCESSIBLE_ARRAY(uint8_t, buffer, bufferSize);
+    uint8_t* bufferEnd = &buffer[bufferSize];
+    dyld_chained_fixups_header* header = (dyld_chained_fixups_header*)buffer;
+    header->fixups_version = 0;
+    header->starts_offset  = sizeof(dyld_chained_fixups_header);
+    header->imports_offset = 0;
+    header->symbols_offset = 0;
+    header->imports_count  = 0;
+    header->imports_format = 0;
+    header->symbols_format = 0;
+    dyld_chained_starts_in_image* starts = (dyld_chained_starts_in_image*)(dyld_chained_starts_in_image*)((uint8_t*)header + header->starts_offset);
+    starts->seg_count = (uint32_t)this->layout.segments.size();
+    for (uint32_t i=0; i < starts->seg_count; ++i)
+        starts->seg_info_offset[i] = 0;
+    __block uint8_t curSegIndex = 0;
+    __block dyld_chained_starts_in_segment* curSeg = (dyld_chained_starts_in_segment*)(&(starts->seg_info_offset[starts->seg_count]));
+    parseOrgArm64eChainedFixups(diag, nullptr, nullptr, ^(uint32_t segmentIndex, bool segIndexSet, uint64_t segmentOffset, uint16_t format, bool& stop) {
+        uint32_t pageIndex = (uint32_t)(segmentOffset/0x1000);
+        if ( segmentIndex != curSegIndex ) {
+            if ( curSegIndex == 0 ) {
+                starts->seg_info_offset[segmentIndex] = (uint32_t)((uint8_t*)curSeg - (uint8_t*)starts);
+            }
+            else {
+                starts->seg_info_offset[segmentIndex] = (uint32_t)((uint8_t*)(&curSeg->page_start[curSeg->page_count]) - (uint8_t*)starts);
+                curSeg = (dyld_chained_starts_in_segment*)((uint8_t*)starts+starts->seg_info_offset[segmentIndex]);
+                assert((uint8_t*)curSeg < bufferEnd);
+           }
+           curSeg->page_count = 0;
+           curSegIndex = segmentIndex;
+        }
+        while ( curSeg->page_count != pageIndex ) {
+            assert((uint8_t*)(&curSeg->page_start[curSeg->page_count]) < bufferEnd);
+            curSeg->page_start[curSeg->page_count] = 0xFFFF;
+            curSeg->page_count++;
+        }
+        curSeg->size                  = (uint32_t)((uint8_t*)(&curSeg->page_start[pageIndex]) - (uint8_t*)curSeg);
+        curSeg->page_size             = 0x1000; // old arm64e encoding used 4KB pages
+        curSeg->pointer_format        = DYLD_CHAINED_PTR_ARM64E;
+        curSeg->segment_offset        = this->layout.segments[segmentIndex].vmAddr - baseAddress;
+        curSeg->max_valid_pointer     = 0;
+        curSeg->page_count            = pageIndex+1;
+        assert((uint8_t*)(&curSeg->page_start[pageIndex]) < bufferEnd);
+        curSeg->page_start[pageIndex] = segmentOffset & 0xFFF;
+        //fprintf(stderr, "segment_offset=0x%llX, vmAddr=0x%llX\n", curSeg->segment_offset, segments[segmentIndex].vmAddr );
+        //printf("segIndex=%d, segOffset=0x%08llX, page_start[%d]=0x%04X, page_start[%d]=0x%04X\n",
+        //        segmentIndex, segmentOffset, pageIndex, curSeg->page_start[pageIndex], pageIndex-1, pageIndex ? curSeg->page_start[pageIndex-1] : 0);
+    });
+    callback(header, (uint64_t)bufferSize);
+#endif
+}
+
+const dyld_chained_fixups_header* Fixups::chainedFixupsHeader() const {
+    if ( this->layout.linkedit.chainedFixups.hasValue() ) {
+        // find dyld_chained_starts_in_image from dyld_chained_fixups_header
+        return (dyld_chained_fixups_header*)this->layout.linkedit.chainedFixups.buffer;
+    }
+    return nullptr;
+}
+
+// find dyld_chained_starts_in_image* in image
+// if old arm64e binary, synthesize dyld_chained_starts_in_image*
+void Fixups::withChainStarts(Diagnostics& diag, void (^callback)(const dyld_chained_starts_in_image*)) const
+{
+    if ( const dyld_chained_fixups_header* chainHeader = this->chainedFixupsHeader() ) {
+        // find dyld_chained_starts_in_image from dyld_chained_fixups_header
+        callback((dyld_chained_starts_in_image*)((uint8_t*)chainHeader + chainHeader->starts_offset));
+    }
+#if SUPPORT_OLD_ARM64E_FORMAT
+    // don't want this code in non-arm64e dyld because it causes a stack protector which dereferences a GOT pointer before GOT is set up
+    else if ( this->layout.linkedit.regularBindOpcodes.hasValue() && (this->layout.mf->cputype == CPU_TYPE_ARM64) && (this->layout.mf->maskedCpuSubtype() == CPU_SUBTYPE_ARM64E) ) {
+        // old arm64e binary, create a dyld_chained_starts_in_image for caller
+        this->withThreadedRebaseAsChainStarts(diag, ^(const dyld_chained_fixups_header* header, uint64_t fixupsSize) {
+            callback((dyld_chained_starts_in_image*)((uint8_t*)header + header->starts_offset));
+        });
+    }
+#endif
+    else {
+        diag.error("image does not use chained fixups");
+    }
+}
+
+void Fixups::forEachFixupInAllChains(Diagnostics& diag, const dyld_chained_starts_in_image* starts, bool notifyNonPointers,
+                                     void (^handler)(ChainedFixupPointerOnDisk* fixupLocation, uint64_t fixupSegmentOffset,
+                                                     const dyld_chained_starts_in_segment* segInfo, bool& stop)) const
+{
+
+    bool stopped = false;
+    for (uint32_t segIndex=0; segIndex < starts->seg_count && !stopped; ++segIndex) {
+        if ( starts->seg_info_offset[segIndex] == 0 )
+            continue;
+        const dyld_chained_starts_in_segment* segInfo = (dyld_chained_starts_in_segment*)((uint8_t*)starts + starts->seg_info_offset[segIndex]);
+        auto adaptor = ^(ChainedFixupPointerOnDisk* fixupLocation, uint64_t fixupSegmentOffset, bool& stop) {
+            handler(fixupLocation, fixupSegmentOffset, segInfo, stop);
+        };
+        forEachFixupInSegmentChains(diag, segInfo, segIndex, notifyNonPointers, adaptor);
+    }
+}
+
+void Fixups::forEachFixupInSegmentChains(Diagnostics& diag, const dyld_chained_starts_in_segment* segInfo, uint32_t segIndex, bool notifyNonPointers,
+                                         void (^handler)(ChainedFixupPointerOnDisk* fixupLocation, uint64_t fixupSegmentOffset, bool& stop)) const
+{
+    const uint8_t* segmentBuffer = this->layout.segments[segIndex].buffer;
+    auto adaptor = ^(ChainedFixupPointerOnDisk* fixupLocation, bool& stop) {
+        uint64_t fixupSegmentOffset = (uint64_t)fixupLocation - (uint64_t)segmentBuffer;
+         handler(fixupLocation, fixupSegmentOffset, stop);
+    };
+    bool stopped = false;
+    for (uint32_t pageIndex=0; pageIndex < segInfo->page_count && !stopped; ++pageIndex) {
+        uint16_t offsetInPage = segInfo->page_start[pageIndex];
+        if ( offsetInPage == DYLD_CHAINED_PTR_START_NONE )
+            continue;
+        const uint8_t* pageContentStart = segmentBuffer + (pageIndex * segInfo->page_size);
+        if ( offsetInPage & DYLD_CHAINED_PTR_START_MULTI ) {
+            // 32-bit chains which may need multiple starts per page
+            uint32_t overflowIndex = offsetInPage & ~DYLD_CHAINED_PTR_START_MULTI;
+            bool chainEnd = false;
+            while (!stopped && !chainEnd) {
+                chainEnd = (segInfo->page_start[overflowIndex] & DYLD_CHAINED_PTR_START_LAST);
+                offsetInPage = (segInfo->page_start[overflowIndex] & ~DYLD_CHAINED_PTR_START_LAST);
+                ChainedFixupPointerOnDisk* chain = (ChainedFixupPointerOnDisk*)(pageContentStart+offsetInPage);
+
+                stopped = dyld3::MachOFile::walkChain(diag, chain, segInfo->pointer_format, notifyNonPointers, segInfo->max_valid_pointer, adaptor);
+                ++overflowIndex;
+            }
+        }
+        else {
+            // one chain per page
+            ChainedFixupPointerOnDisk* chain = (ChainedFixupPointerOnDisk*)(pageContentStart+offsetInPage);
+            stopped = dyld3::MachOFile::walkChain(diag, chain, segInfo->pointer_format, notifyNonPointers, segInfo->max_valid_pointer, adaptor);
+        }
+    }
+}
+
+void Fixups::forEachFixupChainSegment(Diagnostics& diag, const dyld_chained_starts_in_image* starts,
+                                      void (^handler)(const dyld_chained_starts_in_segment* segInfo, uint32_t segIndex, bool& stop))
+{
+    bool stopped = false;
+    for (uint32_t segIndex=0; segIndex < starts->seg_count && !stopped; ++segIndex) {
+        if ( starts->seg_info_offset[segIndex] == 0 )
+            continue;
+        const dyld_chained_starts_in_segment* segInfo = (dyld_chained_starts_in_segment*)((uint8_t*)starts + starts->seg_info_offset[segIndex]);
+        handler(segInfo, segIndex, stopped);
+    }
+}
+
+uint16_t Fixups::chainedPointerFormat() const
+{
+    if ( const dyld_chained_fixups_header* chainHeader = this->chainedFixupsHeader() ) {
+        // get pointer format from chain info struct in LINKEDIT
+        return dyld3::MachOFile::chainedPointerFormat(chainHeader);
+    }
+    assert(this->layout.mf->cputype == CPU_TYPE_ARM64
+           && (this->layout.mf->maskedCpuSubtype() == CPU_SUBTYPE_ARM64E)
+           && "chainedPointerFormat() called on non-chained binary");
+    return DYLD_CHAINED_PTR_ARM64E;
+}
+
+// walk through all binds, unifying weak, lazy, and regular binds
+void Fixups::forEachBindUnified_Opcodes(Diagnostics& diag, bool allowLazyBinds,
+                                        void (^handler)(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& targetInfo, bool& stop),
+                                        void (^overrideHandler)(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& targetInfo, bool& stop)) const
+{
+    {
+        __block unsigned         targetIndex = 0;
+        __block BindTargetInfo   targetInfo;
+        BindDetailedHandler binder =  ^(const char* opcodeName,
+                                        bool segIndexSet,  bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal,
+                                        uint32_t pointerSize, uint32_t segmentIndex, uint64_t segmentOffset,
+                                        uint8_t type, const char* symbolName, bool weakImport, bool lazyBind,
+                                        uint64_t addend, bool targetOrAddendChanged, bool& stop) {
+            uint64_t bindVmOffset  = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+            uint64_t runtimeOffset = bindVmOffset - this->layout.textUnslidVMAddr();
+            if ( targetOrAddendChanged ) {
+                targetInfo.targetIndex = targetIndex++;
+                targetInfo.libOrdinal  = libOrdinal;
+                targetInfo.symbolName  = symbolName;
+                targetInfo.addend      = addend;
+                targetInfo.weakImport  = weakImport;
+                targetInfo.lazyBind    = lazyBind && allowLazyBinds;
+            }
+            handler(runtimeOffset, segmentIndex, targetInfo, stop);
+        };
+        bool stopped = this->forEachBind_OpcodesRegular(diag, binder);
+        if ( stopped )
+            return;
+        stopped = this->forEachBind_OpcodesLazy(diag, binder);
+        if ( stopped )
+            return;
+    }
+
+    // Opcode based weak-binds effectively override other binds/rebases.  Process them last
+    // To match dyld2, they are allowed to fail to find a target, in which case the normal rebase/bind will
+    // not be overridden.
+    {
+        __block unsigned         weakTargetIndex = 0;
+        __block BindTargetInfo   weakTargetInfo;
+        BindDetailedHandler weakBinder =  ^(const char* opcodeName,
+                                            bool segIndexSet,  bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal,
+                                            uint32_t pointerSize, uint32_t segmentIndex, uint64_t segmentOffset,
+                                            uint8_t type, const char* symbolName, bool weakImport, bool lazyBind,
+                                            uint64_t addend, bool targetOrAddendChanged, bool& stop) {
+
+            uint64_t bindVmOffset  = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+            uint64_t runtimeOffset = bindVmOffset - this->layout.textUnslidVMAddr();
+            if ( (symbolName != weakTargetInfo.symbolName) || (strcmp(symbolName, weakTargetInfo.symbolName) != 0) || (weakTargetInfo.addend != addend) ) {
+                weakTargetInfo.targetIndex = weakTargetIndex++;
+                weakTargetInfo.libOrdinal  = BIND_SPECIAL_DYLIB_WEAK_LOOKUP;
+                weakTargetInfo.symbolName  = symbolName;
+                weakTargetInfo.addend      = addend;
+                weakTargetInfo.weakImport  = false;
+                weakTargetInfo.lazyBind    = false;
+            }
+            overrideHandler(runtimeOffset, segmentIndex, weakTargetInfo, stop);
+        };
+        auto strongHandler = ^(const char* strongName) { };
+        this->forEachBind_OpcodesWeak(diag, weakBinder, strongHandler);
+    }
+}
+
+void Fixups::forEachBindTarget_Opcodes(Diagnostics& diag, bool allowLazyBinds,
+                                       void (^handler)(const BindTargetInfo& info, bool& stop),
+                                       void (^overrideHandler)(const BindTargetInfo& info, bool& stop)) const
+{
+    __block unsigned lastTargetIndex = -1;
+    __block unsigned lastWeakBindTargetIndex = -1;
+    this->forEachBindUnified_Opcodes(diag, allowLazyBinds,
+                                     ^(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& targetInfo, bool& stop) {
+        // Regular/lazy binds
+        if ( lastTargetIndex != targetInfo.targetIndex) {
+            handler(targetInfo, stop);
+            lastTargetIndex = targetInfo.targetIndex;
+        }
+    }, ^(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& targetInfo, bool& stop) {
+        // Weak binds
+        if ( lastWeakBindTargetIndex != targetInfo.targetIndex) {
+            overrideHandler(targetInfo, stop);
+            lastWeakBindTargetIndex = targetInfo.targetIndex;
+        }
+    });
+}
+
+bool Fixups::forEachBind_OpcodesLazy(Diagnostics& diag, BindDetailedHandler handler) const
+{
+    if ( !this->layout.linkedit.lazyBindOpcodes.hasValue() )
+        return false;
+
+    uint32_t        lazyDoneCount   = 0;
+    uint32_t        lazyBindCount   = 0;
+    const uint32_t  ptrSize         = this->layout.mf->pointerSize();
+    bool            stop            = false;
+    const uint32_t  dylibCount      = this->layout.mf->dependentDylibCount();
+    const uint8_t*  p               = this->layout.linkedit.lazyBindOpcodes.buffer;
+    const uint8_t*  end             = p + this->layout.linkedit.lazyBindOpcodes.bufferSize;
+    uint8_t         type            = BIND_TYPE_POINTER;
+    uint64_t        segmentOffset   = 0;
+    uint8_t         segmentIndex    = 0;
+    const char*     symbolName      = NULL;
+    int             libraryOrdinal  = 0;
+    bool            segIndexSet     = false;
+    bool            libraryOrdinalSet = false;
+    int64_t         addend          = 0;
+    bool            weakImport = false;
+    while (  !stop && diag.noError() && (p < end) ) {
+        uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
+        uint8_t opcode = *p & BIND_OPCODE_MASK;
+        ++p;
+        switch (opcode) {
+            case BIND_OPCODE_DONE:
+                // this opcode marks the end of each lazy pointer binding
+                ++lazyDoneCount;
+                break;
+            case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
+                libraryOrdinal = immediate;
+                libraryOrdinalSet = true;
+                break;
+            case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
+                libraryOrdinal = (int)dyld3::MachOFile::read_uleb128(diag, p, end);
+                libraryOrdinalSet = true;
+                break;
+            case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
+                // the special ordinals are negative numbers
+                if ( immediate == 0 )
+                    libraryOrdinal = 0;
+                else {
+                    int8_t signExtended = BIND_OPCODE_MASK | immediate;
+                    libraryOrdinal = signExtended;
+                }
+                libraryOrdinalSet = true;
+                break;
+            case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
+                weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 );
+                symbolName = (char*)p;
+                while (*p != '\0')
+                    ++p;
+                ++p;
+                break;
+            case BIND_OPCODE_SET_ADDEND_SLEB:
+                addend = dyld3::MachOFile::read_sleb128(diag, p, end);
+                break;
+            case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+                segmentIndex = immediate;
+                segmentOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+                segIndexSet = true;
+                break;
+            case BIND_OPCODE_DO_BIND:
+                handler("BIND_OPCODE_DO_BIND", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+                        ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, true, addend, true, stop);
+                segmentOffset += ptrSize;
+                ++lazyBindCount;
+                break;
+            case BIND_OPCODE_SET_TYPE_IMM:
+            case BIND_OPCODE_ADD_ADDR_ULEB:
+            case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
+            case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
+            case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
+            default:
+                diag.error("bad lazy bind opcode 0x%02X", opcode);
+                break;
+        }
+    }
+    if ( lazyDoneCount > lazyBindCount+7 ) {
+        // diag.error("lazy bind opcodes missing binds");
+    }
+    return stop;
+}
+
+
+
+bool Fixups::forEachBind_OpcodesWeak(Diagnostics& diag, BindDetailedHandler handler,  void (^strongHandler)(const char* symbolName)) const
+{
+    if ( !this->layout.linkedit.weakBindOpcodes.hasValue() )
+        return false;
+
+    const uint32_t  ptrSize         = this->layout.mf->pointerSize();
+    bool            stop            = false;
+    const uint32_t  dylibCount      = this->layout.mf->dependentDylibCount();
+    const uint8_t*  p               = this->layout.linkedit.weakBindOpcodes.buffer;
+    const uint8_t*  end             = p + this->layout.linkedit.weakBindOpcodes.bufferSize;
+    uint8_t         type            = BIND_TYPE_POINTER;
+    uint64_t        segmentOffset   = 0;
+    uint8_t         segmentIndex    = 0;
+    const char*     symbolName      = NULL;
+    int             libraryOrdinal  = BIND_SPECIAL_DYLIB_WEAK_LOOKUP;
+    bool            segIndexSet     = false;
+    bool            libraryOrdinalSet = true;
+    int64_t         addend          = 0;
+    bool            weakImport      = false;
+    bool            targetOrAddendChanged   = true;
+    bool            done            = false;
+    uint64_t        count;
+    uint64_t        skip;
+    while ( !stop && diag.noError() && (p < end) && !done ) {
+        uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
+        uint8_t opcode = *p & BIND_OPCODE_MASK;
+        ++p;
+        switch (opcode) {
+            case BIND_OPCODE_DONE:
+                done = true;
+                break;
+            case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
+            case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
+            case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
+                diag.error("unexpected dylib ordinal in weak_bind");
+                break;
+            case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
+                weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 );
+                symbolName = (char*)p;
+                while (*p != '\0')
+                    ++p;
+                ++p;
+                if ( immediate & BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION ) {
+                    strongHandler(symbolName);
+                }
+                targetOrAddendChanged = true;
+                break;
+            case BIND_OPCODE_SET_TYPE_IMM:
+                type = immediate;
+                break;
+            case BIND_OPCODE_SET_ADDEND_SLEB:
+                addend = dyld3::MachOFile::read_sleb128(diag, p, end);
+                targetOrAddendChanged = true;
+                break;
+            case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+                segmentIndex = immediate;
+                segmentOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+                segIndexSet = true;
+                break;
+            case BIND_OPCODE_ADD_ADDR_ULEB:
+                segmentOffset += dyld3::MachOFile::read_uleb128(diag, p, end);
+                break;
+            case BIND_OPCODE_DO_BIND:
+                handler("BIND_OPCODE_DO_BIND", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+                        ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+                segmentOffset += ptrSize;
+                targetOrAddendChanged = false;
+                break;
+            case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
+                handler("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+                        ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+                segmentOffset += dyld3::MachOFile::read_uleb128(diag, p, end) + ptrSize;
+                 targetOrAddendChanged = false;
+               break;
+            case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
+                handler("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+                        ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+                segmentOffset += immediate*ptrSize + ptrSize;
+                targetOrAddendChanged = false;
+                break;
+            case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
+                count = dyld3::MachOFile::read_uleb128(diag, p, end);
+                skip = dyld3::MachOFile::read_uleb128(diag, p, end);
+                for (uint32_t i=0; i < count; ++i) {
+                    handler("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+                            ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+                    segmentOffset += skip + ptrSize;
+                    targetOrAddendChanged = false;
+                    if ( stop )
+                        break;
+                }
+                break;
+            default:
+                diag.error("bad bind opcode 0x%02X", *p);
+        }
+    }
+    return stop;
+}
+
+bool Fixups::forEachBind_OpcodesRegular(Diagnostics& diag, BindDetailedHandler handler) const
+{
+    if ( !this->layout.linkedit.regularBindOpcodes.hasValue() )
+        return false;
+
+    const uint32_t  ptrSize         = this->layout.mf->pointerSize();
+    bool            stop            = false;
+    const uint32_t  dylibCount      = this->layout.mf->dependentDylibCount();
+    const uint8_t*  p               = this->layout.linkedit.regularBindOpcodes.buffer;
+    const uint8_t*  end             = p + this->layout.linkedit.regularBindOpcodes.bufferSize;
+    uint8_t         type            = 0;
+    uint64_t        segmentOffset   = 0;
+    uint8_t         segmentIndex    = 0;
+    const char*     symbolName      = NULL;
+    int             libraryOrdinal  = 0;
+    bool            segIndexSet     = false;
+    bool            libraryOrdinalSet = false;
+    bool            targetOrAddendChanged   = false;
+    bool            done            = false;
+    int64_t         addend          = 0;
+    uint64_t        count;
+    uint64_t        skip;
+    bool            weakImport = false;
+    while ( !stop && diag.noError() && (p < end) && !done ) {
+        uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
+        uint8_t opcode = *p & BIND_OPCODE_MASK;
+        ++p;
+        switch (opcode) {
+            case BIND_OPCODE_DONE:
+                done = true;
+                break;
+            case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
+                libraryOrdinal = immediate;
+                libraryOrdinalSet = true;
+                break;
+            case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
+                libraryOrdinal = (int)dyld3::MachOFile::read_uleb128(diag, p, end);
+                libraryOrdinalSet = true;
+                break;
+            case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
+                // the special ordinals are negative numbers
+                if ( immediate == 0 )
+                    libraryOrdinal = 0;
+                else {
+                    int8_t signExtended = BIND_OPCODE_MASK | immediate;
+                    libraryOrdinal = signExtended;
+                }
+                libraryOrdinalSet = true;
+                break;
+            case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
+                weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 );
+                symbolName = (char*)p;
+                while (*p != '\0')
+                    ++p;
+                ++p;
+                targetOrAddendChanged = true;
+                break;
+            case BIND_OPCODE_SET_TYPE_IMM:
+                type = immediate;
+                break;
+            case BIND_OPCODE_SET_ADDEND_SLEB:
+                addend = dyld3::MachOFile::read_sleb128(diag, p, end);
+                targetOrAddendChanged = true;
+                break;
+            case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+                segmentIndex = immediate;
+                segmentOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+                segIndexSet = true;
+                break;
+            case BIND_OPCODE_ADD_ADDR_ULEB:
+                segmentOffset += dyld3::MachOFile::read_uleb128(diag, p, end);
+                break;
+            case BIND_OPCODE_DO_BIND:
+                handler("BIND_OPCODE_DO_BIND", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+                        ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+                segmentOffset += ptrSize;
+                targetOrAddendChanged = false;
+                break;
+            case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
+                handler("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+                        ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+                segmentOffset += dyld3::MachOFile::read_uleb128(diag, p, end) + ptrSize;
+                targetOrAddendChanged = false;
+                break;
+            case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
+                handler("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+                        ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+                segmentOffset += immediate*ptrSize + ptrSize;
+                targetOrAddendChanged = false;
+                break;
+            case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
+                count = dyld3::MachOFile::read_uleb128(diag, p, end);
+                skip = dyld3::MachOFile::read_uleb128(diag, p, end);
+                for (uint32_t i=0; i < count; ++i) {
+                    handler("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+                            ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+                    segmentOffset += skip + ptrSize;
+                    targetOrAddendChanged = false;
+                    if ( stop )
+                        break;
+                }
+                break;
+            default:
+                diag.error("bad bind opcode 0x%02X", *p);
+        }
+    }
+    return stop;
+}
+
+void Fixups::forEachBindLocation_Opcodes(Diagnostics& diag, void (^handler)(uint64_t runtimeOffset, uint32_t segmentIndex, unsigned targetIndex, bool& stop),
+                                         void (^overrideHandler)(uint64_t runtimeOffset, uint32_t segmentIndex, unsigned overrideBindTargetIndex, bool& stop)) const
+{
+    this->forEachBindUnified_Opcodes(diag, false,
+                                     ^(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& targetInfo, bool& stop) {
+        handler(runtimeOffset, segmentIndex, targetInfo.targetIndex, stop);
+    }, ^(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& weakTargetInfo, bool& stop) {
+        overrideHandler(runtimeOffset, segmentIndex, weakTargetInfo.targetIndex, stop);
+    });
+}
+
+void Fixups::forEachBindLocation_Relocations(Diagnostics& diag, void (^handler)(uint64_t runtimeOffset, unsigned targetIndex,
+                                                                                bool& stop)) const
+{
+    // As we don't need the private externs workaround, we also don't need a slide here
+    bool supportPrivateExternsWorkaround = false;
+    intptr_t unusedSlide = 0;
+
+    __block int targetIndex = -1;
+    this->forEachBind_Relocations(diag, supportPrivateExternsWorkaround, unusedSlide,
+                                  ^(const char* opcodeName,
+                                    bool segIndexSet,  bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal,
+                                    uint32_t pointerSize, uint32_t segmentIndex, uint64_t segmentOffset,
+                                    uint8_t type, const char* symbolName, bool weakImport, bool lazyBind,
+                                    uint64_t addend, bool targetOrAddendChanged, bool& stop) {
+        if ( targetOrAddendChanged )
+            ++targetIndex;
+        uint64_t bindVMAddr  = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+        uint64_t runtimeOffset = bindVMAddr - this->layout.textUnslidVMAddr();
+        handler(runtimeOffset, targetIndex, stop);
+    });
+}
+
+// old binary, walk external relocations and indirect symbol table
+void Fixups::forEachBindTarget_Relocations(Diagnostics& diag, intptr_t slide,
+                                           void (^handler)(const BindTargetInfo& info, bool& stop)) const
+{
+    __block unsigned targetIndex = 0;
+    this->forEachBind_Relocations(diag, true, slide,
+                                  ^(const char* opcodeName,
+                                    bool segIndexSet,  bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal,
+                                    uint32_t pointerSize, uint32_t segmentIndex, uint64_t segmentOffset,
+                                    uint8_t type, const char* symbolName, bool weakImport, bool lazyBind,
+                                    uint64_t addend, bool targetOrAddendChanged, bool& stop) {
+        if ( targetOrAddendChanged ) {
+            BindTargetInfo info;
+            info.targetIndex = targetIndex;
+            info.libOrdinal  = libOrdinal;
+            info.symbolName  = symbolName;
+            info.addend      = addend;
+            info.weakImport  = weakImport;
+            info.lazyBind    = lazyBind;
+            handler(info, stop);
+           ++targetIndex;
+        }
+    });
+}
+
+bool Fixups::forEachRebaseLocation_Opcodes(Diagnostics& diag, void (^handler)(uint64_t runtimeOffset, uint32_t segmentIndex, bool& stop)) const
+{
+    return this->forEachRebase_Opcodes(diag,
+                                       ^(const char* opcodeName, bool segIndexSet, uint32_t pointerSize,
+                                         uint8_t segmentIndex, uint64_t segmentOffset, Rebase kind,
+                                         bool& stop) {
+        uint64_t rebaseVMAddr = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+        uint64_t runtimeOffset  = rebaseVMAddr - this->layout.textUnslidVMAddr();
+        handler(runtimeOffset, segmentIndex, stop);
+    });
+}
+
+void Fixups::forEachRebase(Diagnostics& diag, void (^callback)(uint64_t runtimeOffset, uint64_t rebasedValue, bool& stop)) const
+{
+    if ( !this->layout.linkedit.rebaseOpcodes.hasValue() )
+        return;
+
+    const bool is64 = this->layout.mf->is64();
+    this->forEachRebase_Opcodes(diag, ^(const char* opcodeName, bool segIndexSet, uint32_t pointerSize,
+                                        uint8_t segmentIndex, uint64_t segmentOffset, Rebase kind,
+                                        bool& stop) {
+        uint64_t rebaseVMAddr = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+        uint64_t runtimeOffset  = rebaseVMAddr - this->layout.textUnslidVMAddr();
+        const uint8_t* fixupLoc = this->layout.segments[segmentIndex].buffer + segmentOffset;
+        uint64_t targetVMAddr = 0;
+        if ( is64 ) {
+            targetVMAddr = *(uint64_t*)fixupLoc;
+        } else {
+            targetVMAddr = *(uint32_t*)fixupLoc;
+        }
+        callback(runtimeOffset, targetVMAddr, stop);
+    });
+}
+
+bool Fixups::forEachRebase_Opcodes(Diagnostics& diag, RebaseDetailHandler handler) const
+{
+    const bool is64 = this->layout.mf->is64();
+    const Rebase pointerRebaseKind = is64 ? Rebase::pointer64 : Rebase::pointer32;
+    assert(this->layout.linkedit.rebaseOpcodes.hasValue());
+
+    const uint8_t* const start = this->layout.linkedit.rebaseOpcodes.buffer;
+    const uint8_t* const end   = start + this->layout.linkedit.rebaseOpcodes.bufferSize;
+    const uint8_t* p           = start;
+    const uint32_t ptrSize     = this->layout.mf->pointerSize();
+    Rebase   kind = Rebase::unknown;
+    int      segIndex = 0;
+    uint64_t segOffset = 0;
+    uint64_t count;
+    uint64_t skip;
+    bool     segIndexSet = false;
+    bool     stop = false;
+    while ( !stop && diag.noError() && (p < end) ) {
+        uint8_t immediate = *p & REBASE_IMMEDIATE_MASK;
+        uint8_t opcode = *p & REBASE_OPCODE_MASK;
+        ++p;
+        switch (opcode) {
+            case REBASE_OPCODE_DONE:
+                // Allow some padding, in case rebases were somehow aligned to 16-bytes in size
+                if ( (end - p) > 15 )
+                    diag.error("rebase opcodes terminated early at offset %d of %d", (int)(p-start), (int)(end-start));
+                stop = true;
+                break;
+            case REBASE_OPCODE_SET_TYPE_IMM:
+                switch ( immediate ) {
+                    case REBASE_TYPE_POINTER:
+                        kind = pointerRebaseKind;
+                        break;
+                    case REBASE_TYPE_TEXT_ABSOLUTE32:
+                        kind = Rebase::textAbsolute32;
+                        break;
+                    case REBASE_TYPE_TEXT_PCREL32:
+                        kind = Rebase::textPCrel32;
+                        break;
+                    default:
+                        kind = Rebase::unknown;
+                        break;
+                }
+                break;
+            case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+                segIndex = immediate;
+                segOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+                segIndexSet = true;
+                break;
+            case REBASE_OPCODE_ADD_ADDR_ULEB:
+                segOffset += dyld3::MachOFile::read_uleb128(diag, p, end);
+                break;
+            case REBASE_OPCODE_ADD_ADDR_IMM_SCALED:
+                segOffset += immediate*ptrSize;
+                break;
+            case REBASE_OPCODE_DO_REBASE_IMM_TIMES:
+                for (int i=0; i < immediate; ++i) {
+                    handler("REBASE_OPCODE_DO_REBASE_IMM_TIMES", segIndexSet, ptrSize, segIndex, segOffset, kind, stop);
+                    segOffset += ptrSize;
+                    if ( stop )
+                        break;
+                }
+                break;
+            case REBASE_OPCODE_DO_REBASE_ULEB_TIMES:
+                count = dyld3::MachOFile::read_uleb128(diag, p, end);
+                for (uint32_t i=0; i < count; ++i) {
+                    handler("REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", segIndexSet, ptrSize, segIndex, segOffset, kind, stop);
+                    segOffset += ptrSize;
+                    if ( stop )
+                        break;
+                }
+                break;
+            case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB:
+                handler("REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", segIndexSet, ptrSize, segIndex, segOffset, kind, stop);
+                segOffset += dyld3::MachOFile::read_uleb128(diag, p, end) + ptrSize;
+                break;
+            case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB:
+                count = dyld3::MachOFile::read_uleb128(diag, p, end);
+                if ( diag.hasError() )
+                    break;
+                skip = dyld3::MachOFile::read_uleb128(diag, p, end);
+                for (uint32_t i=0; i < count; ++i) {
+                    handler("REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB", segIndexSet, ptrSize, segIndex, segOffset, kind, stop);
+                    segOffset += skip + ptrSize;
+                    if ( stop )
+                        break;
+                }
+                break;
+            default:
+                diag.error("unknown rebase opcode 0x%02X", opcode);
+        }
+    }
+    return stop;
+}
+
+bool Fixups::forEachRebaseLocation_Relocations(Diagnostics& diag,
+                                               void (^handler)(uint64_t runtimeOffset, uint32_t segmentIndex,
+                                                               bool& stop)) const
+{
+    return this->forEachRebase_Relocations(diag, ^(const char* opcodeName, bool segIndexSet,
+                                                   uint32_t pointerSize, uint8_t segmentIndex,
+                                                   uint64_t segmentOffset, Rebase kind, bool& stop) {
+        uint64_t rebaseVmOffset = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+        uint64_t runtimeOffset  = rebaseVmOffset - this->layout.textUnslidVMAddr();
+        handler(runtimeOffset, segmentIndex, stop);
+    });
+}
+
+#if SUPPORT_CLASSIC_RELOCS
+// relocs are normally sorted, we don't want to use qsort because it may switch to mergesort which uses malloc
+static void sortRelocations(dyld3::Array<relocation_info>& relocs)
+{
+    // The kernel linker has malloc, and old-style relocations are extremely common.  So use qsort
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+    ::qsort(&relocs[0], (size_t)relocs.count(), sizeof(relocation_info),
+            [](const void* l, const void* r) -> int {
+                if ( ((relocation_info*)l)->r_address < ((relocation_info*)r)->r_address )
+                    return -1;
+                else
+                    return 1;
+    });
+#else
+    uint64_t count = relocs.count();
+    for (uint64_t i=0; i < count-1; ++i) {
+        bool done = true;
+        for (uint64_t j=0; j < count-i-1; ++j) {
+            if ( relocs[j].r_address > relocs[j+1].r_address ) {
+                relocation_info temp = relocs[j];
+                relocs[j]   = relocs[j+1];
+                relocs[j+1] = temp;
+                done = false;
+            }
+        }
+        if ( done )
+            break;
+    }
+#endif
+}
+
+bool Fixups::forEachRebase_Relocations(Diagnostics& diag, RebaseDetailHandler handler) const
+{
+    // old binary, walk relocations
+    bool                            is64Bit     = this->layout.mf->is64();
+    const uint8_t                   ptrSize     = this->layout.mf->pointerSize();
+    const uint64_t                  relocsStartAddress = localRelocBaseAddress();
+    const relocation_info* const    relocsStart = (const relocation_info*)this->layout.linkedit.localRelocs.buffer;
+    const relocation_info* const    relocsEnd   = &relocsStart[this->layout.linkedit.localRelocs.entryCount];
+    const uint8_t                   relocSize   = (is64Bit ? 3 : 2);
+    bool                            stop        = false;
+    STACK_ALLOC_OVERFLOW_SAFE_ARRAY(relocation_info, relocs, 2048);
+    for (const relocation_info* reloc=relocsStart; (reloc < relocsEnd) && !stop; ++reloc) {
+        if ( reloc->r_length != relocSize ) {
+            bool shouldEmitError = true;
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+            if ( this->layout.mf->usesClassicRelocationsInKernelCollection() && (reloc->r_length == 2) && (relocSize == 3) )
+                shouldEmitError = false;
+#endif
+            if ( shouldEmitError ) {
+                diag.error("local relocation has wrong r_length");
+                break;
+            }
+        }
+        if ( reloc->r_type != 0 ) { // 0 == X86_64_RELOC_UNSIGNED == GENERIC_RELOC_VANILLA ==  ARM64_RELOC_UNSIGNED
+            diag.error("local relocation has wrong r_type");
+            break;
+        }
+        relocs.push_back(*reloc);
+    }
+    if ( !relocs.empty() ) {
+        sortRelocations(relocs);
+        for (relocation_info reloc : relocs) {
+            uint32_t addrOff = reloc.r_address;
+            uint32_t segIndex  = 0;
+            uint64_t segOffset = 0;
+            uint64_t addr = 0;
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+            // xnu for x86_64 has __HIB mapped before __DATA, so offsets appear to be
+            // negative
+            if ( this->layout.mf->isStaticExecutable() || this->layout.mf->isFileSet() ) {
+                addr = relocsStartAddress + (int32_t)addrOff;
+            } else {
+                addr = relocsStartAddress + addrOff;
+            }
+#else
+            addr = relocsStartAddress + addrOff;
+#endif
+            if ( segIndexAndOffsetForAddress(addr, segIndex, segOffset) ) {
+                Rebase kind = (reloc.r_length == 2) ? Rebase::pointer32 : Rebase::pointer64;
+                if ( this->layout.mf->cputype == CPU_TYPE_I386 ) {
+                    if ( this->layout.segments[segIndex].executable() )
+                        kind = Rebase::textAbsolute32;
+                }
+                handler("local relocation", true, ptrSize, segIndex, segOffset, kind, stop);
+            }
+            else {
+                diag.error("local relocation has out of range r_address");
+                break;
+            }
+        }
+    }
+    // then process indirect symbols
+    const Rebase pointerRebaseKind = is64Bit ? Rebase::pointer64 : Rebase::pointer32;
+    intptr_t unusedSlide = 0;
+    forEachIndirectPointer(diag, false, unusedSlide,
+                           ^(uint64_t address, bool bind, int bindLibOrdinal,
+                             const char* bindSymbolName, bool bindWeakImport, bool bindLazy,
+                             bool selfModifyingStub, bool& indStop) {
+        if ( bind )
+           return;
+        uint32_t segIndex  = 0;
+        uint64_t segOffset = 0;
+        if ( segIndexAndOffsetForAddress(address, segIndex, segOffset) ) {
+            handler("local relocation", true, ptrSize, segIndex, segOffset, pointerRebaseKind, indStop);
+        }
+        else {
+            diag.error("local relocation has out of range r_address");
+            indStop = true;
+        }
+    });
+
+    return stop;
+}
+
+
+bool Fixups::forEachBind_Relocations(Diagnostics& diag, bool supportPrivateExternsWorkaround,
+                                     intptr_t slide, BindDetailedHandler handler) const
+{
+    // Firmare binaries won't have a dynSymTab
+    if ( !this->layout.linkedit.externRelocs.hasValue() )
+        return false;
+
+    const uint64_t                  relocsStartAddress = externalRelocBaseAddress();
+    const relocation_info* const    relocsStart = (const relocation_info*)this->layout.linkedit.externRelocs.buffer;
+    const relocation_info* const    relocsEnd   = &relocsStart[this->layout.linkedit.externRelocs.entryCount];
+    bool                            is64Bit     = this->layout.mf->is64() ;
+    const uint32_t                  ptrSize     = this->layout.mf->pointerSize();
+    const uint32_t                  dylibCount  = this->layout.mf->dependentDylibCount();
+    const uint8_t                   relocSize   = (is64Bit ? 3 : 2);
+    const void*                     symbolTable = this->layout.linkedit.symbolTable.buffer;
+    const struct nlist_64*          symbols64   = (nlist_64*)symbolTable;
+    const struct nlist*             symbols32   = (struct nlist*)symbolTable;
+    const char*                     stringPool  = (char*)this->layout.linkedit.symbolStrings.buffer;
+    uint32_t                        symCount    = this->layout.linkedit.symbolTable.entryCount;
+    uint32_t                        poolSize    = this->layout.linkedit.symbolStrings.bufferSize;
+    uint32_t                        lastSymIndx = -1;
+    uint64_t                        lastAddend  = 0;
+    bool                            stop        = false;
+    for (const relocation_info* reloc=relocsStart; (reloc < relocsEnd) && !stop; ++reloc) {
+        bool isBranch = false;
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+        if ( this->layout.mf->isKextBundle() ) {
+            // kext's may have other kinds of relocations, eg, branch relocs.  Skip them
+            if ( this->layout.mf->isArch("x86_64") || this->layout.mf->isArch("x86_64h") ) {
+                if ( reloc->r_type == X86_64_RELOC_BRANCH ) {
+                    if ( reloc->r_length != 2 ) {
+                        diag.error("external relocation has wrong r_length");
+                        break;
+                    }
+                    if ( reloc->r_pcrel != true ) {
+                        diag.error("external relocation should be pcrel");
+                        break;
+                    }
+                    isBranch = true;
+                }
+            }
+        }
+#endif
+        if ( !isBranch ) {
+            if ( reloc->r_length != relocSize ) {
+                diag.error("external relocation has wrong r_length");
+                break;
+            }
+            if ( reloc->r_type != 0 ) { // 0 == X86_64_RELOC_UNSIGNED == GENERIC_RELOC_VANILLA == ARM64_RELOC_UNSIGNED
+                diag.error("external relocation has wrong r_type");
+                break;
+            }
+        }
+        uint32_t segIndex  = 0;
+        uint64_t segOffset = 0;
+        if ( segIndexAndOffsetForAddress(relocsStartAddress+reloc->r_address, segIndex, segOffset) ) {
+            uint32_t symbolIndex = reloc->r_symbolnum;
+            if ( symbolIndex > symCount ) {
+                diag.error("external relocation has out of range r_symbolnum");
+                break;
+            }
+            else {
+                uint32_t strOffset  = is64Bit ? symbols64[symbolIndex].n_un.n_strx : symbols32[symbolIndex].n_un.n_strx;
+                uint16_t n_desc     = is64Bit ? symbols64[symbolIndex].n_desc : symbols32[symbolIndex].n_desc;
+                uint8_t  n_type     = is64Bit ? symbols64[symbolIndex].n_type : symbols32[symbolIndex].n_type;
+                uint32_t libOrdinal = libOrdinalFromDesc(n_desc);
+                if ( strOffset >= poolSize ) {
+                    diag.error("external relocation has r_symbolnum=%d which has out of range n_strx", symbolIndex);
+                    break;
+                }
+                else {
+                    const char*     symbolName = stringPool + strOffset;
+                    bool            weakImport = (n_desc & N_WEAK_REF);
+                    const uint8_t*  content    = this->layout.segments[segIndex].buffer + segOffset;
+                    uint64_t        addend     = (reloc->r_length == 3) ? *((uint64_t*)content) : *((uint32_t*)content);
+                    // Handle defined weak def symbols which need to get a special ordinal
+                    if ( ((n_type & N_TYPE) == N_SECT) && ((n_type & N_EXT) != 0) && ((n_desc & N_WEAK_DEF) != 0) )
+                        libOrdinal = BIND_SPECIAL_DYLIB_WEAK_LOOKUP;
+                    uint8_t type = isBranch ? BIND_TYPE_TEXT_PCREL32 : BIND_TYPE_POINTER;
+                    bool targetOrAddendChanged = (lastSymIndx != symbolIndex) || (lastAddend != addend);
+                    handler("external relocation", true, true, dylibCount, libOrdinal,
+                             ptrSize, segIndex, segOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+                    lastSymIndx = symbolIndex;
+                    lastAddend  = addend;
+                }
+            }
+        }
+        else {
+            diag.error("local relocation has out of range r_address");
+            break;
+        }
+    }
+    // then process indirect symbols
+    forEachIndirectPointer(diag, supportPrivateExternsWorkaround, slide,
+                           ^(uint64_t address, bool bind, int bindLibOrdinal,
+                             const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& indStop) {
+        if ( !bind )
+           return;
+        uint32_t segIndex  = 0;
+        uint64_t segOffset = 0;
+        if ( segIndexAndOffsetForAddress(address, segIndex, segOffset) ) {
+            handler("indirect symbol", true, true, dylibCount, bindLibOrdinal,
+                     ptrSize, segIndex, segOffset, BIND_TYPE_POINTER, bindSymbolName, bindWeakImport, bindLazy, 0, true, indStop);
+        }
+        else {
+            diag.error("indirect symbol has out of range address");
+            indStop = true;
+        }
+    });
+
+    return false;
+}
+#endif // SUPPORT_CLASSIC_RELOCS
+
+void Fixups::forEachIndirectPointer(Diagnostics& diag, bool supportPrivateExternsWorkaround, intptr_t slide,
+                                    void (^handler)(uint64_t pointerAddress, bool bind, int bindLibOrdinal, const char* bindSymbolName,
+                                                    bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& stop)) const
+{
+    // find lazy and non-lazy pointer sections
+    const bool              is64Bit                  = this->layout.mf->is64();
+    const uint32_t* const   indirectSymbolTable      = (uint32_t*)this->layout.linkedit.indirectSymbolTable.buffer;
+    const uint32_t          indirectSymbolTableCount = this->layout.linkedit.indirectSymbolTable.entryCount;
+    const uint32_t          ptrSize                  = this->layout.mf->pointerSize();
+    const void*             symbolTable              = this->layout.linkedit.symbolTable.buffer;
+    const struct nlist_64*  symbols64                = (nlist_64*)symbolTable;
+    const struct nlist*     symbols32                = (struct nlist*)symbolTable;
+    const char*             stringPool               = (char*)this->layout.linkedit.symbolStrings.buffer;
+    uint32_t                symCount                 = this->layout.linkedit.symbolTable.entryCount;
+    uint32_t                poolSize                 = this->layout.linkedit.symbolStrings.bufferSize;
+    __block bool            stop                     = false;
+
+    // Old kexts put S_LAZY_SYMBOL_POINTERS on the __got section, even if they didn't have indirect symbols to prcess.
+    // In that case, skip the loop as there shouldn't be anything to process
+    if ( (indirectSymbolTableCount == 0) && this->layout.mf->isKextBundle() )
+        return;
+
+    ((const Header*)this->layout.mf)->forEachSection(^(const Header::SectionInfo& sectInfo, bool& sectionStop) {
+        uint8_t  sectionType  = (sectInfo.flags & SECTION_TYPE);
+        bool selfModifyingStub = (sectionType == S_SYMBOL_STUBS) && (sectInfo.flags & S_ATTR_SELF_MODIFYING_CODE) && (sectInfo.reserved2 == 5) && (this->layout.mf->cputype == CPU_TYPE_I386);
+        if ( (sectionType != S_LAZY_SYMBOL_POINTERS) && (sectionType != S_NON_LAZY_SYMBOL_POINTERS) && !selfModifyingStub )
+            return;
+        if ( (sectInfo.flags & S_ATTR_SELF_MODIFYING_CODE) && !selfModifyingStub ) {
+            diag.error("S_ATTR_SELF_MODIFYING_CODE section type only valid in old i386 binaries");
+            sectionStop = true;
+            return;
+        }
+        uint32_t elementSize = selfModifyingStub ? sectInfo.reserved2 : ptrSize;
+        uint32_t elementCount = (uint32_t)(sectInfo.size/elementSize);
+        if ( dyld3::greaterThanAddOrOverflow(sectInfo.reserved1, elementCount, indirectSymbolTableCount) ) {
+            diag.error("section %.*s overflows indirect symbol table", (int)sectInfo.sectionName.size(), sectInfo.sectionName.data());
+            sectionStop = true;
+            return;
+        }
+
+        for (uint32_t i=0; (i < elementCount) && !stop; ++i) {
+            uint32_t symNum = indirectSymbolTable[sectInfo.reserved1 + i];
+            if ( symNum == INDIRECT_SYMBOL_ABS )
+                continue;
+            if ( symNum == INDIRECT_SYMBOL_LOCAL ) {
+                handler(sectInfo.address+i*elementSize, false, 0, "", false, false, false, stop);
+                continue;
+            }
+            if ( symNum > symCount ) {
+                diag.error("indirect symbol[%d] = %d which is invalid symbol index", sectInfo.reserved1 + i, symNum);
+                sectionStop = true;
+                return;
+            }
+            uint16_t n_desc = is64Bit ? symbols64[symNum].n_desc : symbols32[symNum].n_desc;
+            uint8_t  n_type     = is64Bit ? symbols64[symNum].n_type : symbols32[symNum].n_type;
+            uint32_t libOrdinal = libOrdinalFromDesc(n_desc);
+            uint32_t strOffset = is64Bit ? symbols64[symNum].n_un.n_strx : symbols32[symNum].n_un.n_strx;
+            if ( strOffset > poolSize ) {
+               diag.error("symbol[%d] string offset out of range", sectInfo.reserved1 + i);
+                sectionStop = true;
+                return;
+            }
+            const char* symbolName  = stringPool + strOffset;
+            bool        weakImport  = (n_desc & N_WEAK_REF);
+            bool        lazy        = (sectionType == S_LAZY_SYMBOL_POINTERS);
+#if SUPPORT_PRIVATE_EXTERNS_WORKAROUND
+            if ( lazy && ((n_type & N_PEXT) != 0) ) {
+                // don't know why the static linker did not eliminate the internal reference to a private extern definition
+                // As this is private extern, we know the symbol lookup will fail.  We also know that this is a lazy-bind, and so
+                // there is a corresponding rebase.  The rebase will be run later, and will slide whatever value is in here.
+                // So lets change the value in this slot, and let the existing rebase slide it for us
+                // Note we only want to change the value in memory once, before rebases are applied.  We don't want to accidentally
+                // change it again later.
+                if ( supportPrivateExternsWorkaround ) {
+                    uintptr_t* ptr = (uintptr_t*)((uint8_t*)(sectInfo.address+i*elementSize) + slide);
+                    uint64_t n_value = is64Bit ? symbols64[symNum].n_value : symbols32[symNum].n_value;
+                    *ptr = (uintptr_t)n_value;
+                }
+                continue;
+            }
+#endif
+            // Handle defined weak def symbols which need to get a special ordinal
+            if ( ((n_type & N_TYPE) == N_SECT) && ((n_type & N_EXT) != 0) && ((n_desc & N_WEAK_DEF) != 0) )
+                libOrdinal = BIND_SPECIAL_DYLIB_WEAK_LOOKUP;
+            handler(sectInfo.address+i*elementSize, true, libOrdinal, symbolName, weakImport, lazy, selfModifyingStub, stop);
+        }
+        sectionStop = stop;
+    });
+}
+
+uint64_t Fixups::localRelocBaseAddress() const
+{
+    if ( this->layout.mf->isArch("x86_64") || this->layout.mf->isArch("x86_64h") ) {
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+        if ( this->layout.mf->isKextBundle() ) {
+            // for kext bundles the reloc base address starts at __TEXT segment
+            return this->layout.segments[0].vmAddr;
+        }
+#endif
+        // for all other kinds, the x86_64 reloc base address starts at first writable segment (usually __DATA)
+        for ( const SegmentLayout& segment : this->layout.segments ) {
+            if ( segment.writable() )
+                return segment.vmAddr;
+        }
+    }
+    return this->layout.segments[0].vmAddr;
+}
+
+uint64_t Fixups::externalRelocBaseAddress() const
+{
+    // Dyld caches are too large for a raw r_address, so everything is an offset from the base address
+    if ( this->layout.mf->inDyldCache() ) {
+        return ((const Header*)this->layout.mf)->preferredLoadAddress();
+    }
+
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+    if ( this->layout.mf->isKextBundle() ) {
+        // for kext bundles the reloc base address starts at __TEXT segment
+        return ((const Header*)this->layout.mf)->preferredLoadAddress();
+    }
+#endif
+
+    if ( this->layout.mf->isArch("x86_64") || this->layout.mf->isArch("x86_64h") ) {
+        // for x86_64 reloc base address starts at first writable segment (usually __DATA)
+        for ( const SegmentLayout& segment : this->layout.segments ) {
+            if ( segment.writable() )
+                return segment.vmAddr;
+        }
+    }
+    // For everyone else we start at 0
+    return 0;
+}
+
+bool Fixups::segIndexAndOffsetForAddress(uint64_t addr, uint32_t& segIndex, uint64_t& segOffset) const
+{
+    for (uint32_t i=0; i < this->layout.segments.size(); ++i) {
+        const SegmentLayout& segment = this->layout.segments[i];
+        if ( (segment.vmAddr <= addr) && (addr < (segment.vmAddr + segment.vmSize)) ) {
+            segIndex  = i;
+            segOffset = addr - segment.vmAddr;
+            return true;
+        }
+    }
+    return false;
+}
+
+int Fixups::libOrdinalFromDesc(uint16_t n_desc) const
+{
+    // -flat_namespace is always flat lookup
+    if ( (this->layout.mf->flags & MH_TWOLEVEL) == 0 )
+        return BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+
+    // extract byte from undefined symbol entry
+    int libIndex = GET_LIBRARY_ORDINAL(n_desc);
+    switch ( libIndex ) {
+        case SELF_LIBRARY_ORDINAL:
+            return BIND_SPECIAL_DYLIB_SELF;
+
+        case DYNAMIC_LOOKUP_ORDINAL:
+            return BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+
+        case EXECUTABLE_ORDINAL:
+            return BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE;
+    }
+
+    return libIndex;
+}
+
+// MARK: --- SplitSeg methods ---
+
+SplitSeg::SplitSeg(const Layout& layout)
+    : layout(layout)
+{
+}
+
+bool SplitSeg::hasMarker() const
+{
+    if ( !this->layout.linkedit.splitSegInfo.hasValue() )
+        return false;
+
+    return this->layout.linkedit.splitSegInfo.bufferSize == 0;
+}
+
+bool SplitSeg::isV1() const
+{
+    if ( !this->layout.linkedit.splitSegInfo.hasValue() )
+        return false;
+
+    const void* splitSegStart = this->layout.linkedit.splitSegInfo.buffer;
+    return (*(const uint8_t*)splitSegStart) != DYLD_CACHE_ADJ_V2_FORMAT;
+}
+
+bool SplitSeg::isV2() const
+{
+    if ( !this->layout.linkedit.splitSegInfo.hasValue() )
+        return false;
+
+    const void* splitSegStart = this->layout.linkedit.splitSegInfo.buffer;
+    return (*(const uint8_t*)splitSegStart) == DYLD_CACHE_ADJ_V2_FORMAT;
+}
+
+bool SplitSeg::hasValue() const
+{
+    return this->layout.linkedit.splitSegInfo.hasValue();
+}
+
+void SplitSeg::forEachReferenceV2(Diagnostics& diag, ReferenceCallbackV2 callback) const
+{
+    const uint8_t* infoStart = layout.linkedit.splitSegInfo.buffer;
+    const uint8_t* infoEnd = infoStart + layout.linkedit.splitSegInfo.bufferSize;
+
+    if ( *infoStart++ != DYLD_CACHE_ADJ_V2_FORMAT ) {
+        return;
+    }
+
+    // Whole         :== <count> FromToSection+
+    // FromToSection :== <from-sect-index> <to-sect-index> <count> ToOffset+
+    // ToOffset         :== <to-sect-offset-delta> <count> FromOffset+
+    // FromOffset     :== <kind> <count> <from-sect-offset-delta>
+    const uint8_t* p = infoStart;
+    uint64_t sectionCount = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+    for (uint64_t i=0; i < sectionCount; ++i) {
+        uint64_t fromSectionIndex = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+        uint64_t toSectionIndex = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+        uint64_t toOffsetCount = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+        uint64_t toSectionOffset = 0;
+        for (uint64_t j=0; j < toOffsetCount; ++j) {
+            uint64_t toSectionDelta = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+            uint64_t fromOffsetCount = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+            toSectionOffset += toSectionDelta;
+            for (uint64_t k=0; k < fromOffsetCount; ++k) {
+                uint64_t kind = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+                if ( kind > 13 ) {
+                    diag.error("bad kind (%llu) value in %s\n", kind, ((const Header*)this->layout.mf)->installName());
+                }
+                uint64_t fromSectDeltaCount = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+                uint64_t fromSectionOffset = 0;
+                for (uint64_t l=0; l < fromSectDeltaCount; ++l) {
+                    uint64_t delta = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+                    fromSectionOffset += delta;
+                    bool stop = false;
+                    callback(fromSectionIndex, fromSectionOffset, toSectionIndex, toSectionOffset, stop);
+                    if ( stop )
+                        return;
+                }
+            }
+        }
+    }
+}
+
+
+void SplitSeg::forEachSplitSegSection(void (^callback)(std::string_view segmentName,
+                                                       std::string_view sectionName,
+                                                       uint64_t sectionVMAddr)) const
+{
+    callback("mach header", "", 0);
+    ((const Header*)this->layout.mf)->forEachSection(^(const Header::SectionInfo &sectInfo, bool &stop) {
+        callback(sectInfo.segmentName, sectInfo.sectionName, sectInfo.address);
+    });
+}
+
+// MARK: --- ExportTrie methods ---
+
+ExportTrie::ExportTrie(const Layout& layout)
+    : layout(layout)
+{
+}
+
+static void recurseTrie(Diagnostics& diag, const uint8_t* const start, const uint8_t* p, const uint8_t* const end,
+                        dyld3::OverflowSafeArray<char>& cummulativeString, int curStrOffset, bool& stop,
+                        ExportTrie::ExportsCallback callback)
+{
+    if ( p >= end ) {
+        diag.error("malformed trie, node past end");
+        return;
+    }
+    const uint64_t terminalSize = dyld3::MachOFile::read_uleb128(diag, p, end);
+    const uint8_t* children = p + terminalSize;
+    if ( terminalSize != 0 ) {
+        uint64_t    imageOffset = 0;
+        uint64_t    flags       = dyld3::MachOFile::read_uleb128(diag, p, end);
+        uint64_t    other       = 0;
+        const char* importName  = nullptr;
+        if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) {
+            other = dyld3::MachOFile::read_uleb128(diag, p, end); // dylib ordinal
+            importName = (char*)p;
+        }
+        else {
+            imageOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+            if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER )
+                other = dyld3::MachOFile::read_uleb128(diag, p, end);
+            else
+                other = 0;
+        }
+        if ( diag.hasError() )
+            return;
+        callback(cummulativeString.begin(), imageOffset, flags, other, importName, stop);
+        if ( stop )
+            return;
+    }
+    if ( children > end ) {
+        diag.error("malformed trie, terminalSize extends beyond trie data");
+        return;
+    }
+    const uint8_t childrenCount = *children++;
+    const uint8_t* s = children;
+    for (uint8_t i=0; i < childrenCount; ++i) {
+        int edgeStrLen = 0;
+        while (*s != '\0') {
+            cummulativeString.resize(curStrOffset+edgeStrLen + 1);
+            cummulativeString[curStrOffset+edgeStrLen] = *s++;
+            ++edgeStrLen;
+            if ( s > end ) {
+                diag.error("malformed trie node, child node extends past end of trie\n");
+                return;
+            }
+       }
+        cummulativeString.resize(curStrOffset+edgeStrLen + 1);
+        cummulativeString[curStrOffset+edgeStrLen] = *s++;
+        uint64_t childNodeOffset = dyld3::MachOFile::read_uleb128(diag, s, end);
+        if (childNodeOffset == 0) {
+            diag.error("malformed trie, childNodeOffset==0");
+            return;
+        }
+        recurseTrie(diag, start, start+childNodeOffset, end, cummulativeString, curStrOffset+edgeStrLen, stop, callback);
+        if ( diag.hasError() || stop )
+            return;
+    }
+}
+
+void ExportTrie::forEachExportedSymbol(Diagnostics& diag, ExportsCallback callback) const
+{
+    if ( layout.linkedit.exportsTrie.hasValue() ) {
+        const uint8_t* trieStart   = layout.linkedit.exportsTrie.buffer;
+        const uint8_t* trieEnd     = trieStart + layout.linkedit.exportsTrie.bufferSize;
+
+        // We still emit empty export trie load commands just as a placeholder to show we have
+        // no exports.  In that case, don't start recursing as we'll immediately think we ran
+        // of the end of the buffer
+        if ( trieStart == trieEnd )
+            return;
+
+        bool stop = false;
+        STACK_ALLOC_OVERFLOW_SAFE_ARRAY(char, cummulativeString, 4096);
+        recurseTrie(diag, trieStart, trieStart, trieEnd, cummulativeString, 0, stop, callback);
+   }
+}
+
+// MARK: --- ChainedFixupPointerOnDisk methods ---
+
+uint64_t ChainedFixupPointerOnDisk::Arm64e::unpackTarget() const
+{
+    assert(this->authBind.bind == 0);
+    assert(this->authBind.auth == 0);
+    return ((uint64_t)(this->rebase.high8) << 56) | (this->rebase.target);
+}
+
+uint64_t ChainedFixupPointerOnDisk::Arm64e::signExtendedAddend() const
+{
+    assert(this->authBind.bind == 1);
+    assert(this->authBind.auth == 0);
+    uint64_t addend19 = this->bind.addend;
+    if ( addend19 & 0x40000 )
+        return addend19 | 0xFFFFFFFFFFFC0000ULL;
+    else
+        return addend19;
+}
+
+const char* ChainedFixupPointerOnDisk::Arm64e::keyName(uint8_t keyBits)
+{
+    static const char* const names[] = {
+        "IA", "IB", "DA", "DB"
+    };
+    assert(keyBits < 4);
+    return names[keyBits];
+}
+const char* ChainedFixupPointerOnDisk::Arm64e::keyName() const
+{
+    assert(this->authBind.auth == 1);
+    return keyName(this->authBind.key);
+}
+
+uint64_t ChainedFixupPointerOnDisk::Arm64e::signPointer(uint64_t unsignedAddr, void* loc, bool addrDiv, uint16_t diversity, uint8_t key)
+{
+    // don't sign NULL
+    if ( unsignedAddr == 0 )
+        return 0;
+
+#if __has_feature(ptrauth_calls)
+    uint64_t extendedDiscriminator = diversity;
+    if ( addrDiv )
+        extendedDiscriminator = __builtin_ptrauth_blend_discriminator(loc, extendedDiscriminator);
+    switch ( key ) {
+        case 0: // IA
+            return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 0, extendedDiscriminator);
+        case 1: // IB
+            return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 1, extendedDiscriminator);
+        case 2: // DA
+            return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 2, extendedDiscriminator);
+        case 3: // DB
+            return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 3, extendedDiscriminator);
+    }
+    assert(0 && "invalid signing key");
+#else
+    assert(0 && "arm64e signing only arm64e");
+#endif
+}
+
+
+uint64_t ChainedFixupPointerOnDisk::Arm64e::signPointer(void* loc, uint64_t target) const
+{
+    assert(this->authBind.auth == 1);
+    return signPointer(target, loc, authBind.addrDiv, authBind.diversity, authBind.key);
+}
+
+uint64_t ChainedFixupPointerOnDisk::Generic64::unpackedTarget() const
+{
+    return (((uint64_t)this->rebase.high8) << 56) | (uint64_t)(this->rebase.target);
+}
+
+uint64_t ChainedFixupPointerOnDisk::Generic64::signExtendedAddend() const
+{
+    uint64_t addend27     = this->bind.addend;
+    uint64_t top8Bits     = addend27 & 0x00007F80000ULL;
+    uint64_t bottom19Bits = addend27 & 0x0000007FFFFULL;
+    uint64_t newValue     = (top8Bits << 13) | (((uint64_t)(bottom19Bits << 37) >> 37) & 0x00FFFFFFFFFFFFFF);
+    return newValue;
+}
+
+const char* ChainedFixupPointerOnDisk::Kernel64::keyName() const
+{
+    static const char* names[] = {
+        "IA", "IB", "DA", "DB"
+    };
+    assert(this->isAuth == 1);
+    uint8_t keyBits = this->key;
+    assert(keyBits < 4);
+    return names[keyBits];
+}
+
+uint64_t ChainedFixupPointerOnDisk::Cache64e::high8() const
+{
+    assert(this->regular.auth == 0);
+    return ((uint64_t)(this->regular.high8) << 56);
+}
+
+const char* ChainedFixupPointerOnDisk::Cache64e::keyName() const
+{
+    assert(this->auth.auth == 1);
+    static const char* const names[] = {
+        "IA", "DA"
+    };
+    assert(this->auth.keyIsData < 2);
+    return names[this->auth.keyIsData];
+}
+
+uint64_t ChainedFixupPointerOnDisk::Cache64e::signPointer(uint64_t unsignedAddr, void* loc, bool addrDiv, uint16_t diversity, uint8_t keyIsData)
+{
+    // don't sign NULL
+    if ( unsignedAddr == 0 )
+        return 0;
+
+#if __has_feature(ptrauth_calls)
+    uint64_t extendedDiscriminator = diversity;
+    if ( addrDiv )
+        extendedDiscriminator = __builtin_ptrauth_blend_discriminator(loc, extendedDiscriminator);
+    switch ( keyIsData ) {
+        case 0: // IA
+            return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 0, extendedDiscriminator);
+        case 1: // DA
+            return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 2, extendedDiscriminator);
+    }
+    assert(0 && "invalid signing key");
+#else
+    assert(0 && "arm64e signing only arm64e");
+#endif
+}
+
+
+uint64_t ChainedFixupPointerOnDisk::Cache64e::signPointer(void* loc, uint64_t target) const
+{
+    assert(this->auth.auth == 1);
+    return signPointer(target, loc, auth.addrDiv, auth.diversity, auth.keyIsData);
+}
+
+bool ChainedFixupPointerOnDisk::isRebase(uint16_t pointerFormat, uint64_t preferedLoadAddress, uint64_t& targetRuntimeOffset) const
+{
+    switch (pointerFormat) {
+       case DYLD_CHAINED_PTR_ARM64E:
+       case DYLD_CHAINED_PTR_ARM64E_USERLAND:
+       case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
+       case DYLD_CHAINED_PTR_ARM64E_KERNEL:
+       case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
+            if ( this->arm64e.bind.bind )
+                return false;
+            if ( this->arm64e.authRebase.auth ) {
+                targetRuntimeOffset = this->arm64e.authRebase.target;
+                return true;
+            }
+            else {
+                targetRuntimeOffset = this->arm64e.unpackTarget();
+                if ( (pointerFormat == DYLD_CHAINED_PTR_ARM64E) || (pointerFormat == DYLD_CHAINED_PTR_ARM64E_FIRMWARE) ) {
+                    targetRuntimeOffset -= preferedLoadAddress;
+                }
+                return true;
+            }
+            break;
+        case DYLD_CHAINED_PTR_64:
+        case DYLD_CHAINED_PTR_64_OFFSET:
+            if ( this->generic64.bind.bind )
+                return false;
+            targetRuntimeOffset = this->generic64.unpackedTarget();
+            if ( pointerFormat == DYLD_CHAINED_PTR_64 )
+                targetRuntimeOffset -= preferedLoadAddress;
+            return true;
+            break;
+        case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
+        case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
+            targetRuntimeOffset = this->kernel64.target;
+            return true;
+            break;
+        case DYLD_CHAINED_PTR_32:
+            if ( this->generic32.bind.bind )
+                return false;
+            targetRuntimeOffset = this->generic32.rebase.target - preferedLoadAddress;
+            return true;
+            break;
+        case DYLD_CHAINED_PTR_32_FIRMWARE:
+            targetRuntimeOffset = this->firmware32.target - preferedLoadAddress;
+            return true;
+            break;
+        case DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE:
+             if ( this->cache64e.regular.auth ) {
+                 targetRuntimeOffset = this->cache64e.auth.runtimeOffset;
+                 return true;
+             }
+             else {
+                 targetRuntimeOffset = this->cache64e.regular.runtimeOffset;
+                 return true;
+             }
+             break;
+        default:
+            break;
+    }
+    assert(0 && "unsupported pointer chain format");
+}
+
+bool ChainedFixupPointerOnDisk::isBind(uint16_t pointerFormat, uint32_t& bindOrdinal, int64_t& addend) const
+{
+    addend = 0;
+    switch (pointerFormat) {
+        case DYLD_CHAINED_PTR_ARM64E:
+        case DYLD_CHAINED_PTR_ARM64E_USERLAND:
+        case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
+        case DYLD_CHAINED_PTR_ARM64E_KERNEL:
+        case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
+            if ( !this->arm64e.authBind.bind )
+                return false;
+            if ( this->arm64e.authBind.auth ) {
+                if ( pointerFormat == DYLD_CHAINED_PTR_ARM64E_USERLAND24 )
+                    bindOrdinal = this->arm64e.authBind24.ordinal;
+                else
+                    bindOrdinal = this->arm64e.authBind.ordinal;
+                return true;
+            }
+            else {
+                if ( pointerFormat == DYLD_CHAINED_PTR_ARM64E_USERLAND24 )
+                    bindOrdinal = this->arm64e.bind24.ordinal;
+                else
+                    bindOrdinal = this->arm64e.bind.ordinal;
+                addend = this->arm64e.signExtendedAddend();
+                return true;
+            }
+            break;
+        case DYLD_CHAINED_PTR_64:
+        case DYLD_CHAINED_PTR_64_OFFSET:
+            if ( !this->generic64.bind.bind )
+                return false;
+            bindOrdinal = this->generic64.bind.ordinal;
+            addend = this->generic64.bind.addend;
+            return true;
+            break;
+        case DYLD_CHAINED_PTR_32:
+            if ( !this->generic32.bind.bind )
+                return false;
+            bindOrdinal = this->generic32.bind.ordinal;
+            addend = this->generic32.bind.addend;
+            return true;
+            break;
+        case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
+        case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
+            return false;
+        default:
+            break;
+    }
+    assert(0 && "unsupported pointer chain format");
+}
+
+unsigned ChainedFixupPointerOnDisk::strideSize(uint16_t pointerFormat)
+{
+    switch (pointerFormat) {
+        case DYLD_CHAINED_PTR_ARM64E:
+        case DYLD_CHAINED_PTR_ARM64E_USERLAND:
+        case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
+        case DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE:
+            return 8;
+        case DYLD_CHAINED_PTR_ARM64E_KERNEL:
+        case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
+        case DYLD_CHAINED_PTR_32_FIRMWARE:
+        case DYLD_CHAINED_PTR_64:
+        case DYLD_CHAINED_PTR_64_OFFSET:
+        case DYLD_CHAINED_PTR_32:
+        case DYLD_CHAINED_PTR_32_CACHE:
+        case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
+            return 4;
+        case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
+            return 1;
+    }
+    assert(0 && "unsupported pointer chain format");
+}
+
+// MARK: --- SymbolTable methods ---
+
+SymbolTable::SymbolTable(const Layout& layout)
+    : layout(layout)
+{
+}
+
+void SymbolTable::forEachLocalSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const
+{
+    const bool is64Bit = this->layout.mf->is64();
+    if ( this->layout.linkedit.symbolTable.hasValue() ) {
+        uint32_t localsStartIndex = 0;
+        uint32_t localsCount      = this->layout.linkedit.symbolTable.entryCount;
+        if ( this->layout.linkedit.localSymbolTable.hasValue() ) {
+            localsStartIndex = this->layout.linkedit.localSymbolTable.entryIndex;
+            localsCount      = this->layout.linkedit.localSymbolTable.entryCount;
+        }
+        uint32_t               maxStringOffset  = this->layout.linkedit.symbolStrings.bufferSize;
+        const char*            stringPool       = (char*)this->layout.linkedit.symbolStrings.buffer;
+        const struct nlist*    symbols          = (struct nlist*)this->layout.linkedit.symbolTable.buffer;
+        const struct nlist_64* symbols64        = (struct nlist_64*)this->layout.linkedit.symbolTable.buffer;
+        bool                   stop             = false;
+        for (uint32_t i=0; (i < localsCount) && !stop; ++i) {
+            if ( is64Bit ) {
+                const struct nlist_64& sym = symbols64[localsStartIndex+i];
+                if ( sym.n_un.n_strx > maxStringOffset )
+                    continue;
+                if ( ((sym.n_type & N_EXT) == 0) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
+                    callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+            }
+            else {
+                const struct nlist& sym = symbols[localsStartIndex+i];
+                if ( sym.n_un.n_strx > maxStringOffset )
+                    continue;
+                if ( ((sym.n_type & N_EXT) == 0) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
+                    callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+            }
+        }
+    }
+}
+
+
+void SymbolTable::forEachGlobalSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const
+{
+    const bool is64Bit = this->layout.mf->is64();
+    if ( this->layout.linkedit.symbolTable.hasValue() ) {
+        uint32_t globalsStartIndex = 0;
+        uint32_t globalsCount      = this->layout.linkedit.symbolTable.entryCount;
+        if ( this->layout.linkedit.globalSymbolTable.hasValue() ) {
+            globalsStartIndex = this->layout.linkedit.globalSymbolTable.entryIndex;
+            globalsCount      = this->layout.linkedit.globalSymbolTable.entryCount;
+        }
+        uint32_t               maxStringOffset  = this->layout.linkedit.symbolStrings.bufferSize;
+        const char*            stringPool       = (char*)this->layout.linkedit.symbolStrings.buffer;
+        const struct nlist*    symbols          = (struct nlist*)this->layout.linkedit.symbolTable.buffer;
+        const struct nlist_64* symbols64        = (struct nlist_64*)this->layout.linkedit.symbolTable.buffer;
+        bool                   stop             = false;
+        for (uint32_t i=0; (i < globalsCount) && !stop; ++i) {
+            if ( is64Bit ) {
+                const struct nlist_64& sym = symbols64[globalsStartIndex+i];
+                if ( sym.n_un.n_strx > maxStringOffset )
+                    continue;
+                if ( (sym.n_type & N_EXT) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
+                    callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+            }
+            else {
+                const struct nlist& sym = symbols[globalsStartIndex+i];
+                if ( sym.n_un.n_strx > maxStringOffset )
+                    continue;
+                if ( (sym.n_type & N_EXT) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
+                    callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+            }
+        }
+    }
+}
+
+
+void SymbolTable::forEachImportedSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const
+{
+    const bool is64Bit = this->layout.mf->is64();
+    if ( this->layout.linkedit.symbolTable.hasValue() ) {
+        uint32_t undefsStartIndex = 0;
+        uint32_t undefsCount      = this->layout.linkedit.symbolTable.entryCount;
+        if ( this->layout.linkedit.undefSymbolTable.hasValue() ) {
+            undefsStartIndex = this->layout.linkedit.undefSymbolTable.entryIndex;
+            undefsCount      = this->layout.linkedit.undefSymbolTable.entryCount;
+        }
+        uint32_t               maxStringOffset  = this->layout.linkedit.symbolStrings.bufferSize;
+        const char*            stringPool       = (char*)this->layout.linkedit.symbolStrings.buffer;
+        const struct nlist*    symbols          = (struct nlist*)this->layout.linkedit.symbolTable.buffer;
+        const struct nlist_64* symbols64        = (struct nlist_64*)this->layout.linkedit.symbolTable.buffer;
+        bool                   stop             = false;
+        for (uint32_t i=0; (i < undefsCount) && !stop; ++i) {
+            if ( is64Bit ) {
+                const struct nlist_64& sym = symbols64[undefsStartIndex+i];
+                if ( sym.n_un.n_strx > maxStringOffset )
+                    continue;
+                if ( (sym.n_type & N_TYPE) == N_UNDF )
+                    callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+            }
+            else {
+                const struct nlist& sym = symbols[undefsStartIndex+i];
+                if ( sym.n_un.n_strx > maxStringOffset )
+                    continue;
+                if ( (sym.n_type & N_TYPE) == N_UNDF )
+                    callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+            }
+        }
+    }
+}
+
+void SymbolTable::forEachIndirectSymbol(Diagnostics& diag,
+                                        void (^callback)(const char* symbolName, uint32_t symNum)) const
+{
+    // find lazy and non-lazy pointer sections
+    const bool              is64Bit                  = this->layout.mf->is64();
+    const uint32_t* const   indirectSymbolTable      = (uint32_t*)this->layout.linkedit.indirectSymbolTable.buffer;
+    const uint32_t          indirectSymbolTableCount = this->layout.linkedit.indirectSymbolTable.entryCount;
+    const void*             symbolTable              = this->layout.linkedit.symbolTable.buffer;
+    const struct nlist_64*  symbols64                = (nlist_64*)symbolTable;
+    const struct nlist*     symbols32                = (struct nlist*)symbolTable;
+    const char*             stringPool               = (char*)this->layout.linkedit.symbolStrings.buffer;
+    uint32_t                symCount                 = this->layout.linkedit.symbolTable.entryCount;
+    uint32_t                poolSize                 = this->layout.linkedit.symbolStrings.bufferSize;
+
+    if ( indirectSymbolTableCount == 0 )
+        return;
+
+    for (uint32_t i = 0; i != indirectSymbolTableCount; ++i ) {
+        uint32_t symNum = indirectSymbolTable[i];
+        if ( symNum == INDIRECT_SYMBOL_ABS ) {
+            // FIXME: The client wants to know about all the entries, so what should we pass here?
+            callback(nullptr, symNum);
+            continue;
+        }
+        if ( symNum == INDIRECT_SYMBOL_LOCAL ) {
+            callback("", symNum);
+            continue;
+        }
+        if ( symNum == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS) ) {
+            // FIXME: We are using the "local" callback. Should we use the "abs" one instead
+            callback("", symNum);
+            continue;
+        }
+        if ( symNum > symCount ) {
+            diag.error("indirect symbol[%d] = %d which is invalid symbol index", i, symNum);
+            return;
+        }
+        uint32_t strOffset = is64Bit ? symbols64[symNum].n_un.n_strx : symbols32[symNum].n_un.n_strx;
+        if ( strOffset > poolSize ) {
+            diag.error("symbol[%d] string offset out of range", i);
+            return;
+        }
+        const char* symbolName  = stringPool + strOffset;
+        callback(symbolName, symNum);
+    }
+}
+
+} // namespace mach_o
+