Loading...
cache_builder/CacheDylib.cpp dyld-1340 dyld-1042.1
--- dyld/dyld-1340/cache_builder/CacheDylib.cpp
+++ dyld/dyld-1042.1/cache_builder/CacheDylib.cpp
@@ -22,34 +22,24 @@
 * @APPLE_LICENSE_HEADER_END@
 */
 
+#include "Allocator.h"
 #include "Array.h"
 #include "BuilderConfig.h"
 #include "BuilderOptions.h"
 #include "CacheDylib.h"
-#include "Chunk.h"
 #include "MachOFile.h"
 #include "MachOFileAbstraction.hpp"
-#include "Header.h"
 #include "ObjCVisitor.h"
 #include "Optimizers.h"
 #include "OptimizerObjC.h"
 #include "StringUtils.h"
 #include "Trie.hpp"
 
-// mach_o
-#include "Header.h"
-#include "Image.h"
-#include "FunctionVariants.h"
-
 #include <CommonCrypto/CommonHMAC.h>
 #include <CommonCrypto/CommonDigest.h>
 #include <CommonCrypto/CommonDigestSPI.h>
 
-#include <optional>
 #include <vector>
-
-// mach_o_writer
-#include "HeaderWriter.h"
 
 // FIXME: We should get this from cctools
 #define DYLD_CACHE_ADJ_V2_FORMAT 0x7F
@@ -71,10 +61,6 @@
 using namespace cache_builder;
 using dyld3::MachOFile;
 using error::Error;
-using mach_o::Header;
-using mach_o::Version32;
-using mach_o::Image;
-using mach_o::FunctionVariantFixups;
 
 //
 // MARK: --- cache_builder::CacheDylib methods ---
@@ -89,20 +75,8 @@
 CacheDylib::CacheDylib(InputFile& inputFile)
     : inputFile(&inputFile)
     , inputMF(inputFile.mf)
-    , inputHdr((const Header*)inputFile.mf)
-    , inputLoadAddress(this->inputHdr->preferredLoadAddress())
-    , installName(this->inputHdr->installName())
-{
-    if ( inputFile.mf )
-        inputImage = std::make_unique<mach_o::Image>(inputFile.mf, inputFile.size, Image::MappingKind::wholeSliceMapped);
-}
-
-CacheDylib::CacheDylib(std::string_view installName)
-    : inputFile(nullptr)
-    , inputMF(nullptr)
-    , inputHdr(nullptr)
-    , inputLoadAddress(0ull)
-    , installName(installName)
+    , inputLoadAddress(this->inputMF->preferredLoadAddress())
+    , installName(inputFile.mf->installName())
 {
 }
 
@@ -208,56 +182,132 @@
                 });
             });
         }
-
-        // Move to auth if __objc_const or __objc_data is present.
-        // This allows new method lists added by the category optimizer to be signed.
-        // Note the linker eagerly moves these sections to AUTH, as of rdar://111858154,
-        // so it is not expected that this code ever finds anything to move, but we'll keep it to be safe
-        ((const Header*)mf)->forEachSection(^(const Header::SegmentInfo &segInfo, const Header::SectionInfo &sectInfo, bool &stop) {
-            if ( segInfo.segmentIndex != segmentIndexToSearch )
-                return;
-            if ( (sectInfo.sectionName == "__objc_const") || (sectInfo.sectionName == "__objc_data") ) {
-                foundAuthFixup = true;
-                stop = true;
+    });
+
+    return foundAuthFixup;
+}
+
+static bool segmentSupportsDataConst(Diagnostics& diag, const BuilderConfig& config,
+                                     const CacheDylib& cacheDylib, std::string_view segmentName,
+                                     objc_visitor::Visitor& objcVisitor)
+{
+    // <rdar://problem/66284631> Don't put __objc_const read-only memory as Swift has method lists we can't see
+    __block bool isBadSwiftLibrary = false;
+    cacheDylib.inputMF->withFileLayout(diag, ^(const mach_o::Layout &layout) {
+        if ( !layout.isSwiftLibrary() )
+            return;
+
+        isBadSwiftLibrary = layout.hasSection(segmentName, "__objc_const");
+    });
+    if ( isBadSwiftLibrary )
+        return false;
+
+    // <rdar://problem/69813664> _NSTheOneTruePredicate is incompatible with __DATA_CONST
+    if ( (cacheDylib.installName == "/System/Library/Frameworks/Foundation.framework/Foundation")
+        || (cacheDylib.installName == "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation") )
+        return false;
+
+    // rdar://74112547 CF writes to kCFNull constant object
+    if ( (cacheDylib.installName == "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")
+        || (cacheDylib.installName == "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation") )
+        return false;
+
+    // rdar://77149283 libcrypto.0.9.8.dylib writes to __DATA_CONST
+    if ( (cacheDylib.installName == "/usr/lib/libcrypto.0.9.7.dylib")
+        || (cacheDylib.installName == "/usr/lib/libcrypto.0.9.8.dylib") )
+        return false;
+
+    // Don't use data const for dylibs containing resolver functions
+    // This will be fixed in ld64 by moving their pointer atoms to __DATA
+    __block bool hasResolver = false;
+    cacheDylib.inputMF->withFileLayout(diag, ^(const mach_o::Layout &layout) {
+        mach_o::ExportTrie exportTrie(layout);
+
+        exportTrie.forEachExportedSymbol(diag,
+                                         ^(const char* symbolName, uint64_t imageOffset, uint64_t flags, uint64_t other,
+                                           const char* importName, bool& expStop) {
+            if ( (flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) != 0 ) {
+                diag.verbose("%s: preventing use of __DATA_CONST due to resolvers\n", cacheDylib.installName.data());
+                hasResolver = true;
+                expStop = true;
             }
         });
     });
-
-    return foundAuthFixup;
+    if ( hasResolver )
+        return false;
+
+    // If we are still allowed to use __DATA_CONST, then make sure that we are not using pointer based method lists.
+    // These may not be written in libobjc due to uniquing or sorting (as those are done in the builder),
+    // but clients can still call setIMP to mutate them.
+    __block bool hasPointerMethodList = false;
+    objcVisitor.forEachClassAndMetaClass(^(const objc_visitor::Class& objcClass, bool& stopClass) {
+        objc_visitor::MethodList objcMethodList = objcClass.getBaseMethods(objcVisitor);
+
+        if ( (objcMethodList.numMethods() != 0) && !objcMethodList.usesRelativeOffsets() ) {
+            hasPointerMethodList = true;
+            stopClass = true;
+        }
+    });
+    if ( hasPointerMethodList )
+        return false;
+
+    objcVisitor.forEachCategory(^(const objc_visitor::Category &objcCategory, bool& stopCategory) {
+        objc_visitor::MethodList instanceMethodList = objcCategory.getInstanceMethods(objcVisitor);
+        objc_visitor::MethodList classMethodList    = objcCategory.getClassMethods(objcVisitor);
+
+        if ( (instanceMethodList.numMethods() != 0) && !instanceMethodList.usesRelativeOffsets() ) {
+            hasPointerMethodList = true;
+            stopCategory = true;
+            return;
+        }
+
+        if ( (classMethodList.numMethods() != 0) && !classMethodList.usesRelativeOffsets() ) {
+            hasPointerMethodList = true;
+            stopCategory = true;
+            return;
+        }
+    });
+    if ( hasPointerMethodList )
+        return false;
+
+    return true;
 }
 
 void CacheDylib::categorizeSegments(const BuilderConfig& config,
                                     objc_visitor::Visitor& objcVisitor)
 {
     bool hasUnalignedFixups = ::hasUnalignedFixups(this->inputMF);
-    this->inputHdr->forEachSegment(^(const Header::SegmentInfo& info, uint64_t sizeOfSections, uint32_t maxAlignOfSections, bool& stop) {
+    this->inputMF->forEachSegment(^(const MachOFile::SegmentInfo& info, bool& stop) {
+        // Segment name is 16-characters long, and not necessarily null terminated
+        std::string_view segmentName(info.segName, strnlen(info.segName, 16));
+
         auto addSegment = [&](DylibSegmentChunk::Kind kind) {
             // TODO: Cache VMSize/fileSize might be less than input VMSize if we deduplicate strings for example
-            uint64_t inputFileSize = std::min((uint64_t)info.fileSize, sizeOfSections);
-            uint64_t cacheFileSize = sizeOfSections;
-            uint64_t vmSize        = sizeOfSections;
+            uint64_t inputFileSize = std::min(info.fileSize, info.sizeOfSections);
+            uint64_t cacheFileSize = info.sizeOfSections;
+            uint64_t vmSize        = info.sizeOfSections;
 
             // LINKEDIT doesn't get space any more. Its individual chunks will get their own space
-            if ( info.segmentName == "__LINKEDIT" ) {
+            if ( segmentName == "__LINKEDIT" ) {
                 inputFileSize = 0;
                 cacheFileSize = 0;
                 vmSize        = 0;
             }
 
-            uint64_t minAlignment = 1 << maxAlignOfSections;
+            uint64_t minAlignment = 1 << info.p2align;
             // Always align __TEXT to a page as split seg can't handle less
-            if ( info.segmentName == "__TEXT" )
+            if ( segmentName == "__TEXT" )
                 minAlignment = config.layout.machHeaderAlignment;
             else if ( hasUnalignedFixups )
-                minAlignment = (this->inputHdr->uses16KPages() ? 0x4000 : 0x1000);
+                minAlignment = (this->inputMF->uses16KPages() ? 0x4000 : 0x1000);
 
             DylibSegmentChunk segment(kind, minAlignment);
-            segment.segmentName     = info.segmentName;
+            segment.segmentName     = segmentName;
             segment.inputFile       = this->inputFile;
-            segment.inputFileOffset = InputDylibFileOffset((uint64_t)info.fileOffset);
+            segment.inputFileOffset = InputDylibFileOffset(info.fileOffset);
             segment.inputFileSize   = InputDylibFileSize(inputFileSize);
-            segment.inputVMAddress  = InputDylibVMAddress(info.vmaddr);
-            segment.inputVMSize     = InputDylibVMSize(info.vmsize);
+            segment.inputVMAddress  = InputDylibVMAddress(info.vmAddr);
+            segment.inputVMSize     = InputDylibVMSize(info.vmSize);
 
             segment.cacheVMSize      = CacheVMSize(vmSize);
             segment.subCacheFileSize = CacheFileSize(cacheFileSize);
@@ -270,13 +320,13 @@
         };
 
         // __TEXT
-        if ( info.initProt == (VM_PROT_READ | VM_PROT_EXECUTE) ) {
+        if ( info.protections == (VM_PROT_READ | VM_PROT_EXECUTE) ) {
             addSegment(DylibSegmentChunk::Kind::dylibText);
             return;
         }
 
         // DATA*
-        if ( info.initProt == (VM_PROT_READ | VM_PROT_WRITE) ) {
+        if ( info.protections == (VM_PROT_READ | VM_PROT_WRITE) ) {
             // If we don't have split seg v2, then all __DATA* segments must look like __DATA so that they
             // stay contiguous
             __block bool isSplitSegV2 = false;
@@ -293,12 +343,7 @@
                 return;
             }
 
-            if ( info.segmentName == "__TPRO_CONST" ) {
-                addSegment(DylibSegmentChunk::Kind::tproDataConst);
-                return;
-            }
-
-            if ( info.segmentName == "__OBJC_CONST" ) {
+            if ( segmentName == "__OBJC_CONST" ) {
                 // In arm64e, "__OBJC_CONST __objc_class_ro" contains authenticated values
                 if ( config.layout.hasAuthRegion )
                     addSegment(DylibSegmentChunk::Kind::dylibAuthConst);
@@ -307,25 +352,34 @@
                 return;
             }
 
-            if ( info.segmentName == "__DATA_DIRTY" ) {
+            if ( segmentName == "__DATA_DIRTY" ) {
                 addSegment(DylibSegmentChunk::Kind::dylibDataDirty);
                 return;
             }
 
             bool hasAuthFixups = false;
-            if ( (info.segmentName == "__AUTH") || (info.segmentName == "__AUTH_CONST") ) {
+            if ( (segmentName == "__AUTH") || (segmentName == "__AUTH_CONST") ) {
                 hasAuthFixups = true;
             } else if ( config.layout.hasAuthRegion ) {
                 // HACK: Some dylibs don't get __AUTH segments.  This matches ld64
-                hasAuthFixups = segmentHasAuthFixups(this->inputMF, info.segmentIndex);
-            }
-
-            bool isConst = info.segmentName.ends_with("_CONST");
+                hasAuthFixups = segmentHasAuthFixups(this->inputMF, info.segIndex);
+            }
+
+            bool supportsDataConst = false;
+            bool isConst = segmentName.ends_with("_CONST");
+            if ( isConst ) {
+                supportsDataConst = segmentSupportsDataConst(diag, config, *this, segmentName,
+                                                             objcVisitor);
+            }
+
             if ( hasAuthFixups ) {
                 // AUTH/AUTH_CONST
                 if ( isConst ) {
                     // AUTH_CONST
-                    addSegment(DylibSegmentChunk::Kind::dylibAuthConst);
+                    if ( supportsDataConst )
+                        addSegment(DylibSegmentChunk::Kind::dylibAuthConst);
+                    else
+                        addSegment(DylibSegmentChunk::Kind::dylibAuthConstWorkaround);
                     return;
                 } else {
                     // AUTH
@@ -336,7 +390,10 @@
                 // DATA/DATA_CONST
                 if ( isConst ) {
                     // DATA_CONST
-                    addSegment(DylibSegmentChunk::Kind::dylibDataConst);
+                    if ( supportsDataConst )
+                        addSegment(DylibSegmentChunk::Kind::dylibDataConst);
+                    else
+                        addSegment(DylibSegmentChunk::Kind::dylibDataConstWorkaround);
                     return;
                 } else {
                     // DATA
@@ -347,8 +404,8 @@
         }
 
         // LINKEDIT/readOnly
-        if ( info.initProt == (VM_PROT_READ) ) {
-            if ( info.segmentName != "__LINKEDIT" ) {
+        if ( info.protections == (VM_PROT_READ) ) {
+            if ( segmentName != "__LINKEDIT" ) {
                 addSegment(DylibSegmentChunk::Kind::dylibReadOnly);
                 return;
             }
@@ -563,14 +620,6 @@
                 });
                 break;
             }
-            case LC_FUNCTION_VARIANTS: {
-                const linkedit_data_command* linkeditCmd = (const linkedit_data_command*)cmd;
-
-                addLinkedit(Kind::linkeditFunctionVariants, InputDylibFileOffset((uint64_t)linkeditCmd->dataoff),
-                            InputDylibFileSize((uint64_t)linkeditCmd->datasize), CacheVMSize((uint64_t)linkeditCmd->datasize),
-                            pointerSize);
-                break;
-            }
         }
     });
     diag.assertNoError();
@@ -578,7 +627,7 @@
 
 void CacheDylib::copyRawSegments(const BuilderConfig& config, Timer::AggregateTimer& timer)
 {
-    const bool log = config.log.printDebugCacheLayout;
+    const bool log = false;
 
     Timer::AggregateTimer::Scope timedScope(timer, "dylib copyRawSegments time");
 
@@ -691,51 +740,6 @@
     }
 }
 
-std::optional<CacheDylib::BindTargetAndName> CacheDylib::findDyldMagicSymbolAddress(const char* fullSymbolName, std::string_view name) const
-{
-    auto nextString = [&]() -> std::string_view {
-        auto pos = name.find('$');
-        if ( pos == std::string_view::npos ) {
-            auto str = name;
-            name = "";
-            return str;
-        }
-        auto str = name.substr(0, pos);
-        name = name.substr(pos + 1);
-        return str;
-    };
-
-    std::string_view type = nextString();
-    if ( type == "segment" ) {
-        std::string_view segmentType = nextString();
-        std::string_view segmentName = nextString();
-
-        bool isStart = (segmentType == "start");
-        bool isEnd   = (segmentType == "end");
-
-        __block std::optional<VMAddress> vmAddr;
-
-        this->inputHdr->forEachSegment(^(const Header::SegmentInfo& info, bool& stop) {
-            if ( info.segmentName == segmentName ) {
-                if ( isStart )
-                    vmAddr = VMAddress(info.vmaddr);
-                else if ( isEnd )
-                    vmAddr = VMAddress(info.vmaddr) + VMOffset(info.vmsize);
-
-                stop = true;
-            }
-        });
-
-        if ( !vmAddr )
-            return std::nullopt;
-
-        VMOffset vmOff = *vmAddr - VMAddress(inputLoadAddress.rawValue());
-        return std::make_pair(BindTarget{ BindTarget::Kind::inputImage, { .inputImage = { vmOff, this, /* weak def */ false }  } }, std::string(fullSymbolName));
-    }
-
-    return std::nullopt;
-}
-
 // FIXME: This was stolen from Loader.  try unify them again
 CacheDylib::BindTargetAndName CacheDylib::resolveSymbol(Diagnostics& diag, int libOrdinal, const char* symbolName,
                                                         bool weakImport, const std::vector<const CacheDylib*>& cacheDylibs) const
@@ -744,14 +748,14 @@
 
     BindTarget nullBindTarget = { BindTarget::Kind::absolute, { .absolute = { 0 } } };
 
-    if ( (libOrdinal > 0) && ((unsigned)libOrdinal <= this->inputDependents.size()) ) {
-        targetDylib = this->inputDependents[libOrdinal - 1].dylib;
+    if ( (libOrdinal > 0) && ((unsigned)libOrdinal <= this->dependents.size()) ) {
+        targetDylib = this->dependents[libOrdinal - 1].dylib;
     }
     else if ( libOrdinal == BIND_SPECIAL_DYLIB_SELF ) {
         targetDylib = this;
     }
     else if ( libOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE ) {
-        diag.error("shared cache dylibs bind to the main executable: %s\n  Referenced from: %s", symbolName, this->installName.data());
+        diag.error("shared cache dylibs bind to the main executable: %s", symbolName);
         return { nullBindTarget, "" };
     }
     else if ( libOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP ) {
@@ -767,7 +771,7 @@
         }
 
         // missing symbol, but not weak-import or lazy-bound, so error
-        diag.error("symbol not found in flat namespace '%s'\n  Referenced from: %s", symbolName, this->installName.data());
+        diag.error("symbol not found in flat namespace '%s'", symbolName);
         return { nullBindTarget, "" };
     }
     else if ( libOrdinal == BIND_SPECIAL_DYLIB_WEAK_LOOKUP ) {
@@ -775,7 +779,7 @@
 
         // look first in /usr/lib/libc++, most will be here
         for ( const CacheDylib* cacheDylib : cacheDylibs ) {
-            if ( cacheDylib->inputHdr->hasWeakDefs() && startsWith(cacheDylib->installName, "/usr/lib/libc++.") ) {
+            if ( cacheDylib->inputMF->hasWeakDefs() && startsWith(cacheDylib->installName, "/usr/lib/libc++.") ) {
                 std::optional<BindTargetAndName> bindTargetAndName = cacheDylib->hasExportedSymbol(diag, symbolName, SearchMode::onlySelf);
                 if ( bindTargetAndName.has_value() )
                     return bindTargetAndName.value();
@@ -791,7 +795,7 @@
             return sellBindTargetAndName.value();
 
         // if this image directly links with something that also defines this weak-def, use that because we know it will be loaded
-        for ( const CacheDylib::DependentDylib& dependentDylib : this->inputDependents ) {
+        for ( const CacheDylib::DependentDylib& dependentDylib : this->dependents ) {
             if ( dependentDylib.kind == DependentDylib::Kind::upward )
                 continue;
 
@@ -807,7 +811,7 @@
         }
 
         // no impl??
-        diag.error("weak-def symbol (%s) not found in dyld cache\n  Referenced from: %s", symbolName, this->installName.data());
+        diag.error("weak-def symbol (%s) not found in dyld cache", symbolName);
         return { nullBindTarget, "" };
     }
     else {
@@ -815,25 +819,6 @@
         return { nullBindTarget, "" };
     }
     if ( targetDylib != nullptr ) {
-        if ( const char* dyldMagic = strstr(symbolName, "$dyld$") ) {
-            std::string_view name = dyldMagic + 6;
-            std::optional<BindTargetAndName> target;
-
-            // only synthetic dylibs without a need for the patch table can use magic dyld symbols
-            // dyld itself does not know about them so it won't be able to bind them
-            if ( !needsPatchTable )
-                target = targetDylib->findDyldMagicSymbolAddress(symbolName, name);
-            if ( target )
-                return target.value();
-
-            const char* expectedInDylib = "unknown";
-            if ( targetDylib != nullptr )
-                expectedInDylib = targetDylib->installName.data();
-
-            diag.error("Symbol not found: %s\n  Referenced from: %s\n  Expected in: %s", symbolName, this->installName.data(), expectedInDylib);
-            return { nullBindTarget, "" };
-        }
-
         std::optional<BindTargetAndName> bindTargetAndName = targetDylib->hasExportedSymbol(diag, symbolName, SearchMode::selfAndReexports);
         if ( diag.hasError() )
             return { nullBindTarget, "" };
@@ -894,12 +879,12 @@
             if ( importedName[0] == '\0' ) {
                 importedName = symbolName;
             }
-            if ( (ordinal == 0) || (ordinal > this->inputDependents.size()) ) {
+            if ( (ordinal == 0) || (ordinal > this->dependents.size()) ) {
                 diag.error("re-export ordinal %lld in %s out of range for %s", ordinal, this->installName.data(), symbolName);
                 return {};
             }
             uint32_t depIndex = (uint32_t)(ordinal - 1);
-            if ( const CacheDylib* dependentDylib = this->inputDependents[depIndex].dylib )
+            if ( const CacheDylib* dependentDylib = this->dependents[depIndex].dylib )
                 return dependentDylib->hasExportedSymbol(diag, importedName, mode);
 
             // re-exported symbol from weak-linked dependent which is missing
@@ -910,7 +895,6 @@
                 return {};
             bool     isAbsoluteSymbol = ((flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) == EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE);
             bool     isWeakDef        = (flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION);
-            bool     isFuncVariant    = (flags & EXPORT_SYMBOL_FLAGS_FUNCTION_VARIANT);
             uint64_t value            = MachOFile::read_uleb128(diag, p, trieEnd);
 
             if ( isAbsoluteSymbol ) {
@@ -918,21 +902,15 @@
                 return (BindTargetAndName) { result, symbolName };
             }
 
-            uint16_t fvTableIndex = 0;
-            if ( isFuncVariant ) {
-                // next uleb128 is func-variant table index
-                fvTableIndex = (uint16_t)MachOFile::read_uleb128(diag, p, trieEnd);
-            }
-
             // Bind to image
-            BindTarget result = { BindTarget::Kind::inputImage, { .inputImage = { VMOffset(value), this, isWeakDef, isFuncVariant, fvTableIndex } } };
+            BindTarget result = { BindTarget::Kind::inputImage, { .inputImage = { VMOffset(value), this, isWeakDef } } };
             return (BindTargetAndName) { result, symbolName };
         }
     }
 
     if ( canSearchDependentReexports ) {
         // Search re-exported dylibs
-        for ( const CacheDylib::DependentDylib& dependentDylib : this->inputDependents ) {
+        for ( const CacheDylib::DependentDylib& dependentDylib : this->dependents ) {
             if ( dependentDylib.kind != DependentDylib::Kind::reexport )
                 continue;
 
@@ -949,10 +927,10 @@
     return {};
 }
 
-std::vector<Error> CacheDylib::calculateBindTargets(Diagnostics& diag,
-                                                    const BuilderConfig& config, Timer::AggregateTimer& timer,
-                                                    const std::vector<const CacheDylib*>& cacheDylibs,
-                                                    PatchInfo& dylibPatchInfo)
+void CacheDylib::calculateBindTargets(Diagnostics& diag,
+                                      const BuilderConfig& config, Timer::AggregateTimer& timer,
+                                      const std::vector<const CacheDylib*>& cacheDylibs,
+                                      PatchInfo& dylibPatchInfo)
 {
     Timer::AggregateTimer::Scope timedScope(timer, "dylib calculateBindTargets time");
 
@@ -960,21 +938,39 @@
     // race looking at the export trie in a target dylib, while it is being shifted by AdjustDylibSegments.
     // Given that, we'll do all the analysis on the input dylibs, with knowledge of where they'll shift to
 
-    __block std::vector<Error> errors;
     auto handleBindTarget = ^(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop) {
-        Diagnostics symbolDiag;
-        BindTargetAndName bindTargetAndName = this->resolveSymbol(symbolDiag, libOrdinal, symbolName, weakImport, cacheDylibs);
+        BindTargetAndName bindTargetAndName = this->resolveSymbol(diag, libOrdinal, symbolName, weakImport, cacheDylibs);
         BindTarget&       bindTarget        = bindTargetAndName.first;
-        if ( symbolDiag.hasError() ) {
-            errors.push_back(Error("%s", symbolDiag.errorMessageCStr()));
+        if ( diag.hasError() ) {
+            stop = true;
             return;
         }
 
+        // Adjust the bind target.  We have a runtime offset for the target input dylib, but we need to know where that runtime Offset will
+        // map to in the target cache dylib
+        switch ( bindTarget.kind ) {
+            case BindTarget::Kind::absolute:
+                // Skip these.  They won't change due to shifting the input dylib in to the cache
+                break;
+            case BindTarget::Kind::inputImage: {
+                // Convert from an input dylib offset to the cache dylib offset
+                BindTarget::InputImage inputImage        = bindTarget.inputImage;
+                InputDylibVMAddress    targetInputVMAddr = inputImage.targetDylib->inputLoadAddress + inputImage.targetRuntimeOffset;
+                CacheVMAddress         targetCacheVMAddr = inputImage.targetDylib->adjustor->adjustVMAddr(targetInputVMAddr);
+
+                // Actually change the bindTarget to reflect the new type
+                bindTarget.kind = BindTarget::Kind::cacheImage;
+                bindTarget.inputImage.~InputImage();
+                bindTarget.cacheImage = (BindTarget::CacheImage) { VMOffset(targetCacheVMAddr - inputImage.targetDylib->cacheLoadAddress), inputImage.targetDylib, inputImage.isWeak };
+                break;
+            }
+            case BindTarget::Kind::cacheImage:
+                diag.error("Shouldn't see cacheImage fixups at this point");
+                stop = true;
+                return;
+        }
+
         bindTarget.addend = addend;
-        bindTarget.isWeakImport = weakImport;
-#if DEBUG
-        bindTarget.name = symbolName;
-#endif
         this->bindTargets.push_back(std::move(bindTarget));
         dylibPatchInfo.bindTargetNames.push_back(std::move(bindTargetAndName.second));
     };
@@ -995,29 +991,54 @@
     }
     else if ( this->inputMF->hasOpcodeFixups() ) {
         // Use the fixups from the source dylib
-        this->inputMF->withFileLayout(diag, ^(const mach_o::Layout &layout) {
-            mach_o::Fixups fixups(layout);
-
-            bool allowLazyBinds = false;
-            fixups.forEachBindTarget(diag, allowLazyBinds, 0,
-                ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
-                    handleBindTarget(info.libOrdinal, info.symbolName, info.addend, info.weakImport, stop);
-                },
-                ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
-                    if ( !this->weakBindTargetsStartIndex.has_value() )
-                        this->weakBindTargetsStartIndex = this->bindTargets.size();
-                    handleBindTarget(info.libOrdinal, info.symbolName, info.addend, info.weakImport, stop);
-                });
+        mach_o::LinkeditLayout linkedit;
+        if ( !this->inputMF->getLinkeditLayout(diag, linkedit) ) {
+            diag.error("Couldn't get dylib layout");
+            return;
+        }
+
+        // Use the segment layout from the cache dylib so that VMAddresses are correct
+        __block std::vector<mach_o::SegmentLayout> segmentLayout;
+        segmentLayout.reserve(this->segments.size());
+        for ( const DylibSegmentChunk& dylibSegment : this->segments ) {
+            mach_o::SegmentLayout segment;
+            segment.vmAddr      = dylibSegment.cacheVMAddress.rawValue();
+            segment.vmSize      = dylibSegment.cacheVMSize.rawValue();
+            segment.fileOffset  = dylibSegment.subCacheFileOffset.rawValue();
+            segment.fileSize    = dylibSegment.subCacheFileSize.rawValue();
+            segment.buffer      = dylibSegment.subCacheBuffer;
+
+            segment.kind        = mach_o::SegmentLayout::Kind::unknown;
+            if ( dylibSegment.segmentName == "__TEXT" ) {
+                segment.kind    = mach_o::SegmentLayout::Kind::text;
+            } else if ( dylibSegment.segmentName == "__LINKEDIT" ) {
+                segment.kind    = mach_o::SegmentLayout::Kind::linkedit;
+            }
+            segmentLayout.push_back(segment);
+        }
+
+        // The cache segments don't have the permissions.  Get that from the load commands
+        this->cacheMF->forEachSegment(^(const MachOFile::SegmentInfo& info, bool& stop) {
+            segmentLayout[info.segIndex].protections = info.protections;
         });
+
+        mach_o::Layout layout(this->inputMF, { segmentLayout.data(), segmentLayout.data() + segmentLayout.size() }, linkedit);
+        mach_o::Fixups fixups(layout);
+
+        bool allowLazyBinds = false;
+        fixups.forEachBindTarget(diag, allowLazyBinds, 0,
+            ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
+                handleBindTarget(info.libOrdinal, info.symbolName, info.addend, info.weakImport, stop);
+            },
+            ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
+                if ( !this->weakBindTargetsStartIndex.has_value() )
+                    this->weakBindTargetsStartIndex = this->bindTargets.size();
+                handleBindTarget(info.libOrdinal, info.symbolName, info.addend, info.weakImport, stop);
+            });
     }
     else {
         // Cache dylibs shouldn't use old style fixups.
     }
-
-    if ( !errors.empty() )
-        diag.error("missing symbols");
-
-    return std::move(errors);
 }
 
 void CacheDylib::bindLocation(Diagnostics& diag, const BuilderConfig& config,
@@ -1025,13 +1046,28 @@
                               uint32_t bindOrdinal, uint32_t segIndex,
                               dyld3::MachOFile::ChainedFixupPointerOnDisk* fixupLoc,
                               CacheVMAddress fixupVMAddr, MachOFile::PointerMetaData pmd,
-                              CoalescedGOTsMap& coalescedGOTs, CoalescedGOTsMap& coalescedAuthGOTs,
-                              CoalescedGOTsMap& coalescedAuthPtrs, PatchInfo& dylibPatchInfo,
-                              FunctionVariantsOptimizer& functionVariantsOptimizer)
+                              CoalescedGOTMap& coalescedGOTs, CoalescedGOTMap& coalescedAuthGOTs,
+                              PatchInfo& dylibPatchInfo)
 {
     switch ( bindTarget.kind ) {
         case BindTarget::Kind::absolute: {
             uint64_t targetValue = bindTarget.absolute.value + addend;
+
+            auto gotIt = coalescedGOTs.find(fixupVMAddr);
+            if ( gotIt != coalescedGOTs.end() ) {
+                // Probably a missing weak import.  Rewrite the original GOT anyway, but also the coalesced one
+                dyld_cache_patchable_location patchLoc(gotIt->second, pmd, addend);
+                auto& gotUses = dylibPatchInfo.bindGOTUses[bindOrdinal];
+                gotUses.emplace_back((PatchInfo::GOTInfo){ patchLoc, VMOffset(targetValue) });
+            } else {
+                auto authgotIt = coalescedAuthGOTs.find(fixupVMAddr);
+                if ( authgotIt != coalescedAuthGOTs.end() ) {
+                    // Probably a missing weak import.  Rewrite the original GOT anyway, but also the coalesced one
+                    dyld_cache_patchable_location patchLoc(authgotIt->second, pmd, addend);
+                    auto &gotUses = dylibPatchInfo.bindAuthGOTUses[bindOrdinal];
+                    gotUses.emplace_back((PatchInfo::GOTInfo){ patchLoc, VMOffset(targetValue) });
+                }
+            }
 
             if ( config.layout.is64 ) {
                 fixupLoc->raw64 = targetValue;
@@ -1053,6 +1089,7 @@
             CacheVMAddress targetDylibLoadAddress = bindTarget.cacheImage.targetDylib->cacheLoadAddress;
             CacheVMAddress targetVMAddr = targetDylibLoadAddress + bindTarget.cacheImage.targetRuntimeOffset;
             uint64_t       finalVMAddrWithAddend  = targetVMAddr.rawValue() + addend;
+
             if ( config.layout.is64 ) {
                 uint64_t finalVMAddr = finalVMAddrWithAddend;
 
@@ -1078,13 +1115,42 @@
             // Work out if the location we just wrote is a coalesced GOT.  If so, NULL the current location and
             // note down the fixup to the GOT.  We can't just apply the GOT fixup, as we might be running in parallel with
             // other threads all trying to do the same thing
-            if( needsPatchTable ) {
-                // The GOT map is keyed by the input VMAddr, so convert back to that
-                VMOffset segmentVMOffset = fixupVMAddr - this->segments[segIndex].cacheVMAddress;
-                InputDylibVMAddress inputFixupVMAddr = this->segments[segIndex].inputVMAddress + segmentVMOffset;
-                auto checkGOTs = ^(CoalescedGOTsMap& gotMap) {
-                    auto gotIt = gotMap.find(inputFixupVMAddr);
-                    if ( gotIt != gotMap.end() ) {
+            {
+                uint64_t                   patchTableAddend = addend;
+                MachOFile::PointerMetaData patchTablePMD    = pmd;
+                uint64_t                   addendHigh8      = addend >> 56;
+                if ( addendHigh8 != 0 ) {
+                    // Put the high8 from the addend in to the high8 of the patch
+                    assert(patchTablePMD.high8 == 0);
+                    patchTablePMD.high8 = (uint32_t)addendHigh8;
+
+                    // Remove high8 from the addend
+                    patchTableAddend = patchTableAddend & 0x00FFFFFFFFFFFFFFULL;
+                }
+
+                VMOffset finalVMOffset = CacheVMAddress(finalVMAddrWithAddend) - config.layout.cacheBaseAddress;
+
+                auto gotIt = coalescedGOTs.find(fixupVMAddr);
+                if ( gotIt != coalescedGOTs.end() ) {
+                    dyld_cache_patchable_location patchLoc(gotIt->second, patchTablePMD, patchTableAddend);
+                    auto& gotUses = dylibPatchInfo.bindGOTUses[bindOrdinal];
+                    gotUses.emplace_back((PatchInfo::GOTInfo){ patchLoc, finalVMOffset });
+
+                    // NULL out this entry
+                    if ( config.layout.is64 ) {
+                        fixupLoc->raw64 = 0;
+                    } else {
+                        fixupLoc->raw32 = 0;
+                    }
+
+                    // Tell the slide info emitter to ignore this location
+                    this->segments[segIndex].tracker.remove(fixupLoc);
+                } else {
+                    auto authgotIt = coalescedAuthGOTs.find(fixupVMAddr);
+                    if ( authgotIt != coalescedAuthGOTs.end() ) {
+                        dyld_cache_patchable_location patchLoc(authgotIt->second, patchTablePMD, patchTableAddend);
+                        auto& gotUses = dylibPatchInfo.bindAuthGOTUses[bindOrdinal];
+                        gotUses.emplace_back((PatchInfo::GOTInfo){ patchLoc, finalVMOffset });
 
                         // NULL out this entry
                         if ( config.layout.is64 ) {
@@ -1095,37 +1161,9 @@
 
                         // Tell the slide info emitter to ignore this location
                         this->segments[segIndex].tracker.remove(fixupLoc);
-                        return true;
-                    }
-                    return false;
-                };
-                if ( checkGOTs(coalescedGOTs) || checkGOTs(coalescedAuthGOTs) || checkGOTs(coalescedAuthPtrs) ) {
-                    // normal GOT/auth GOT/auth ptr
-                } else {
-                    // if target is a function variant, record that dyld may need to update pointer at launch
-                    if ( bindTarget.cacheImage.isFunctionVariant ) {
-                        uint64_t fvTableVmAddr = 0;
-                        uint32_t fvTableVmSize = 0;
-                        for ( const LinkeditDataChunk& chunk :  bindTarget.cacheImage.targetDylib->linkeditChunks ) {
-                            if ( chunk.isFunctionVariantsTable() ) {
-                                fvTableVmAddr = chunk.cacheVMAddress.rawValue();
-                                fvTableVmSize = (uint32_t)chunk.cacheVMSize.rawValue();
-                                break;
-                            }
-                        }
-                        dyld_cache_function_variant_entry entry;
-                        entry.fixupLocVmAddr               = fixupVMAddr.rawValue();
-                        entry.functionVariantTableVmAddr   = fvTableVmAddr;
-                        entry.functionVariantTableSizeDiv4 = fvTableVmSize/4;
-                        entry.dylibHeaderVmAddr            = bindTarget.cacheImage.targetDylib->cacheLoadAddress.rawValue();
-                        entry.variantIndex                 = bindTarget.cacheImage.functionVariantTableIndex;
-                        entry.pacAuth                      = pmd.authenticated;
-                        entry.pacAddress                   = pmd.usesAddrDiversity;
-                        entry.pacKey                       = pmd.key;
-                        entry.pacDiversity                 = pmd.diversity;
-                        entry.targetDylibIndex             = bindTarget.cacheImage.targetDylib->cacheIndex;
-                        assert(entry.variantIndex == bindTarget.cacheImage.functionVariantTableIndex);
-                        functionVariantsOptimizer.infos.push_back(entry);
+                    } else {
+                        // Location wasn't coalesced.  So add to the regular list of uses
+                        dylibPatchInfo.bindUses[bindOrdinal].emplace_back(fixupVMAddr, patchTablePMD, patchTableAddend);
                     }
                 }
             }
@@ -1135,9 +1173,8 @@
 }
 
 void CacheDylib::bindWithChainedFixups(Diagnostics& diag, const BuilderConfig& config,
-                                       CoalescedGOTsMap& coalescedGOTs, CoalescedGOTsMap& coalescedAuthGOTs,
-                                       CoalescedGOTsMap& coalescedAuthPtrs, PatchInfo& dylibPatchInfo,
-                                       FunctionVariantsOptimizer& functionVariantsOptimizer)
+                                       CoalescedGOTMap& coalescedGOTs, CoalescedGOTMap& coalescedAuthGOTs,
+                                       PatchInfo& dylibPatchInfo)
 {
     auto fixupHandler = ^(MachOFile::ChainedFixupPointerOnDisk* fixupLoc, uint16_t chainedFormat,
                           uint32_t segIndex, CacheVMAddress fixupVMAddr,
@@ -1200,9 +1237,7 @@
 
         this->bindLocation(diag, config, targetInTable, addend, bindOrdinal, segIndex,
                            fixupLoc, fixupVMAddr, pmd,
-                           coalescedGOTs, coalescedAuthGOTs, 
-                           coalescedAuthPtrs, dylibPatchInfo,
-                           functionVariantsOptimizer);
+                           coalescedGOTs, coalescedAuthGOTs, dylibPatchInfo);
     };
 
     this->inputMF->withFileLayout(diag, ^(const mach_o::Layout &layout) {
@@ -1234,9 +1269,8 @@
 }
 
 void CacheDylib::bindWithOpcodeFixups(Diagnostics& diag, const BuilderConfig& config,
-                                      CoalescedGOTsMap& coalescedGOTs, CoalescedGOTsMap& coalescedAuthGOTs,
-                                      CoalescedGOTsMap& coalescedAuthPtrs, PatchInfo& dylibPatchInfo,
-                                      FunctionVariantsOptimizer& functionVariantsOptimizer)
+                                      CoalescedGOTMap& coalescedGOTs, CoalescedGOTMap& coalescedAuthGOTs,
+                                      PatchInfo& dylibPatchInfo)
 {
     auto handleFixup = ^(uint64_t fixupRuntimeOffset, int bindOrdinal, uint32_t segmentIndex, bool& stopSegment) {
         DylibSegmentChunk& segmentInfo = this->segments[segmentIndex];
@@ -1257,8 +1291,7 @@
         this->bindLocation(diag, config, targetInTable, addend, bindOrdinal, segmentIndex,
                            (dyld3::MachOFile::ChainedFixupPointerOnDisk*)fixupLoc,
                            fixupVMAddr, dyld3::MachOFile::PointerMetaData(),
-                           coalescedGOTs, coalescedAuthGOTs, coalescedAuthPtrs, dylibPatchInfo,
-                           functionVariantsOptimizer);
+                           coalescedGOTs, coalescedAuthGOTs, dylibPatchInfo);
     };
 
     // Use the fixups from the source dylib
@@ -1289,8 +1322,8 @@
     }
 
     // The cache segments don't have the permissions.  Get that from the load commands
-    this->cacheHdr->forEachSegment(^(const Header::SegmentInfo& info, bool& stop) {
-        segmentLayout[info.segmentIndex].protections = info.initProt;
+    this->cacheMF->forEachSegment(^(const MachOFile::SegmentInfo& info, bool& stop) {
+        segmentLayout[info.segIndex].protections = info.protections;
     });
 
     mach_o::Layout layout(this->inputMF, { segmentLayout.data(), segmentLayout.data() + segmentLayout.size() }, linkedit);
@@ -1342,181 +1375,48 @@
         });
 }
 
-void CacheDylib::calculateBindLocationPatchInfo(Diagnostics& diag, const BuilderConfig& config,
-                                                const BindTarget& bindTarget, uint64_t addend,
-                                                uint32_t bindOrdinal, uint32_t segIndex,
-                                                InputDylibVMAddress fixupVMAddr, MachOFile::PointerMetaData pmd,
-                                                CoalescedGOTsMap& coalescedGOTs, CoalescedGOTsMap& coalescedAuthGOTs,
-                                                CoalescedGOTsMap& coalescedAuthPtrs, PatchInfo& dylibPatchInfo)
-{
-    switch ( bindTarget.kind ) {
-        case BindTarget::Kind::absolute: {
-            uint64_t targetValue = bindTarget.absolute.value + addend;
-
-            auto checkGOTs = ^(CoalescedGOTsMap& gotMap, std::vector<std::vector<PatchInfo::GOTInfo>>& gotInfo) {
-                auto gotIt = gotMap.find(fixupVMAddr);
-                if ( gotIt != gotMap.end() ) {
-                    // Probably a missing weak import.  Rewrite the original GOT anyway, but also the coalesced one
-                    const ChunkPlusOffset gotPlusOffset = gotIt->second;
-                    DyldCachePatchableGOTLocation patchLoc(gotPlusOffset.first, gotPlusOffset.second, pmd, addend, bindTarget.isWeakImport);
-                    auto& gotUses = gotInfo[bindOrdinal];
-                    gotUses.emplace_back((PatchInfo::GOTInfo){ patchLoc, targetValue });
-                    return true;
-                }
-                return false;
-            };
-            if ( checkGOTs(coalescedGOTs, dylibPatchInfo.bindGOTUses) ) {
-                // normal GOT
-            } else if ( checkGOTs(coalescedAuthGOTs, dylibPatchInfo.bindAuthGOTUses) ) {
-                // auth GOT
-            } else if ( checkGOTs(coalescedAuthPtrs, dylibPatchInfo.bindAuthPtrUses) ) {
-                // auth ptr
-            }
-            return;
-        }
-        case BindTarget::Kind::inputImage: {
-            InputDylibVMAddress targetDylibLoadAddress = bindTarget.inputImage.targetDylib->inputLoadAddress;
-            InputDylibVMAddress targetVMAddr = targetDylibLoadAddress + bindTarget.inputImage.targetRuntimeOffset;
-            uint64_t            finalTargetVMAddrWithAddend  = targetVMAddr.rawValue() + addend;
-
-            // Work out if the location we just wrote is a coalesced GOT.  If so, NULL the current location and
-            // note down the fixup to the GOT.  We can't just apply the GOT fixup, as we might be running in parallel with
-            // other threads all trying to do the same thing
-            uint64_t                   patchTableAddend = addend;
-            MachOFile::PointerMetaData patchTablePMD    = pmd;
-            uint64_t                   addendHigh8      = addend >> 56;
-            if ( addendHigh8 != 0 ) {
-                // Put the high8 from the addend in to the high8 of the patch
-                assert(patchTablePMD.high8 == 0);
-                patchTablePMD.high8 = (uint32_t)addendHigh8;
-
-                // Remove high8 from the addend
-                patchTableAddend = patchTableAddend & 0x00FFFFFFFFFFFFFFULL;
-            }
-
-            InputDylibVMOffset finalTargetVMOffset = InputDylibVMAddress(finalTargetVMAddrWithAddend) - targetDylibLoadAddress;
-
-            auto checkGOTs = ^(CoalescedGOTsMap& gotMap, std::vector<std::vector<PatchInfo::GOTInfo>>& gotInfo) {
-                auto gotIt = gotMap.find(fixupVMAddr);
-                if ( gotIt != gotMap.end() ) {
-                    const ChunkPlusOffset gotPlusOffset = gotIt->second;
-                    DyldCachePatchableGOTLocation patchLoc(gotPlusOffset.first, gotPlusOffset.second, patchTablePMD, patchTableAddend, bindTarget.isWeakImport);
-                    auto& gotUses = gotInfo[bindOrdinal];
-                    DylibOffset dylibOffset = { bindTarget.inputImage.targetDylib, finalTargetVMOffset };
-                    gotUses.emplace_back((PatchInfo::GOTInfo){ patchLoc, dylibOffset });
-                    return true;
-                }
-                return false;
-            };
-            if ( checkGOTs(coalescedGOTs, dylibPatchInfo.bindGOTUses) ) {
-                // normal GOT
-            } else if ( checkGOTs(coalescedAuthGOTs, dylibPatchInfo.bindAuthGOTUses) ) {
-                // auth GOT
-            } else if ( checkGOTs(coalescedAuthPtrs, dylibPatchInfo.bindAuthPtrUses) ) {
-                // auth ptr
-            } else {
-                // Location wasn't coalesced.  So add to the regular list of uses
-                InputDylibVMOffset fixupVMOffset = fixupVMAddr - this->inputLoadAddress;
-                DyldCachePatchableLocation patchLoc = { fixupVMOffset, patchTablePMD, patchTableAddend, bindTarget.isWeakImport };
-                dylibPatchInfo.bindUses[bindOrdinal].push_back(patchLoc);
-            }
-            break;
-        }
-        case BindTarget::Kind::cacheImage: {
-            diag.error("Input binds should not have been converted to cache binds in %s: %d",
-                       this->installName.data(), bindOrdinal);
-            return;
-        }
-    }
-}
-
-void CacheDylib::calcuatePatchInfo(Diagnostics& diag, const BuilderConfig& config, Timer::AggregateTimer& timer,
-                                   PatchInfo& dylibPatchInfo)
-{
-    Timer::AggregateTimer::Scope timedScope(timer, "dylib patch info calculation time");
-
-    __block CoalescedGOTsMap coalescedGOTs      = optimizedSections.gots.getCoalescedGOTsMap();
-    __block CoalescedGOTsMap coalescedAuthGOTs  = optimizedSections.auth_gots.getCoalescedGOTsMap();
-    __block CoalescedGOTsMap coalescedAuthPtrs  = optimizedSections.auth_ptrs.getCoalescedGOTsMap();
+void CacheDylib::bind(Diagnostics& diag, const BuilderConfig& config, Timer::AggregateTimer& timer,
+                      PatchInfo& dylibPatchInfo)
+{
+    Timer::AggregateTimer::Scope timedScope(timer, "dylib bind time");
+
+    // As we are running in parallel, addresses in other dylibs may not have been shifted yet.  We may also
+    // race looking at the export trie in a target dylib, while it is being shifted by AdjustDylibSegments.
+    // Given that, we'll look at our own cache dylib, but everyone elses input dylib, as those won't mutate
+
+    // Map from where the GOT is located in the dylib to where its located in the coalesced section
+    std::unordered_map<const CacheVMAddress, CacheVMAddress, CacheVMAddressHash, CacheVMAddressEqual> coalescedGOTs;
+    if ( !optimizedSections.gots.offsetMap.empty() ) {
+        uint32_t segmentIndex = optimizedSections.gots.segmentIndex.value();
+        CacheVMAddress dylibGOTBaseVMAddr = this->segments[segmentIndex].cacheVMAddress + optimizedSections.gots.sectionVMOffsetInSegment;
+        CacheVMAddress cacheGOTBaseVMAddr = optimizedSections.gots.subCacheSection->cacheChunk->cacheVMAddress;
+        for ( const auto& dylibOffsetAndCacheOffset : optimizedSections.gots.offsetMap ) {
+            VMOffset dylibSectionOffset((uint64_t)dylibOffsetAndCacheOffset.first);
+            VMOffset cacheSectionOffset((uint64_t)dylibOffsetAndCacheOffset.second);
+            coalescedGOTs[dylibGOTBaseVMAddr + dylibSectionOffset] = cacheGOTBaseVMAddr + cacheSectionOffset;
+        }
+    }
+    std::unordered_map<const CacheVMAddress, CacheVMAddress, CacheVMAddressHash, CacheVMAddressEqual> coalescedAuthGOTs;
+    if ( !optimizedSections.auth_gots.offsetMap.empty() ) {
+        uint32_t segmentIndex = optimizedSections.auth_gots.segmentIndex.value();
+        CacheVMAddress dylibGOTBaseVMAddr = this->segments[segmentIndex].cacheVMAddress + optimizedSections.auth_gots.sectionVMOffsetInSegment;
+        CacheVMAddress cacheGOTBaseVMAddr = optimizedSections.auth_gots.subCacheSection->cacheChunk->cacheVMAddress;
+        for ( const auto& dylibOffsetAndCacheOffset : optimizedSections.auth_gots.offsetMap ) {
+            VMOffset dylibSectionOffset((uint64_t)dylibOffsetAndCacheOffset.first);
+            VMOffset cacheSectionOffset((uint64_t)dylibOffsetAndCacheOffset.second);
+            coalescedAuthGOTs[dylibGOTBaseVMAddr + dylibSectionOffset] = cacheGOTBaseVMAddr + cacheSectionOffset;
+        }
+    }
 
     // Track which locations this dylib uses in other dylibs.  One per bindTarget
     dylibPatchInfo.bindUses.resize(this->bindTargets.size());
     dylibPatchInfo.bindGOTUses.resize(this->bindTargets.size());
     dylibPatchInfo.bindAuthGOTUses.resize(this->bindTargets.size());
-    dylibPatchInfo.bindAuthPtrUses.resize(this->bindTargets.size());
-
-    if ( !needsPatchTable )
-        return;
-
-    auto handleFixup = ^(InputDylibVMAddress fixupVMAddr, int64_t embeddedAddend, int bindOrdinal, dyld3::MachOFile::PointerMetaData pmd,
-                         uint32_t segmentIndex, bool& stopSegment) {
-        if ( bindOrdinal >= this->bindTargets.size() ) {
-            diag.error("out of range bind ordinal %d (max %lu)", bindOrdinal, this->bindTargets.size());
-            stopSegment   = true;
-            return;
-        }
-
-        const BindTarget& targetInTable = this->bindTargets[bindOrdinal];
-        uint64_t          addend        = targetInTable.addend + embeddedAddend;
-
-        this->calculateBindLocationPatchInfo(diag, config, targetInTable, addend, bindOrdinal, segmentIndex,
-                                             fixupVMAddr, pmd,
-                                             coalescedGOTs, coalescedAuthGOTs, coalescedAuthPtrs, dylibPatchInfo);
-    };
-
-    this->inputMF->withFileLayout(diag, ^(const mach_o::Layout& layout) {
-        mach_o::Fixups fixups(layout);
-        if ( this->inputMF->hasChainedFixups() ) {
-            fixups.withChainStarts(diag, ^(const dyld_chained_starts_in_image* starts) {
-                fixups.forEachFixupChainSegment(diag, starts, ^(const dyld_chained_starts_in_segment *segInfo, uint32_t segIndex, bool &stopSegment) {
-                    InputDylibVMAddress segmentVMAddr = this->segments[segIndex].inputVMAddress;
-                    fixups.forEachFixupInSegmentChains(diag, segInfo, segIndex, true,
-                                                       ^(dyld3::MachOFile::ChainedFixupPointerOnDisk *fixupLocation, uint64_t fixupSegmentOffset, bool &stopChain) {
-                        uint32_t bindOrdinal = 0;
-                        int64_t embeddedAddend = 0;
-                        if ( fixupLocation->isBind(segInfo->pointer_format, bindOrdinal, embeddedAddend) ) {
-                            MachOFile::PointerMetaData pmd(fixupLocation, segInfo->pointer_format);
-                            handleFixup(segmentVMAddr + VMOffset(fixupSegmentOffset), embeddedAddend, bindOrdinal, pmd, segIndex, stopChain);
-                        }
-                    });
-                });
-            });
-        } else if ( this->inputMF->hasOpcodeFixups() ) {
-            const dyld3::MachOFile::PointerMetaData pmd;
-            fixups.forEachBindLocation_Opcodes(diag,
-                                               ^(uint64_t runtimeOffset, uint32_t segmentIndex, unsigned int targetIndex, bool& stop) {
-                InputDylibVMAddress fixupVMAddr = this->inputLoadAddress + VMOffset(runtimeOffset);
-                handleFixup(fixupVMAddr, 0, targetIndex, pmd, segmentIndex, stop);
-            },
-            ^(uint64_t runtimeOffset, uint32_t segmentIndex, unsigned int overrideBindTargetIndex, bool& stop) {
-                assert(this->weakBindTargetsStartIndex.has_value());
-                InputDylibVMAddress fixupVMAddr = this->inputLoadAddress + VMOffset(runtimeOffset);
-                handleFixup(fixupVMAddr, 0, this->weakBindTargetsStartIndex.value() + overrideBindTargetIndex, pmd, segmentIndex, stop);
-            });
-        } else {
-            // Cache dylibs shouldn't use old style fixups.
-        }
-    });
-}
-
-void CacheDylib::bind(Diagnostics& diag, const BuilderConfig& config, Timer::AggregateTimer& timer,
-                      PatchInfo& dylibPatchInfo, FunctionVariantsOptimizer& functionVariantsOptimizer)
-{
-    Timer::AggregateTimer::Scope timedScope(timer, "dylib bind time");
-
-    // As we are running in parallel, addresses in other dylibs may not have been shifted yet.  We may also
-    // race looking at the export trie in a target dylib, while it is being shifted by AdjustDylibSegments.
-    // Given that, we'll look at our own cache dylib, but everyone elses input dylib, as those won't mutate
-    CoalescedGOTsMap coalescedGOTs      = optimizedSections.gots.getCoalescedGOTsMap();
-    CoalescedGOTsMap coalescedAuthGOTs  = optimizedSections.auth_gots.getCoalescedGOTsMap();
-    CoalescedGOTsMap coalescedAuthPtrs  = optimizedSections.auth_ptrs.getCoalescedGOTsMap();
 
     if ( this->inputMF->hasChainedFixups() )
-        bindWithChainedFixups(diag, config, coalescedGOTs, coalescedAuthGOTs, coalescedAuthPtrs, dylibPatchInfo,
-                              functionVariantsOptimizer);
+        bindWithChainedFixups(diag, config, coalescedGOTs, coalescedAuthGOTs, dylibPatchInfo);
     else if ( this->inputMF->hasOpcodeFixups() ) {
-        bindWithOpcodeFixups(diag, config, coalescedGOTs, coalescedAuthGOTs, coalescedAuthPtrs, dylibPatchInfo,
-                             functionVariantsOptimizer);
+        bindWithOpcodeFixups(diag, config, coalescedGOTs, coalescedAuthGOTs, dylibPatchInfo);
     } else {
         // Cache dylibs shouldn't use old style fixups.
     }
@@ -1528,16 +1428,16 @@
 }
 
 void CacheDylib::updateObjCSelectorReferences(Diagnostics& diag, const BuilderConfig& config,
-                                              Timer::AggregateTimer& timer, ObjCSelectorOptimizer& objcSelectorOptimizer)
-{
-    if ( !this->inputHdr->hasObjC() )
+                                              Timer::AggregateTimer& timer, const ObjCSelectorOptimizer& objcSelectorOptimizer)
+{
+    if ( !this->inputMF->hasObjC() )
         return;
 
     Timer::AggregateTimer::Scope timedScope(timer, "dylib updateObjCSelectorReferences time");
 
+    lsl::EphemeralAllocator allocator;
     __block objc_visitor::Visitor objcVisitor = this->makeCacheObjCVisitor(config,
                                                                            objcSelectorOptimizer.selectorStringsChunk,
-                                                                           nullptr,
                                                                            nullptr);
 
     // Update every selector reference to point to the canonical selectors
@@ -1555,8 +1455,7 @@
         objcVisitor.updateTargetVMAddress(selRefValue, newSelCacheVMAddress);
     });
 
-    objcVisitor.forEachMethodList(^(objc_visitor::MethodList& objcMethodList,
-                                    std::optional<metadata_visitor::ResolvedValue> extendedMethodTypes) {
+    auto visitMethodList = ^(objc_visitor::MethodList objcMethodList) {
         // Set both relative and pointer based lists to uniqued.  They will be after this method is done
         objcMethodList.setIsUniqued();
 
@@ -1582,6 +1481,31 @@
 
             objcVisitor.updateTargetVMAddress(nameRef, newSelCacheVMAddress);
         }
+    };
+
+    objcVisitor.forEachClassAndMetaClass(^(const objc_visitor::Class& objcClass, bool& stopClass) {
+        objc_visitor::MethodList objcMethodList = objcClass.getBaseMethods(objcVisitor);
+        visitMethodList(objcMethodList);
+    });
+
+    objcVisitor.forEachCategory(^(const objc_visitor::Category& objcCategory, bool& stopCategory) {
+        objc_visitor::MethodList instanceMethodList = objcCategory.getInstanceMethods(objcVisitor);
+        objc_visitor::MethodList classMethodList    = objcCategory.getClassMethods(objcVisitor);
+
+        visitMethodList(instanceMethodList);
+        visitMethodList(classMethodList);
+    });
+
+    objcVisitor.forEachProtocol(^(const objc_visitor::Protocol& objcProtocol, bool& stopProtocol) {
+        objc_visitor::MethodList instanceMethodList         = objcProtocol.getInstanceMethods(objcVisitor);
+        objc_visitor::MethodList classMethodList            = objcProtocol.getClassMethods(objcVisitor);
+        objc_visitor::MethodList optionalInstanceMethodList = objcProtocol.getOptionalInstanceMethods(objcVisitor);
+        objc_visitor::MethodList optionalClassMethodList    = objcProtocol.getOptionalClassMethods(objcVisitor);
+
+        visitMethodList(instanceMethodList);
+        visitMethodList(classMethodList);
+        visitMethodList(optionalInstanceMethodList);
+        visitMethodList(optionalClassMethodList);
     });
 }
 
@@ -1741,28 +1665,19 @@
                                                  Timer::AggregateTimer& timer,
                                                  const Chunk*           selectorStringsChunk)
 {
-    if ( !this->inputHdr->hasObjC() )
+    if ( !this->inputMF->hasObjC() )
         return;
 
     Timer::AggregateTimer::Scope timedScope(timer, "dylib convertObjCMethodListsToOffsets time");
 
-    __block objc_visitor::Visitor objcVisitor = this->makeCacheObjCVisitor(config, selectorStringsChunk, nullptr, nullptr);
-
-    // protocols can be listed multiple times in the _objc_protolist section, so we'll visit them multiple times here
-    // We don't want to convert the method list twice, so keep track of all seen method lists
-    // FIXME: Remove this once ld removes the duplicates (rdar://133008657)
-    __block std::unordered_set<const void*> seenMethodLists;
-
-    objcVisitor.forEachMethodList(^(objc_visitor::MethodList& objcMethodList,
-                                    std::optional<metadata_visitor::ResolvedValue> extendedMethodTypes) {
+    lsl::EphemeralAllocator allocator;
+    __block objc_visitor::Visitor objcVisitor = this->makeCacheObjCVisitor(config, selectorStringsChunk, nullptr);
+
+    auto visitMethodList = ^(objc_visitor::MethodList objcMethodList) {
         // Skip pointer based method lists
         if ( !objcMethodList.usesRelativeOffsets() )
             return;
 
-        // Skip method lists we've already converted
-        if ( bool inserted = seenMethodLists.insert(objcMethodList.getLocation()).second; !inserted )
-            return;
-
         uint32_t numMethods = objcMethodList.numMethods();
         for ( uint32_t i = 0; i != numMethods; ++i ) {
             objc_visitor::Method objcMethod = objcMethodList.getMethod(objcVisitor, i);
@@ -1776,6 +1691,31 @@
         }
 
         objcMethodList.setUsesOffsetsFromSelectorBuffer();
+    };
+
+    objcVisitor.forEachClassAndMetaClass(^(const objc_visitor::Class& objcClass, bool& stopClass) {
+        objc_visitor::MethodList objcMethodList = objcClass.getBaseMethods(objcVisitor);
+        visitMethodList(objcMethodList);
+    });
+
+    objcVisitor.forEachCategory(^(const objc_visitor::Category& objcCategory, bool& stopCategory) {
+        objc_visitor::MethodList instanceMethodList = objcCategory.getInstanceMethods(objcVisitor);
+        objc_visitor::MethodList classMethodList    = objcCategory.getClassMethods(objcVisitor);
+
+        visitMethodList(instanceMethodList);
+        visitMethodList(classMethodList);
+    });
+
+    objcVisitor.forEachProtocol(^(const objc_visitor::Protocol& objcProtocol, bool& stopProtocol) {
+        objc_visitor::MethodList instanceMethodList         = objcProtocol.getInstanceMethods(objcVisitor);
+        objc_visitor::MethodList classMethodList            = objcProtocol.getClassMethods(objcVisitor);
+        objc_visitor::MethodList optionalInstanceMethodList = objcProtocol.getOptionalInstanceMethods(objcVisitor);
+        objc_visitor::MethodList optionalClassMethodList    = objcProtocol.getOptionalClassMethods(objcVisitor);
+
+        visitMethodList(instanceMethodList);
+        visitMethodList(classMethodList);
+        visitMethodList(optionalInstanceMethodList);
+        visitMethodList(optionalClassMethodList);
     });
 }
 
@@ -1783,21 +1723,75 @@
                                      Timer::AggregateTimer& timer,
                                      const Chunk*           selectorStringsChunk)
 {
-    if ( !this->inputHdr->hasObjC() )
+    if ( !this->inputMF->hasObjC() )
         return;
 
     Timer::AggregateTimer::Scope timedScope(timer, "dylib sortObjCMethodLists time");
 
-    __block objc_visitor::Visitor objcVisitor = this->makeCacheObjCVisitor(config, selectorStringsChunk, nullptr, nullptr);
-
-    objcVisitor.forEachMethodList(^(objc_visitor::MethodList& objcMethodList,
-                                    std::optional<metadata_visitor::ResolvedValue> extendedMethodTypes) {
+    lsl::EphemeralAllocator allocator;
+    __block objc_visitor::Visitor objcVisitor = this->makeCacheObjCVisitor(config, selectorStringsChunk, nullptr);
+
+    auto visitMethodList = ^(objc_visitor::MethodList               objcMethodList,
+                             std::optional<metadata_visitor::ResolvedValue> extendedMethodTypes) {
         if ( objcMethodList.usesRelativeOffsets() )
             sortObjCRelativeMethodList(config, objcVisitor, objcMethodList, extendedMethodTypes);
         else
             sortObjCPointerMethodList(config, objcVisitor, objcMethodList, extendedMethodTypes);
 
         objcMethodList.setIsSorted();
+    };
+
+    objcVisitor.forEachClassAndMetaClass(^(const objc_visitor::Class& objcClass, bool& stopClass) {
+        objc_visitor::MethodList objcMethodList = objcClass.getBaseMethods(objcVisitor);
+        visitMethodList(objcMethodList, std::nullopt);
+    });
+
+    objcVisitor.forEachCategory(^(const objc_visitor::Category& objcCategory, bool& stopCategory) {
+        objc_visitor::MethodList instanceMethodList = objcCategory.getInstanceMethods(objcVisitor);
+        objc_visitor::MethodList classMethodList    = objcCategory.getClassMethods(objcVisitor);
+
+        visitMethodList(instanceMethodList, std::nullopt);
+        visitMethodList(classMethodList, std::nullopt);
+    });
+
+    objcVisitor.forEachProtocol(^(const objc_visitor::Protocol& objcProtocol, bool& stopProtocol) {
+        objc_visitor::MethodList instanceMethodList         = objcProtocol.getInstanceMethods(objcVisitor);
+        objc_visitor::MethodList classMethodList            = objcProtocol.getClassMethods(objcVisitor);
+        objc_visitor::MethodList optionalInstanceMethodList = objcProtocol.getOptionalInstanceMethods(objcVisitor);
+        objc_visitor::MethodList optionalClassMethodList    = objcProtocol.getOptionalClassMethods(objcVisitor);
+
+        // This is an optional flat array with entries for all method lists.
+        // Each method list of length N has N char* entries in this list, if its present
+        std::optional<metadata_visitor::ResolvedValue> extendedMethodTypes = objcProtocol.getExtendedMethodTypes(objcVisitor);
+        const uint32_t pointerSize = objcVisitor.mf()->pointerSize();
+
+        visitMethodList(instanceMethodList, extendedMethodTypes);
+        if ( extendedMethodTypes.has_value() ) {
+            const uint8_t* methodTypesBase = (const uint8_t*)extendedMethodTypes->value();
+            methodTypesBase += (instanceMethodList.numMethods() * pointerSize);
+            extendedMethodTypes.emplace(metadata_visitor::ResolvedValue(extendedMethodTypes.value(), methodTypesBase));
+        }
+
+        visitMethodList(classMethodList, extendedMethodTypes);
+        if ( extendedMethodTypes.has_value() ) {
+            const uint8_t* methodTypesBase = (const uint8_t*)extendedMethodTypes->value();
+            methodTypesBase += (classMethodList.numMethods() * pointerSize);
+            extendedMethodTypes.emplace(metadata_visitor::ResolvedValue(extendedMethodTypes.value(), methodTypesBase));
+        }
+
+        visitMethodList(optionalInstanceMethodList, extendedMethodTypes);
+        if ( extendedMethodTypes.has_value() ) {
+            const uint8_t* methodTypesBase = (const uint8_t*)extendedMethodTypes->value();
+            methodTypesBase += (optionalInstanceMethodList.numMethods() * pointerSize);
+            extendedMethodTypes.emplace(metadata_visitor::ResolvedValue(extendedMethodTypes.value(), methodTypesBase));
+        }
+
+        visitMethodList(optionalClassMethodList, extendedMethodTypes);
+        if ( extendedMethodTypes.has_value() ) {
+            const uint8_t* methodTypesBase = (const uint8_t*)extendedMethodTypes->value();
+            methodTypesBase += (optionalClassMethodList.numMethods() * pointerSize);
+            extendedMethodTypes.emplace(metadata_visitor::ResolvedValue(extendedMethodTypes.value(), methodTypesBase));
+        }
     });
 }
 
@@ -1811,6 +1805,8 @@
         // Must be split seg v1
         return;
     }
+
+    const dyld3::MachOFile* mf = this->cacheMF;
 
     __block uint32_t textSectionIndex = ~0U;
     __block const uint8_t* textSectionContent = nullptr;
@@ -1818,16 +1814,16 @@
     __block uint64_t selRefSectionVMAddr = 0;
     // The mach_header is section 0
     __block uint32_t sectionIndex = 1;
-    this->cacheHdr->forEachSection(^(const Header::SegmentInfo &segInfo, const Header::SectionInfo &sectInfo, bool &stop) {
-        if ( (sectInfo.segmentName == "__TEXT" ) && (sectInfo.sectionName == "__text") ) {
+    mf->forEachSection(^(const dyld3::MachOFile::SectionInfo &sectInfo, bool malformedSectionRange, bool &stop) {
+        if ( !strcmp(sectInfo.segInfo.segName, "__TEXT") && !strcmp(sectInfo.sectName, "__text") ) {
             textSectionIndex = sectionIndex;
-            VMOffset sectionOffsetInSegment(sectInfo.address - segInfo.vmaddr);
-            textSectionContent = this->segments[sectInfo.segIndex].subCacheBuffer;
+            VMOffset sectionOffsetInSegment(sectInfo.sectAddr - sectInfo.segInfo.vmAddr);
+            textSectionContent = this->segments[sectInfo.segInfo.segIndex].subCacheBuffer;
             textSectionContent += sectionOffsetInSegment.rawValue();
         }
-        if ( sectInfo.segmentName.starts_with("__DATA") && (sectInfo.sectionName == "__objc_selrefs") ) {
+        if ( !strncmp(sectInfo.segInfo.segName, "__DATA", 6) && !strcmp(sectInfo.sectName, "__objc_selrefs") ) {
             selRefSectionIndex = sectionIndex;
-            selRefSectionVMAddr = sectInfo.address;
+            selRefSectionVMAddr = sectInfo.sectAddr;
         }
         ++sectionIndex;
     });
@@ -1875,30 +1871,31 @@
                                             Timer::AggregateTimer& timer,
                                             const ObjCStringsChunk* selectorStringsChunk)
 {
-    const bool logSelectors = config.log.printDebugCacheLayout;
+    const bool logSelectors = false;
 
     Timer::AggregateTimer::Scope timedScope(timer, "dylib optimizeLoadsFromConstants time");
 
-    if ( !this->cacheHdr->is64() )
+    const dyld3::MachOFile* mf = this->cacheMF;
+    if ( !mf->is64() )
         return;
 
     __block const uint8_t* textSectionContent = nullptr;
     __block CacheVMAddress textSectionVMAddr;
     __block const uint8_t* selRefSectionContent = nullptr;
     __block CacheVMAddress selRefSectionVMAddr;
-    this->cacheHdr->forEachSection(^(const Header::SegmentInfo &segInfo, const Header::SectionInfo &sectInfo, bool &stop) {
-        VMOffset sectionOffsetInSegment(sectInfo.address - segInfo.vmaddr);
-        if ( ( sectInfo.segmentName == "__TEXT" ) && (sectInfo.sectionName == "__text") ) {
-            textSectionContent = this->segments[sectInfo.segIndex].subCacheBuffer;
+    mf->forEachSection(^(const dyld3::MachOFile::SectionInfo &sectInfo, bool malformedSectionRange, bool &stop) {
+        VMOffset sectionOffsetInSegment(sectInfo.sectAddr - sectInfo.segInfo.vmAddr);
+        if ( !strcmp(sectInfo.segInfo.segName, "__TEXT") && !strcmp(sectInfo.sectName, "__text") ) {
+            textSectionContent = this->segments[sectInfo.segInfo.segIndex].subCacheBuffer;
             textSectionContent += sectionOffsetInSegment.rawValue();
 
-            textSectionVMAddr = CacheVMAddress(sectInfo.address);
-        }
-        if ( sectInfo.segmentName.starts_with("__DATA") && (sectInfo.sectionName == "__objc_selrefs") ) {
-            selRefSectionContent = this->segments[sectInfo.segIndex].subCacheBuffer;
+            textSectionVMAddr = CacheVMAddress(sectInfo.sectAddr);
+        }
+        if ( !strncmp(sectInfo.segInfo.segName, "__DATA", 6) && !strcmp(sectInfo.sectName, "__objc_selrefs") ) {
+            selRefSectionContent = this->segments[sectInfo.segInfo.segIndex].subCacheBuffer;
             selRefSectionContent += sectionOffsetInSegment.rawValue();
 
-            selRefSectionVMAddr = CacheVMAddress(sectInfo.address);
+            selRefSectionVMAddr = CacheVMAddress(sectInfo.sectAddr);
         }
     });
 
@@ -2059,9 +2056,9 @@
     if ( bindTarget.kind != BindTarget::Kind::inputImage )
         return Error("Couldn't build IMP caches because: symbol is wrong kind");
 
-    BindTarget::InputImage bindInputImage    = bindTarget.inputImage;
-    InputDylibVMAddress    targetInputVMAddr = bindInputImage.targetDylib->inputLoadAddress + bindInputImage.targetRuntimeOffset;
-    CacheVMAddress         targetCacheVMAddr = bindInputImage.targetDylib->adjustor->adjustVMAddr(targetInputVMAddr);
+    BindTarget::InputImage inputImage        = bindTarget.inputImage;
+    InputDylibVMAddress    targetInputVMAddr = inputImage.targetDylib->inputLoadAddress + inputImage.targetRuntimeOffset;
+    CacheVMAddress         targetCacheVMAddr = inputImage.targetDylib->adjustor->adjustVMAddr(targetInputVMAddr);
 
     // Find the segment for the content
     for ( DylibSegmentChunk& segment : this->segments ) {
@@ -2126,7 +2123,7 @@
     if ( !objcIMPCachesOptimizer.builder )
         return Error();
 
-    const bool log = config.log.printDebugIMPCaches;
+    const bool log = false;
 
     Timer::AggregateTimer::Scope timedScope(timer, "emitObjCIMPCaches time");
 
@@ -2142,7 +2139,8 @@
     if ( !this->inputMF->hasChainedFixupsLoadCommand() )
         return Error();
 
-    __block objc_visitor::Visitor objcVisitor = this->makeCacheObjCVisitor(config, nullptr, nullptr, nullptr);
+    lsl::EphemeralAllocator allocator;
+    __block objc_visitor::Visitor objcVisitor = this->makeCacheObjCVisitor(config, nullptr, nullptr);
 
     // Walk the classes in this dylib, and see if any have an IMP cache
     objcVisitor.forEachClassAndMetaClass(^(objc_visitor::Class& objcClass, bool& stopClass) {
@@ -2163,18 +2161,9 @@
         if ( objcClass.getMethodCachePropertiesVMAddr(objcVisitor).has_value() )
             return;
 
-        MachOFile::PointerMetaData PMD;
-        if ( config.layout.hasAuthRegion && (objcIMPCachesOptimizer.libobjcImpCachesVersion >= 4) ) {
-            PMD.diversity         = 0x9cff; // hash of "originalPreoptCache"
-            PMD.high8             = 0;
-            PMD.authenticated     = 1;
-            PMD.key               = 2;     // DA
-            PMD.usesAddrDiversity = 1;
-        }
-
         // Set the "vtable" to point to the cache
         CacheVMAddress impCacheVMAddr = objcIMPCachesOptimizer.impCachesChunk->cacheVMAddress + impCacheOffset;
-        objcClass.setMethodCachePropertiesVMAddr(objcVisitor, VMAddress(impCacheVMAddr.rawValue()), PMD);
+        objcClass.setMethodCachePropertiesVMAddr(objcVisitor, VMAddress(impCacheVMAddr.rawValue()));
 
         // Tell the slide info emitter to slide this location
         metadata_visitor::ResolvedValue vtableField = objcClass.getMethodCachePropertiesField(objcVisitor);
@@ -2225,13 +2214,13 @@
                 currentBucket->impOffset = 0;
             } else {
                 imp_caches::BucketMethod bucketMethod = {
+                    .installName = bucket.installName,
                     .className = bucket.className,
                     .methodName = bucket.methodName,
                     .isInstanceMethod = bucket.isInstanceMethod
                 };
-                const auto& dylibMethodMap = objcIMPCachesOptimizer.methodMap.at(bucket.installName);
-                auto bucketIt = dylibMethodMap.find(bucketMethod);
-                assert(bucketIt != dylibMethodMap.end());
+                auto bucketIt = objcIMPCachesOptimizer.methodMap.find(bucketMethod);
+                assert(bucketIt != objcIMPCachesOptimizer.methodMap.end());
 
                 const ObjCIMPCachesOptimizer::InputDylibLocation& bucketInputLocation = bucketIt->second;
                 CacheVMAddress methodVMAddr = bucketInputLocation.first->adjustor->adjustVMAddr(bucketInputLocation.second);
@@ -2273,20 +2262,8 @@
 {
     CacheDylib::GOTToTargetMap gotToTargetMap;
 
-    for ( UniquedGOTKind sectionKind : { UniquedGOTKind::regular, UniquedGOTKind::authGot, UniquedGOTKind::authPtr } ) {
-        std::span<const std::vector<PatchInfo::GOTInfo>> bindGOTUses;
-        switch ( sectionKind ) {
-            case UniquedGOTKind::regular:
-                bindGOTUses = dylibPatchInfo.bindGOTUses;
-                break;
-            case UniquedGOTKind::authGot:
-                bindGOTUses = dylibPatchInfo.bindAuthGOTUses;
-                break;
-            case UniquedGOTKind::authPtr:
-                bindGOTUses = dylibPatchInfo.bindAuthPtrUses;
-                break;
-        }
-
+    for ( bool auth : { false, true } ) {
+        const auto& bindGOTUses = auth ? dylibPatchInfo.bindAuthGOTUses : dylibPatchInfo.bindGOTUses;
         assert(this->bindTargets.size() == bindGOTUses.size());
         for ( uint32_t bindIndex = 0; bindIndex != this->bindTargets.size(); ++bindIndex ) {
             const BindTarget& bindTarget = this->bindTargets[bindIndex];
@@ -2304,9 +2281,9 @@
             const BindTarget::CacheImage& cacheImageTarget = bindTarget.cacheImage;
             CacheVMAddress bindTargetVMAddr = cacheImageTarget.targetDylib->cacheLoadAddress + cacheImageTarget.targetRuntimeOffset;
 
+
             for ( const PatchInfo::GOTInfo& gotInfo : clientUses ) {
-                CacheVMAddress gotVMAddr = gotInfo.useLocation.clientGOT->cacheVMAddress + gotInfo.useLocation.clientGOTOffset;
-                gotToTargetMap[gotVMAddr] = bindTargetVMAddr;
+                gotToTargetMap[gotInfo.patchInfo.cacheVMAddr] = bindTargetVMAddr;
             }
         }
     }
@@ -2359,26 +2336,27 @@
     };
 
     // Walk all the stubs in the stubs sections
-    this->cacheHdr->forEachSection(^(const Header::SegmentInfo &segInfo, const Header::SectionInfo &sectInfo, bool &stop) {
-        unsigned sectionType = (sectInfo.flags & SECTION_TYPE);
+    this->cacheMF->forEachSection(^(const dyld3::MachOFile::SectionInfo &sectInfo,
+                                    bool malformedSectionRange, bool &stop) {
+        unsigned sectionType = (sectInfo.sectFlags & SECTION_TYPE);
         if ( sectionType != S_SYMBOL_STUBS )
             return;
 
         // We can only optimize certain stubs sections, depending on the arch
-        if ( sectInfo.sectionName != this->developmentStubs.sectionName )
+        if ( sectInfo.sectName != this->developmentStubs.sectionName )
             return;
-        if ( sectInfo.segmentName != this->developmentStubs.segmentName )
+        if ( sectInfo.segInfo.segName != this->developmentStubs.segmentName )
             return;
 
         // reserved1/reserved2 tell us how large stubs are, and our offset in to the symbol table
         const uint64_t indirectTableOffset = sectInfo.reserved1;
         const uint64_t stubsSize = sectInfo.reserved2;
-        const uint64_t stubsCount = sectInfo.size / stubsSize;
-
-        CacheVMAddress stubsSectionBaseAddress(sectInfo.address);
+        const uint64_t stubsCount = sectInfo.sectSize / stubsSize;
+
+        CacheVMAddress stubsSectionBaseAddress(sectInfo.sectAddr);
 
         // Work out where the stub buffer is in the cache
-        const DylibSegmentChunk& segment = this->segments[segInfo.segmentIndex];
+        const DylibSegmentChunk& segment = this->segments[sectInfo.segInfo.segIndex];
         CacheVMAddress segmentBaseAddress = segment.cacheVMAddress;
         VMOffset sectionOffsetInSegment = stubsSectionBaseAddress - segmentBaseAddress;
         const uint8_t* sectionBuffer = segment.subCacheBuffer + sectionOffsetInSegment.rawValue();
@@ -2402,7 +2380,7 @@
                 continue;
             }
 
-            if ( this->cacheHdr->isArch("arm64") ) {
+            if ( this->cacheMF->isArch("arm64") ) {
                 uint64_t targetLPAddr = StubOptimizer::gotAddrFromArm64Stub(diag, this->installName,
                                                                             stubInstrs,
                                                                             oldStubVMAddr.rawValue());
@@ -2428,9 +2406,9 @@
                     // Customer stub
                     uint8_t* newStubBuffer = customerStubs.subCacheBuffer + stubOffset;
                     StubOptimizer::generateArm64StubTo(newStubBuffer, newStubVMAddr.rawValue(),
-                                                       targetLPAddr, gotTargetVMAddr->rawValue());
+                                                       gotTargetVMAddr->rawValue());
                 }
-            } else if ( this->cacheHdr->isArch("arm64e") ) {
+            } else if ( this->cacheMF->isArch("arm64e") ) {
                 uint64_t targetLPAddr = StubOptimizer::gotAddrFromArm64eStub(diag, this->installName,
                                                                              stubInstrs,
                                                                              oldStubVMAddr.rawValue());
@@ -2456,9 +2434,9 @@
                     // Customer stub
                     uint8_t* newStubBuffer = customerStubs.subCacheBuffer + stubOffset;
                     StubOptimizer::generateArm64eStubTo(newStubBuffer, newStubVMAddr.rawValue(),
-                                                        targetLPAddr, gotTargetVMAddr->rawValue());
+                                                        gotTargetVMAddr->rawValue());
                 }
-            } else if ( this->cacheHdr->isArch("arm64_32") ) {
+            } else if ( this->cacheMF->isArch("arm64_32") ) {
                 uint64_t targetLPAddr = StubOptimizer::gotAddrFromArm64_32Stub(diag, this->installName,
                                                                                stubInstrs,
                                                                                oldStubVMAddr.rawValue());
@@ -2509,29 +2487,29 @@
     {
         // Section #0 is the mach_header
         __block uint32_t sectionIndex = 1;
-        this->cacheHdr->forEachSection(^(const Header::SegmentInfo &segInfo, const Header::SectionInfo &sectInfo, bool &stop) {
-            if ( sectInfo.segmentName == "__TEXT" ) {
-                if ( sectInfo.sectionName == "__text" ) {
+        this->cacheMF->forEachSection(^(const dyld3::MachOFile::SectionInfo &sectInfo, bool malformedSectionRange, bool &stop) {
+            if ( !strcmp(sectInfo.segInfo.segName, "__TEXT") ) {
+                if ( !strcmp(sectInfo.sectName, "__text") ) {
                     textSectionIndex = sectionIndex;
-                    textSectionVMAddr = sectInfo.address;
+                    textSectionVMAddr = sectInfo.sectAddr;
 
                     // Work out the buffer for the text section
-                    const DylibSegmentChunk& segment = this->segments[segInfo.segmentIndex];
+                    const DylibSegmentChunk& segment = this->segments[sectInfo.segInfo.segIndex];
                     CacheVMAddress segmentBaseAddress = segment.cacheVMAddress;
-                    CacheVMAddress sectionBaseAddress(sectInfo.address);
+                    CacheVMAddress sectionBaseAddress(sectInfo.sectAddr);
                     VMOffset sectionOffsetInSegment = sectionBaseAddress - segmentBaseAddress;
                     textSectionBuffer = segment.subCacheBuffer + sectionOffsetInSegment.rawValue();
-                } else if ( sectInfo.sectionName == "__stubs" ) {
+                } else if ( !strcmp(sectInfo.sectName, "__stubs") ) {
                     // On arm64e devices, we ignore __stubs and only handle __auth_stubs
-                    if ( !this->cacheHdr->isArch("arm64e") ) {
+                    if ( !this->cacheMF->isArch("arm64e") ) {
                         stubSectionIndex = sectionIndex;
-                        stubSectionVMAddr = sectInfo.address;
+                        stubSectionVMAddr = sectInfo.sectAddr;
                     }
-                } else if ( sectInfo.sectionName == "__auth_stubs" ) {
+                } else if ( !strcmp(sectInfo.sectName, "__auth_stubs") ) {
                     // On arm64e devices, we ignore __stubs and only handle __auth_stubs
-                    if ( this->cacheHdr->isArch("arm64e") ) {
+                    if ( this->cacheMF->isArch("arm64e") ) {
                         stubSectionIndex = sectionIndex;
-                        stubSectionVMAddr = sectInfo.address;
+                        stubSectionVMAddr = sectInfo.sectAddr;
                     }
                 }
             }
@@ -2705,7 +2683,7 @@
                             CacheFileOffset readWriteFileOffset)
 {
     // validate there is enough free space to add the load commands
-    uint32_t freeSpace = ((const Header*)objcMF)->loadCommandsFreeSpace();
+    uint32_t freeSpace = objcMF->loadCommandsFreeSpace();
     const uint32_t segSize = sizeof(macho_segment_command<P>);
     if ( freeSpace < 2*segSize ) {
         diag.warning("not enough space in libojbc.dylib to add load commands for objc optimization regions");
@@ -2713,18 +2691,11 @@
     }
 
     // find location of LINKEDIT LC_SEGMENT load command, we need to insert new segments before it
-    uint32_t linkeditIndex = 0;
-    uint8_t* linkeditSeg = nullptr;
-    linkeditSeg = (uint8_t*)((mach_o::Header*)objcMF)->findLoadCommand(linkeditIndex, ^bool(const load_command *lc) {
-        CString segmentName;
-        if ( lc->cmd == LC_SEGMENT )
-            segmentName = ((const segment_command*)lc)->segname;
-        else if ( lc->cmd == LC_SEGMENT_64 )
-            segmentName = ((const segment_command_64*)lc)->segname;
-        
-        return segmentName == "__LINKEDIT";
-    });
-    
+    __block uint8_t* linkeditSeg = nullptr;
+    objcMF->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) {
+        if ( strcmp(info.segName, "__LINKEDIT") == 0 )
+            linkeditSeg = (uint8_t*)objcMF + info.loadCommandOffset;
+    });
     if ( linkeditSeg == nullptr ) {
         diag.warning("__LINKEDIT not found in libojbc.dylib");
         return;
@@ -2769,9 +2740,7 @@
 
 void CacheDylib::addObjcSegments(Diagnostics& diag, Timer::AggregateTimer& timer,
                                  const ObjCHeaderInfoReadOnlyChunk* headerInfoReadOnlyChunk,
-                                 const ObjCImageInfoChunk* imageInfoChunk,
                                  const ObjCProtocolHashTableChunk* protocolHashTableChunk,
-                                 const ObjCPreAttachedCategoriesChunk* preAttachedCategoriesChunk,
                                  const ObjCHeaderInfoReadWriteChunk* headerInfoReadWriteChunk,
                                  const ObjCCanonicalProtocolsChunk* canonicalProtocolsChunk)
 {
@@ -2784,18 +2753,16 @@
     // Find the ranges for OBJC_RO and OBJC_RW
 
     // Read-only
-    // Note these asserts are just to make sure we use the correct chunks for the start/end
-    static_assert(Chunk::Kind::objcHeaderInfoRO < Chunk::Kind::objcImageInfo);
-    static_assert(Chunk::Kind::objcImageInfo < Chunk::Kind::objcStrings);
+    // Note these asserts are just to make sure we use the correct
+    static_assert(Chunk::Kind::objcHeaderInfoRO < Chunk::Kind::objcStrings);
     static_assert(Chunk::Kind::objcStrings < Chunk::Kind::objcSelectorsHashTable);
     static_assert(Chunk::Kind::objcSelectorsHashTable < Chunk::Kind::objcClassesHashTable);
     static_assert(Chunk::Kind::objcClassesHashTable < Chunk::Kind::objcProtocolsHashTable);
     static_assert(Chunk::Kind::objcProtocolsHashTable < Chunk::Kind::objcIMPCaches);
-    static_assert(Chunk::Kind::objcIMPCaches < Chunk::Kind::objcPreAttachedCategories);
 
     CacheFileOffset readOnlyFileOffset = headerInfoReadOnlyChunk->subCacheFileOffset;
     CacheVMAddress readOnlyVMAddr = headerInfoReadOnlyChunk->cacheVMAddress;
-    CacheVMSize readOnlyVMSize = (preAttachedCategoriesChunk->cacheVMAddress + preAttachedCategoriesChunk->cacheVMSize) - readOnlyVMAddr;
+    CacheVMSize readOnlyVMSize = (protocolHashTableChunk->cacheVMAddress + protocolHashTableChunk->cacheVMSize) - readOnlyVMAddr;
 
 
     // Read-write
@@ -2805,7 +2772,7 @@
     CacheVMAddress readWriteVMAddr = headerInfoReadWriteChunk->cacheVMAddress;
     CacheVMSize readWriteVMSize = (canonicalProtocolsChunk->cacheVMAddress + canonicalProtocolsChunk->cacheVMSize) - readWriteVMAddr;
 
-    if ( this->inputHdr->is64() ) {
+    if ( this->inputMF->is64() ) {
         typedef Pointer64<LittleEndian> P;
         addObjcSegments<P>(diag, this->cacheMF,
                            readOnlyVMAddr, readOnlyVMSize, readOnlyFileOffset,
@@ -2818,81 +2785,9 @@
     }
 }
 
-void CacheDylib::removeLinkedDylibs(Diagnostics& diag)
-{
-    mach_o::HeaderWriter* header = (mach_o::HeaderWriter*)cacheHdr;
-    uint32_t        lcLibSystemIndex = 0;
-    if ( !header->findLoadCommand(lcLibSystemIndex, ^bool(const load_command *lc) {
-        const dylib_command* dyliblc = mach_o::Header::isDylibLoadCommand(lc);
-        if ( !dyliblc ) return false;
-
-        const char* loadPath = (char*)dyliblc + dyliblc->dylib.name.offset;
-        return strstr(loadPath, "libSystem");
-    }) ) {
-        diag.error("can't remove linked dylibs from %s, expected to find libSystem dependency", header->installName());
-        return;
-    }
-
-    uint32_t lcDylibStart = 0;
-    uint32_t lcDylibEnd = 0;
-    header->findLoadCommandRange(lcDylibStart, lcDylibEnd, ^bool(const load_command *lc) {
-        return mach_o::Header::isDylibLoadCommand(lc) != nullptr;
-    });
-    // libSystem was found, so the range of dylib load commands also must not be empty
-    assert(lcDylibStart != lcDylibEnd);
-    assert(lcLibSystemIndex >= lcDylibStart);
-
-    if ( lcDylibStart != lcLibSystemIndex ) {
-        diag.error("expected libSystem to be the first linked dylib of %s, but it's ordinal is: %u",
-                header->installName(), lcLibSystemIndex-lcDylibStart);
-        return;
-    }
-
-    // This removes all load commands after LC_LOAD_DYLIB of libSystem
-    if ( mach_o::Error err = header->removeLoadCommands(lcLibSystemIndex+1, lcDylibEnd) )
-        diag.error(err);
-}
-
-void CacheDylib::addLinkedDylib(Diagnostics& diag, const CacheDylib& dylib)
-{
-    const char* dylibInstallName = nullptr;
-    Version32   compatVersion;
-    Version32   currentVersion;
-    dylib.inputHdr->getDylibInstallName(&dylibInstallName, &compatVersion, &currentVersion);
-
-    // find the range of all LC_LOAD* commands, new dylib will be added as last
-    uint32_t lcLoadStart = 0;
-    uint32_t lcLoadEnd = 0;
-
-    mach_o::HeaderWriter* header = (mach_o::HeaderWriter*)this->cacheHdr;
-    header->findLoadCommandRange(lcLoadStart, lcLoadEnd, ^bool(const load_command *lc) {
-        return mach_o::Header::isDylibLoadCommand(lc) != nullptr;
-    });
-
-    if ( lcLoadEnd == 0 ) {
-        // there should be at least one already
-        diag.error("%s has no linked dylibs", header->installName());
-        return;
-    }
-
-    // determine command size
-    mach_o::LinkedDylibAttributes attr = mach_o::LinkedDylibAttributes::regular;
-    uint32_t traditionalCmd = 0;
-    uint32_t cmdSize = header->sizeForLinkedDylibCommand(dylibInstallName, attr, traditionalCmd);
-
-    // insert command
-    load_command* lc = header->insertLoadCommand(lcLoadEnd, cmdSize);
-    if ( lc == nullptr ) {
-        diag.error("not enough space in %s to add %s load command", header->installName(), dylibInstallName);
-        return;
-    }
-    header->setLinkedDylib(lc, dylibInstallName, attr, mach_o::Version32(compatVersion), mach_o::Version32(currentVersion));
-}
-
 objc_visitor::Visitor CacheDylib::makeCacheObjCVisitor(const BuilderConfig& config,
                                                        const Chunk* selectorStringsChunk,
-                                                       const ObjCCanonicalProtocolsChunk* canonicalProtocolsChunk,
-                                                       const ObjCPreAttachedCategoriesChunk* categoriesChunk) const
+                                                       const ObjCCanonicalProtocolsChunk* canonicalProtocolsChunk) const
 {
     // Get the segment ranges.  We need this as the dylib's segments are in different buffers, not in VM layout
     std::vector<metadata_visitor::Segment> cacheSegments;
@@ -2940,19 +2835,6 @@
         cacheSegments.push_back(std::move(segment));
     }
 
-    // Add the categories data chunk too.  That way we can resolve references which land on it
-    if ( categoriesChunk != nullptr ) {
-        metadata_visitor::Segment segment;
-        segment.startVMAddr = VMAddress(categoriesChunk->cacheVMAddress.rawValue());
-        segment.endVMAddr   = VMAddress((categoriesChunk->cacheVMAddress + categoriesChunk->cacheVMSize).rawValue());
-        segment.bufferStart = categoriesChunk->subCacheBuffer;
-
-        // Cache segments never have a chained format. They always use the Fixup struct
-        segment.onDiskDylibChainedPointerFormat = { };
-
-        cacheSegments.push_back(std::move(segment));
-    }
-
     VMAddress selectorStringsAddress;
     if ( selectorStringsChunk != nullptr )
         selectorStringsAddress = VMAddress(selectorStringsChunk->cacheVMAddress.rawValue());
@@ -2999,7 +2881,7 @@
 metadata_visitor::Visitor CacheDylib::makeCacheVisitor(const BuilderConfig& config) const
 {
     // Get the segment ranges.  We need this as the dylib's segments are in different buffers, not in VM layout
-    __block std::vector<metadata_visitor::Segment> cacheSegments;
+    std::vector<metadata_visitor::Segment> cacheSegments;
     cacheSegments.reserve(this->segments.size());
     for ( uint32_t segIndex = 0; segIndex != this->segments.size(); ++segIndex ) {
         const DylibSegmentChunk& segmentInfo = this->segments[segIndex];
@@ -3019,17 +2901,36 @@
     }
 
     // Add the GOTs too, if we have them
-    optimizedSections.forEachCacheGOTChunk(^(const cache_builder::Chunk* chunk) {
-        metadata_visitor::Segment segment;
-        segment.startVMAddr = VMAddress(chunk->cacheVMAddress.rawValue());
-        segment.endVMAddr   = VMAddress((chunk->cacheVMAddress + chunk->cacheVMSize).rawValue());
-        segment.bufferStart = chunk->subCacheBuffer;
-
-        // Cache segments never have a chained format. They always use the Fixup struct
-        segment.onDiskDylibChainedPointerFormat = { };
-
-        cacheSegments.push_back(std::move(segment));
-    });
+    if ( this->optimizedSections.gots.subCacheSection != nullptr ) {
+        auto* chunk = this->optimizedSections.gots.subCacheSection->cacheChunk;
+        if ( chunk != nullptr ) {
+            metadata_visitor::Segment segment;
+            segment.startVMAddr = VMAddress(chunk->cacheVMAddress.rawValue());
+            segment.endVMAddr   = VMAddress((chunk->cacheVMAddress + chunk->cacheVMSize).rawValue());
+            segment.bufferStart = chunk->subCacheBuffer;
+
+            // Cache segments never have a chained format. They always use the Fixup struct
+            segment.onDiskDylibChainedPointerFormat = { };
+
+            cacheSegments.push_back(std::move(segment));
+        }
+    }
+
+    // Add the auth GOTs too, if we have them
+    if ( this->optimizedSections.auth_gots.subCacheSection != nullptr ) {
+        auto* chunk = this->optimizedSections.auth_gots.subCacheSection->cacheChunk;
+        if ( chunk != nullptr ) {
+            metadata_visitor::Segment segment;
+            segment.startVMAddr = VMAddress(chunk->cacheVMAddress.rawValue());
+            segment.endVMAddr   = VMAddress((chunk->cacheVMAddress + chunk->cacheVMSize).rawValue());
+            segment.bufferStart = chunk->subCacheBuffer;
+
+            // Cache segments never have a chained format. They always use the Fixup struct
+            segment.onDiskDylibChainedPointerFormat = { };
+
+            cacheSegments.push_back(std::move(segment));
+        }
+    }
 
     std::vector<uint64_t> unusedBindTargets;
     metadata_visitor::Visitor visitor(config.layout.cacheBaseAddress, this->cacheMF,
@@ -3045,17 +2946,18 @@
                                                       CacheVMSize sectionVMSize,
                                                       bool& stop))
 {
-    this->inputHdr->forEachSection(^(const Header::SegmentInfo &segInfo, const Header::SectionInfo &sectInfo,
-                                    bool &stop) {
-        const DylibSegmentChunk& segment = this->segments[sectInfo.segIndex];
-
-        VMAddress sectionVMAddr(sectInfo.address);
-        VMAddress segmentVMAddr(segInfo.vmaddr);
+    this->inputMF->forEachSection(^(const dyld3::MachOFile::SectionInfo &sectInfo,
+                                    bool malformedSectionRange, bool &stop) {
+        const DylibSegmentChunk& segment = this->segments[sectInfo.segInfo.segIndex];
+
+        VMAddress sectionVMAddr(sectInfo.sectAddr);
+        VMAddress segmentVMAddr(sectInfo.segInfo.vmAddr);
         VMOffset sectionOffsetInSegment = sectionVMAddr - segmentVMAddr;
         uint8_t* sectionBuffer = segment.subCacheBuffer + sectionOffsetInSegment.rawValue();
         CacheVMAddress cacheVMAddr = segment.cacheVMAddress + sectionOffsetInSegment;
 
-        callback(sectInfo.segmentName, sectInfo.sectionName,
-                 sectionBuffer, cacheVMAddr, CacheVMSize(sectInfo.size), stop);
-    });
-}
+        callback(std::string_view(sectInfo.segInfo.segName, strnlen(sectInfo.segInfo.segName, 16)),
+                 std::string_view(sectInfo.sectName, strnlen(sectInfo.sectName, 16)),
+                 sectionBuffer, cacheVMAddr, CacheVMSize(sectInfo.sectSize), stop);
+    });
+}