Loading...
mach_o/ChainedFixups.cpp dyld-1162 dyld-1330
--- dyld/dyld-1162/mach_o/ChainedFixups.cpp
+++ dyld/dyld-1330/mach_o/ChainedFixups.cpp
@@ -29,17 +29,14 @@
 #include <mach-o/loader.h>
 #include <mach-o/nlist.h>
 
-#if BUILDING_MACHO_WRITER
-#include "Algorithm.h"
-#include "Array.h"
-#endif
-
 #include "Array.h"
 
 #include "ChainedFixups.h"
 #include "Misc.h"
 #include "Image.h"
 
+
+
 using dyld3::Array;
 
 namespace mach_o {
@@ -55,10 +52,14 @@
 {
 }
 
-const dyld_chained_fixups_header* ChainedFixups::bytes(size_t& size) const
-{
-    size = _fixupsSize;
+const dyld_chained_fixups_header* ChainedFixups::linkeditHeader() const
+{
     return _fixupsHeader;
+}
+
+const dyld_chained_starts_offsets* ChainedFixups::startsSectionHeader() const
+{
+    return _chainStartsHeader;
 }
 
 void ChainedFixups::forEachBindTarget(void (^callback)(const Fixup::BindTarget&, bool& stop)) const
@@ -154,22 +155,8 @@
     return (dyld_chained_starts_in_segment*)((uint8_t*)imageStarts + segInfoOffset);
 }
 
-const ChainedFixups::PointerFormat& ChainedFixups::pointerFormat() const
-{
-    const dyld_chained_starts_in_image* imageStarts = (dyld_chained_starts_in_image*)((uint8_t*)_fixupsHeader + _fixupsHeader->starts_offset);
-    for (uint32_t segIndex=0; segIndex < imageStarts->seg_count; ++segIndex) {
-        uint32_t segInfoOffset = imageStarts->seg_info_offset[segIndex];
-        if ( segInfoOffset == 0 )
-            continue;
-        const dyld_chained_starts_in_segment* segStarts = (dyld_chained_starts_in_segment*)((uint8_t*)imageStarts + segInfoOffset);
-        if ( segStarts->pointer_format != 0 )
-            return PointerFormat::make(segStarts->pointer_format);
-    }
-    assert(0 && "can't find pointer format");
-}
-
 void ChainedFixups::forEachFixupChainStartLocation(std::span<const MappedSegment> segments,
-                                                   void (^callback)(const void* loc, uint32_t segIndex, const PointerFormat&, bool& stop)) const
+                                                   void (^callback)(const void* loc, uint32_t segIndex, uint32_t pageIndex, uint32_t pageSize, const PointerFormat&, bool& stop)) const
 {
     bool stop = false;
     for (uint32_t segIndex=0; segIndex < segments.size(); ++segIndex) {
@@ -185,15 +172,15 @@
                     bool chainEnd = false;
                     while ( !chainEnd ) {
                         chainEnd = (segStarts->page_start[overflowIndex] & DYLD_CHAINED_PTR_START_LAST);
-                        uint16_t  startOffset = (segStarts->page_start[overflowIndex] & ~DYLD_CHAINED_PTR_START_LAST);
-                        uint32_t* chainStart  = (uint32_t*)((uint8_t*)(segments[segIndex].content) + startOffset);
-                        callback(chainStart, segIndex, pf, stop);
+                        uint16_t       startOffset = (segStarts->page_start[overflowIndex] & ~DYLD_CHAINED_PTR_START_LAST);
+                        const uint8_t* chainStart  = (uint8_t*)(segments[segIndex].content) + pageIndex * segStarts->page_size + startOffset;
+                        callback(chainStart, segIndex, pageIndex, segStarts->page_size, pf, stop);
                         ++overflowIndex;
                     }
                 }
                 else {
                     const uint8_t* chainStart = ((uint8_t*)(segments[segIndex].content)) + pageIndex * segStarts->page_size + offsetInPage;
-                    callback(chainStart, segIndex, pf, stop);
+                    callback(chainStart, segIndex, pageIndex, segStarts->page_size, pf, stop);
                 }
                 if ( stop )
                     return;
@@ -203,12 +190,8 @@
 }
 
 
-Error ChainedFixups::valid(std::span<const MappedSegment> segments) const
-{
-#if BUILDING_MACHO_WRITER
-    if ( _buildError.hasError() )
-        return Error("%s", _buildError.message());
-#endif
+Error ChainedFixups::validLinkedit(uint64_t preferredLoadAddress, std::span<const MappedSegment> segments) const
+{
     // validate dyld_chained_fixups_header
     if ( _fixupsHeader->fixups_version != 0 )
         return Error("chained fixups, unknown header version (%d)", _fixupsHeader->fixups_version);
@@ -320,20 +303,39 @@
     }
     // validate import table size can fit
     uint32_t maxBindOrdinal = PointerFormat::make(pointer_format_for_all).maxBindOrdinal(false);
-    if ( _fixupsHeader->imports_count >= maxBindOrdinal )
+    if ( (_fixupsHeader->imports_count != 0) && (_fixupsHeader->imports_count >= maxBindOrdinal) )
         return Error("chained fixups, imports_count (%d) exceeds max of %d", _fixupsHeader->imports_count, maxBindOrdinal);
 
     // validate max_valid_pointer is larger than last segment
-    //if ( maxValidPointerSeen != 0 ) {
-    //    uint64_t lastSegmentLastVMAddr = segmentsInfo[leInfo.layout.linkeditSegIndex-1].vmAddr + segmentsInfo[leInfo.layout.linkeditSegIndex-1].vmSize;
-    //    if ( maxValidPointerSeen < lastSegmentLastVMAddr ) {
-    //        return Error("chained fixups, max_valid_pointer too small for image");
-    //    }
-    //}
+    if ( maxValidPointerSeen != 0 ) {
+        size_t lastDataSegmentIndex = segments.size() - (segments.back().segName == "__LINKEDIT" ? 2 : 1);
+        const MappedSegment& lastDataSegment = segments[lastDataSegmentIndex];
+        // note: runtime offset is relative to the load address but max valid pointer encodes an 'absolute' valid pointer
+        uint64_t lastDataSegmentLastVMAddr = preferredLoadAddress + lastDataSegment.runtimeOffset + lastDataSegment.runtimeSize;
+        if ( maxValidPointerSeen < lastDataSegmentLastVMAddr )
+            return Error("chained fixups, max_valid_pointer (0x%x) too small for image last vm address 0x%llx", maxValidPointerSeen, lastDataSegmentLastVMAddr);
+    }
     return Error::none();
 }
 
-
+Error ChainedFixups::validStartsSection(std::span<const MappedSegment> segments) const
+{
+    // validate dyld_chained_starts_offsets
+    if ( !PointerFormat::valid(_chainStartsHeader->pointer_format) ) {
+        return Error("chained fixups, unknown pointer_format (%d)", _chainStartsHeader->pointer_format);
+    }
+    return Error::none();
+}
+
+Error ChainedFixups::valid(uint64_t preferredLoadAddress, std::span<const MappedSegment> segments,
+                           bool startsInSection) const
+{
+    if ( startsInSection ) {
+        return validStartsSection(segments);
+    } else {
+        return validLinkedit(preferredLoadAddress, segments);
+    }
+}
 const char* ChainedFixups::importsFormatName(uint32_t format)
 {
     switch (format) {
@@ -353,563 +355,52 @@
 }
 
 void ChainedFixups::PointerFormat::forEachFixupLocationInChain(const void* chainStartLoc, uint64_t prefLoadAddr, const MappedSegment* seg,
-                                                               void (^callback)(const Fixup& info, bool& stop)) const
-{
-    bool stop = false;
-    const void* nextLoc = nullptr;
+                                                                std::span<const uint64_t> segOffsetTable, uint32_t pageIndex, uint32_t pageSize,
+                                                                void (^callback)(const Fixup& f, bool& stop)) const
+{
+    bool         stop    = false;
+    const void*  nextLoc = nullptr;
+    const void*  endPage = nullptr;
+    // note: seg is null for firmware and firmware does not require chains to be limited to one page
+    if ( seg != nullptr ) {
+        const void*  startPage = (void*)((uint8_t*)seg->content + pageIndex*pageSize);
+        endPage = (void*)((uint8_t*)startPage + pageSize);
+        if ( (chainStartLoc < startPage) || (chainStartLoc > endPage) )
+            return; // error: chain is not on page
+    }
     for ( const void* fixupLoc = chainStartLoc; (fixupLoc != nullptr) && !stop; fixupLoc = nextLoc) {
-        // get next before calling callback, because callback may update location (set runtime pointer)
+        // get next before calling callback, because callback may update location (change PointerFormat bits into runtime pointer)
         nextLoc = this->nextLocation(fixupLoc);
-        callback(this->parseChainEntry(fixupLoc, seg, prefLoadAddr), stop);
-    }
-}
-
-
-#if BUILDING_MACHO_WRITER
-
-
-template <typename T>
-static T align8(T value)
-{
-    return (value + 7) & (-8);
-}
-
-size_t ChainedFixups::linkeditSize(std::span<const Fixup::BindTarget> bindTargets,
-                                   std::span<const SegmentFixupsInfo> segments,
-                                   uint32_t pageSize)
-{
-    // scan binds to figure out which imports table format to use
-    uint16_t  imFormat;
-    size_t    stringPoolSize;
-    if ( Error err = importsFormat(bindTargets, imFormat, stringPoolSize) )
-        return 0;
-
-    // allocate space in _bytes for full dyld_chained_fixups data structure
-    size_t maxBytesNeeded = align8(sizeof(dyld_chained_fixups_header));
-    maxBytesNeeded += offsetof(dyld_chained_starts_in_image,seg_info_offset[segments.size()]);
-    for ( const SegmentFixupsInfo& segment : segments ) {
-        const MappedSegment& seg = segment.mappedSegment;
-        uint32_t extras = segment.numPageExtras;
-        std::span<const Fixup> segFixups = segment.fixups;
-        if ( seg.writable && (seg.runtimeSize != 0) && !segFixups.empty() ) {
-            uint64_t lastFixupSegmentOffset = (uint64_t)segFixups.back().location - (uint64_t)seg.content;
-            uint64_t lastFixupPage = (lastFixupSegmentOffset / pageSize) + 1;
-            size_t segInfoSize = align8(offsetof(dyld_chained_starts_in_segment, page_start[lastFixupPage + extras]));
-            maxBytesNeeded += segInfoSize;
-        }
-    }
-
-    maxBytesNeeded = align8(maxBytesNeeded);
-
-    size_t importTableSize = 0;
-    if ( imFormat ==  DYLD_CHAINED_IMPORT_ADDEND64 )
-        importTableSize = align8(sizeof(dyld_chained_import_addend64) * bindTargets.size());
-    else if ( imFormat ==  DYLD_CHAINED_IMPORT_ADDEND )
-        importTableSize = align8(sizeof(dyld_chained_import_addend) * bindTargets.size());
-    else
-        importTableSize = align8(sizeof(dyld_chained_import) * bindTargets.size());
-
-    maxBytesNeeded += importTableSize;
-    maxBytesNeeded += align8(stringPoolSize);
-
-    return maxBytesNeeded;
-}
-
-void ChainedFixups::calculateSegmentPageExtras(std::span<SegmentFixupsInfo> segments,
-                                               const PointerFormat& pointerFormat,
-                                               uint32_t pageSize)
-{
-    for ( SegmentFixupsInfo& segmentFixupInfo : segments ) {
-        const MappedSegment& segment = segmentFixupInfo.mappedSegment;
-        const std::span<const Fixup> fixupsInSegment = segmentFixupInfo.fixups;
-        uint32_t numExtras = 0;
-
-        // skip segments with no fixups
-        if ( !segment.writable || (segment.runtimeSize == 0) || fixupsInSegment.empty() )
-            continue;
-
-        int             curPageIndex    = -1;
-        const Fixup*    prevFixup       = nullptr;
-        bool            pageHasExtras   = false;
-        for ( const Fixup& fixup : fixupsInSegment ) {
-            uint64_t offset = (uint8_t*)fixup.location - (uint8_t*)segment.content;
-            int pageIndex = (int)(offset/pageSize);
-            if ( pageIndex != curPageIndex ) {
-                curPageIndex = pageIndex;
-                prevFixup = nullptr;
-                pageHasExtras = false;
-            }
-            if ( prevFixup != nullptr ) {
-                intptr_t delta = (uint8_t*)fixup.location - (uint8_t*)(prevFixup->location);
-                if ( delta > pointerFormat.maxNext() ) {
-                    // prev/next are too far apart for chain to span, instead terminate chain at prevFixup
-                    // then start new overflow chain
-                    if ( !pageHasExtras ) {
-                        // A page with extras needs a start and end of the chain too
-                        numExtras += 2;
-                        pageHasExtras = true;
-                    }
-                    ++numExtras;
-                }
-            }
-            prevFixup = &fixup;
-        }
-
-        segmentFixupInfo.numPageExtras = numExtras;
-    }
-}
-
-Error ChainedFixups::importsFormat(std::span<const Fixup::BindTarget> bindTargets, uint16_t& importsFormat, size_t& stringPoolSize)
-{
-    bool     hasLargeOrdinal = false;
-    bool     has32bitAddend  = false;
-    bool     has64bitAddend  = false;
-    stringPoolSize = 1;
-    for (const Fixup::BindTarget& bind : bindTargets) {
-        stringPoolSize += (bind.symbolName.size() + 1);
-        if ( bind.libOrdinal < -15 ) {
-            // TODO: currently only -1, -2, and -3 have meaning.  Should we error here for < -3 ?
-            return Error("special libOrdinal (%d) too small", bind.libOrdinal);
-        }
-        if ( bind.libOrdinal > 240 ) {
-            hasLargeOrdinal = true;
-            if ( bind.libOrdinal > 65520 ) {
-                return Error("libOrdinal (%d) too large", bind.libOrdinal);
-            }
-        }
-        if ( bind.addend != 0 ) {
-            int32_t addend32 = (int32_t)bind.addend;
-            if ( (int64_t)addend32 == bind.addend )
-                has32bitAddend = true;
-            else
-                has64bitAddend = true;
-        }
-    }
-    bool hasLargeStringOffsets = dyld_chained_import{.name_offset=(uint32_t)stringPoolSize}.name_offset != stringPoolSize;
-
-    if ( hasLargeStringOffsets || has64bitAddend || hasLargeOrdinal )
-        importsFormat = DYLD_CHAINED_IMPORT_ADDEND64;
-    else if ( has32bitAddend )
-        importsFormat = DYLD_CHAINED_IMPORT_ADDEND;
-    else
-        importsFormat = DYLD_CHAINED_IMPORT;
-
-    if ( stringPoolSize > 0xFFFFFFFF )
-        return Error("imports string pool > 4GB");
-
-    return Error::none();
-}
-
-#if BUILDING_UNIT_TESTS
-ChainedFixups::ChainedFixups(std::span<const Fixup::BindTarget> bindTargets,
-                             std::span<const Fixup> fixups,
-                             std::span<const MappedSegment> segments,
-                             uint64_t preferredLoadAddress,
-                             const PointerFormat& pointerFormat, uint32_t pageSize, bool setDataChains)
-{
-    std::vector<std::vector<Fixup>> fixupsInSegments;
-    fixupsInSegments.reserve(segments.size());
-
-    {
-        // unify and sort fixups to make chains
-        std::vector<Fixup> sortedFixups(fixups.begin(), fixups.end());
-        std::sort(sortedFixups.begin(), sortedFixups.end());
-
-        // verify there are no locations with multiple fixups
-        if ( sortedFixups.size() > 1 ) {
-            Fixup lastLoc = sortedFixups.back();
-            for (const Fixup& f : sortedFixups) {
-                if ( f.location == lastLoc.location ) {
-                    _buildError = Error("multiple fixups at same location in %.*s at offset=0x%lX",
-                                        (int)f.segment->segName.size(), f.segment->segName.data(), (uint8_t*)f.location - (uint8_t*)(f.segment->content));
-                    return;
-                }
-                lastLoc = f;
-            }
-        }
-
-        for ( const Fixup fixup : sortedFixups) {
-            uint64_t segmentIndex = fixup.segment - &segments.front();
-            fixupsInSegments[segmentIndex].push_back(fixup);
-        }
-    }
-
-    std::vector<SegmentFixupsInfo> segmentFixupInfos;
-    for ( uint32_t segIndex = 0; segIndex != segments.size(); ++segIndex ) {
-        segmentFixupInfos.push_back({ segments[segIndex], fixupsInSegments[segIndex], 0 });
-    }
-
-    calculateSegmentPageExtras(segmentFixupInfos, pointerFormat, pageSize);
-
-    buildFixups(bindTargets, segmentFixupInfos, preferredLoadAddress, pointerFormat, pageSize, setDataChains);
-}
-#endif
-
-
-ChainedFixups::ChainedFixups(std::span<const Fixup::BindTarget> bindTargets,
-                             std::span<const SegmentFixupsInfo> segments,
-                             uint64_t preferredLoadAddress,
-                             const PointerFormat& pointerFormat, uint32_t pageSize, bool setDataChains)
-{
-    buildFixups(bindTargets, segments, preferredLoadAddress, pointerFormat, pageSize, setDataChains);
-}
-
-template<typename T, typename U>
-void atomic_min(std::atomic<T>& location, U value, const T defaultValue = nullptr) {
-    // If we manage to swap with the default value, then no other thread had set the value, and we're done
-    T expected = defaultValue;
-    while ( !location.compare_exchange_weak(expected, value, std::memory_order::release, std::memory_order_relaxed) ) {
-        // Value change before the store, if new value is smaller (but not null) then there's no need to store
-        if ( expected != defaultValue && expected <= value )
-            break;
-    }
-}
-
-template<typename T, typename U>
-void atomic_max(std::atomic<T>& location, U value) {
-    // If we manage to swap with nullptr, then no other thread had set the value, and we're done
-    T expected = nullptr;
-    while ( !location.compare_exchange_weak(expected, value, std::memory_order::release, std::memory_order_relaxed) ) {
-        // Value change before the store, if new value is larger then there's no need to store
-        if ( expected >= value )
-            break;
-    }
-}
-
-
-void ChainedFixups::buildFixups(std::span<const Fixup::BindTarget> bindTargets,
-                                std::span<const SegmentFixupsInfo> segments,
-                                uint64_t preferredLoadAddress,
-                                const PointerFormat& pointerFormat, uint32_t pageSize, bool setDataChains)
-{
-    // scan binds to figure out which imports table format to use
-    uint16_t  imFormat;
-    size_t    stringPoolSize;
-    _buildError = importsFormat(bindTargets, imFormat, stringPoolSize);
-    if ( _buildError.hasError() )
-        return;
-
-
-    // build imports table
-    std::vector<char>                         stringPool;
-    size_t                                    importsTableSize = 0;
-    const void*                               importsTableStart = nullptr;
-    std::vector<dyld_chained_import>          imports ;
-    std::vector<dyld_chained_import_addend>   importsAddend;
-    std::vector<dyld_chained_import_addend64> importsAddend64;
-    stringPool.reserve(stringPoolSize);
-    stringPool.push_back('\0'); // so that zero is never a legal string offset
-    if ( imFormat ==  DYLD_CHAINED_IMPORT_ADDEND64 ) {
-        importsAddend64.reserve(bindTargets.size());
-        for (const Fixup::BindTarget& bind : bindTargets) {
-            importsAddend64.push_back({(uint16_t)bind.libOrdinal, bind.weakImport, 0, addSymbolString(bind.symbolName, stringPool), (uint64_t)bind.addend});
-        }
-        importsTableSize = sizeof(dyld_chained_import_addend64) * importsAddend64.size();
-        if ( !importsAddend64.empty() )
-            importsTableStart = &importsAddend64[0];
-    }
-    else if ( imFormat ==  DYLD_CHAINED_IMPORT_ADDEND ) {
-        importsAddend.reserve(bindTargets.size());
-        for (const Fixup::BindTarget& bind : bindTargets) {
-            importsAddend.push_back({(uint8_t)bind.libOrdinal, bind.weakImport, addSymbolString(bind.symbolName, stringPool), (int32_t)bind.addend});
-        }
-        importsTableSize = sizeof(dyld_chained_import_addend) * importsAddend.size();
-        if ( !importsAddend.empty() )
-            importsTableStart = &importsAddend[0];
-    }
-    else {
-        // can use most compact imports encoding
-        imports.reserve(bindTargets.size());
-        for (const Fixup::BindTarget& bind : bindTargets) {
-            imports.push_back({(uint8_t)bind.libOrdinal, bind.weakImport, addSymbolString(bind.symbolName, stringPool)});
-        }
-        importsTableSize = sizeof(dyld_chained_import) * imports.size();
-        if ( !imports.empty() )
-            importsTableStart = &imports[0];
-    }
-
-    // for 32-bit archs, compute maxRebaseAddress value
-    uint64_t maxRebaseAddress = 0;
-    if ( !pointerFormat.is64() ) {
-        for ( const SegmentFixupsInfo& segment : segments ) {
-            const MappedSegment& seg = segment.mappedSegment;
-            if ( seg.segName == "__LINKEDIT" ) {
-                uint64_t baseAddress = preferredLoadAddress;
-                if ( baseAddress == 0x4000 )
-                    baseAddress = 0; // 32-bit main executables have rebase targets that are zero based
-                maxRebaseAddress = (seg.runtimeOffset + 0x00100000-1) & -0x00100000; // align to 1MB
-            }
-        }
-    }
-
-    // allocate space in _bytes for full dyld_chained_fixups data structure
-    size_t maxBytesNeeded = linkeditSize(bindTargets, segments, pageSize);
-    _bytes.resize(maxBytesNeeded, 0); // ensure alignment padding is zeroed out
-
-    // build dyld_chained_fixups data structure
-    dyld_chained_fixups_header* header = (dyld_chained_fixups_header*)(&_bytes[0]);
-    header->fixups_version = 0; // version 0
-    header->starts_offset  = (uint32_t)align8(sizeof(dyld_chained_fixups_header)); // 8-byte align dyld_chained_starts_in_image
-    header->imports_offset = 0; // filled in later
-    header->symbols_offset = 0; // filled in later
-    header->imports_count  = (uint32_t)bindTargets.size();
-    header->imports_format = imFormat;
-    header->symbols_format = 0; // raw strings
-    dyld_chained_starts_in_image* startsInfo = (dyld_chained_starts_in_image*)(&_bytes[header->starts_offset]);
-    startsInfo->seg_count  = (uint32_t)segments.size();
-
-    // create dyld_chained_starts_in_segment for each segment
-    {
-        uint32_t segInfoOffset = (uint32_t)align8(offsetof(dyld_chained_starts_in_image,seg_info_offset[segments.size()]));
-        for ( uint32_t segIndex = 0; segIndex != segments.size(); ++segIndex ) {
-            const MappedSegment& segment = segments[segIndex].mappedSegment;
-            const std::span<const Fixup> fixupsInSegment = segments[segIndex].fixups;
-
-            // don't make dyld_chained_starts_in_segment for segments with no fixups
-            if ( !segment.writable || (segment.runtimeSize == 0) || fixupsInSegment.empty() ) {
-                startsInfo->seg_info_offset[segIndex] = 0;
-                continue;
-            }
-
-            startsInfo->seg_info_offset[segIndex] = segInfoOffset;
-            dyld_chained_starts_in_segment* segInfo = (dyld_chained_starts_in_segment*)(&_bytes[header->starts_offset+segInfoOffset]);
-            segInfo->size               = 0; // filled in later
-            segInfo->page_size          = pageSize;
-            segInfo->pointer_format     = pointerFormat.value();
-            segInfo->segment_offset     = segment.runtimeOffset;
-            segInfo->max_valid_pointer  = (uint32_t)maxRebaseAddress;
-            segInfo->page_count         = 0; // fill in later, may be trailing pages with no fixups
-            segInfo->page_start[0]      = DYLD_CHAINED_PTR_START_NONE;
-
-            uint64_t lastFixupSegmentOffset = (uint64_t)fixupsInSegment.back().location - (uint64_t)segment.content;
-            uint64_t lastFixupPage = (lastFixupSegmentOffset / pageSize) + 1;
-
-            segInfo->page_count = lastFixupPage;
-            segInfo->size = (uint32_t)offsetof(dyld_chained_starts_in_segment, page_start[segInfo->page_count]);
-
-            // adjust segment size info to include overflow entries
-            segInfo->size += segments[segIndex].numPageExtras * sizeof(uint16_t);
-
-            segInfoOffset += segInfo->size;
-            segInfoOffset = align8(segInfoOffset);
-        }
-
-        header->imports_offset = align8(header->starts_offset + segInfoOffset);
-        header->symbols_offset = (uint32_t)align8(header->imports_offset + importsTableSize);
-    }
-
-    // For segments, we're going to try do each page in parallel when possible
-    // First this means computing the range of fixups for every page.  We can do that in parallel
-    // Then walk those ranges in parallel.
-    // For segments with pageExtras, its too hard to do pages in parallel so we'll go serially
-    for ( uint32_t segIndex = 0; segIndex != segments.size(); ++segIndex ) {
-        uint32_t segInfoOffset = startsInfo->seg_info_offset[segIndex];
-        if ( segInfoOffset == 0 )
-            continue;
-
-        const dyld_chained_starts_in_segment* segInfo = (dyld_chained_starts_in_segment*)(&_bytes[header->starts_offset + segInfoOffset]);
-
-        const MappedSegment&         segment   = segments[segIndex].mappedSegment;
-        const std::span<const Fixup> segFixups = segments[segIndex].fixups;
-        uint32_t                     segExtras = segments[segIndex].numPageExtras;
-
-        std::span<uint16_t> pageStarts = { (uint16_t*)&segInfo->page_start[0], segInfo->page_count };
-        const uint32_t      minNext    = pointerFormat.minNext();
-
-        if ( segExtras != 0 ) {
-            // Segment has extras.  Take the slow path
-            std::span<uint16_t> extras = { (uint16_t*)&segInfo->page_start[segInfo->page_count], segExtras };
-
-            int       curPageIndex      = -1;
-            int       curExtrasIndex    = -1;
-            const Fixup* prevFixup      = nullptr;
-            for ( const Fixup& fixup : segFixups ) {
-                uint64_t segOffset = (uint8_t*)fixup.location - (uint8_t*)segment.content;
-                int pageIndex = (int)(segOffset/pageSize);
-                if ( pageIndex != curPageIndex ) {
-                    // End the previous chain if we have one
-                    if ( prevFixup != nullptr ) {
-                        if ( (pageStarts[curPageIndex] & DYLD_CHAINED_PTR_START_MULTI) != 0 ) {
-                            // Mark the end of this extras chain
-                            extras[curExtrasIndex] |= DYLD_CHAINED_PTR_START_LAST;
-                        }
-
-                        if ( setDataChains ) {
-                            // set end of chain for this page
-                            pointerFormat.writeChainEntry(*prevFixup, nullptr, preferredLoadAddress);
-                        }
-                    }
-                    while (curPageIndex < pageIndex) {
-                        ++curPageIndex;
-                        pageStarts[curPageIndex] = DYLD_CHAINED_PTR_START_NONE;
-                    }
-                    pageStarts[curPageIndex] = (segOffset - (curPageIndex*pageSize));
-                    prevFixup = nullptr;
-                }
-
-                // Found a previous fixup on this page, so make a chain from it to this fixup
-                if ( prevFixup != nullptr ) {
-                    uint8_t* chain = (uint8_t*)fixup.location;
-                    intptr_t delta = chain - (uint8_t*)(prevFixup->location);
-                    if ( delta <= pointerFormat.maxNext() ) {
-                        if ( (delta % minNext) != 0 ) {
-                            _buildError = Error("pointer not %d-byte aligned at %.*s+0x%llX, fix alignment or disable chained fixups",
-                                                minNext, (int)segment.segName.size(), segment.segName.data(), segOffset);
-                            break;
-                        }
-                        else if ( setDataChains ) {
-                            pointerFormat.writeChainEntry(*prevFixup, chain, preferredLoadAddress);
-                        }
-                    }
-                    else {
-                        // prev/next are too far apart for chain to span, instead terminate chain at prevFixup
-                        if ( setDataChains )
-                            pointerFormat.writeChainEntry(*prevFixup, nullptr, preferredLoadAddress);
-                        // then start new overflow chain
-                        if ( (pageStarts[curPageIndex] & DYLD_CHAINED_PTR_START_MULTI) == 0 ) {
-                            ++curExtrasIndex;
-                            // move first start to overflow array
-                            extras[curExtrasIndex] = pageStarts[curPageIndex];
-                            // change first page start to point into overflow array
-                            pageStarts[curPageIndex] = DYLD_CHAINED_PTR_START_MULTI | (segInfo->page_count + curExtrasIndex);
-                        }
-                        uint16_t pageOffset = segOffset % pageSize;
-                        ++curExtrasIndex;
-                        extras[curExtrasIndex] = pageOffset;
-                    }
-                }
-                prevFixup = &fixup;
-            }
-            // if this page required multiple starts, mark last one
-            if ( (pageStarts[curPageIndex] & DYLD_CHAINED_PTR_START_MULTI) != 0 ) {
-                extras[curExtrasIndex] |= DYLD_CHAINED_PTR_START_LAST;
-            }
-            if ( setDataChains && (prevFixup != nullptr) ) {
-                // set end of chain
-                pointerFormat.writeChainEntry(*prevFixup, nullptr, preferredLoadAddress);
-            }
-        } else {
-            // No extras, so use parallelism
-            typedef std::pair<std::atomic<const Fixup*>, std::atomic<const Fixup*>> FixupRange;
-            // use up to 128kb on stack, main thread has 8mb large stack by default
-            STACK_ALLOC_OVERFLOW_SAFE_ARRAY(FixupRange, fixupRangesStorage, 0x2000);
-            fixupRangesStorage.resize(segInfo->page_count);
-            // array ::resize doesn't initialize new elements, so do it here
-            bzero(&fixupRangesStorage[0], sizeof(FixupRange) * segInfo->page_count);
-            std::span<FixupRange> fixupRanges = { &fixupRangesStorage[0], segInfo->page_count };
-
-            // Walk all fixups and get the range for each page
-            mapReduce(segFixups, ^(size_t, int&, std::span<const Fixup> fixups) {
-
-                int curPageIndex = -1;
-                const Fixup* endFixup = nullptr;
-
-                // The very first fixup we process might be the first on its page, or might be
-                // somewhere in the middle.  So it needs as atomic min to make sure its safe with other threads
-                {
-                    const Fixup& fixup = fixups[0];
-                    uint64_t segOffset = (uint8_t*)fixup.location - (uint8_t*)segment.content;
-                    int pageIndex = (int)(segOffset/pageSize);
-                    atomic_min(fixupRanges[pageIndex].first, &fixup);
-
-                    curPageIndex = pageIndex;
-                    endFixup = &fixup;
-                }
-
-                fixups = fixups.subspan(1);
-
-                for ( const Fixup& fixup : fixups ) {
-                    uint64_t segOffset = (uint8_t*)fixup.location - (uint8_t*)segment.content;
-                    int pageIndex = (int)(segOffset/pageSize);
-
-                    if ( pageIndex != curPageIndex ) {
-                        // Crossing in to a new page.  As fixups are sorted, we know for sure the
-                        // last fixup we processed must be on the end of its page
-                        fixupRanges[curPageIndex].second.store(endFixup, std::memory_order_relaxed);
-
-                        // Also the new fixup we have must be the first on its page
-                        fixupRanges[pageIndex].first.store(&fixup, std::memory_order_relaxed);
-
-                        curPageIndex = pageIndex;
-                    }
-
-                    endFixup = &fixup;
-                }
-
-                // The last fixup we have is somewhere in a page, but we don't know if its the end
-                // of that page or not.  Try set it as the max
-                atomic_max(fixupRanges[curPageIndex].second, endFixup);
-            });
-
-            // If there's an unaligned fixup, this will store if offset in the segment
-            std::atomic<uint64_t> unalignedFixupOffset = ~0ULL;
-            std::atomic<uint64_t>& unalignedFixupOffsetRef = unalignedFixupOffset;
-
-            // Now process all pages in parallel
-            mapReduce(fixupRanges, std::max(fixupRanges.size() / 64, 32ul), ^(size_t, int&, std::span<FixupRange> ranges) {
-                for ( const FixupRange& fixupRange : ranges ) {
-                    size_t pageIndex = &fixupRange - fixupRanges.data();
-                    const Fixup* start = fixupRange.first.load(std::memory_order_relaxed);
-                    const Fixup* end = fixupRange.second.load(std::memory_order_relaxed);
-                    if ( start == nullptr ) {
-                        assert(end == nullptr);
-                        pageStarts[pageIndex] = DYLD_CHAINED_PTR_START_NONE;
-                        continue;
-                    }
-
-                    assert(end != nullptr);
-                    assert(start <= end);
-                    uint64_t startSegOffset = (uint8_t*)start->location - (uint8_t*)segment.content;
-                    pageStarts[pageIndex] = (startSegOffset - (pageIndex * pageSize));
-
-                    if ( setDataChains ) {
-                        const Fixup* fixup = start;
-                        while ( fixup != end ) {
-                            const Fixup* prev = fixup;
-                            ++fixup;
-                            uint8_t* chain = (uint8_t*)fixup->location;
-                            intptr_t delta = chain - (uint8_t*)(prev->location);
-                            if ( (delta % minNext) != 0 ) {
-                                uint64_t segOffset = (uint8_t*)fixup->location - (uint8_t*)segment.content;
-                                atomic_min(unalignedFixupOffsetRef, segOffset, ~0ULL);
-                                break;
-                            }
-                            pointerFormat.writeChainEntry(*prev, chain, preferredLoadAddress);
-                        }
-
-                        // set end of chain
-                        pointerFormat.writeChainEntry(*end, nullptr, preferredLoadAddress);
-                    }
-                }
-            });
-
-            uint64_t segOffset = unalignedFixupOffset.load(std::memory_order_relaxed);
-            if ( (segOffset != ~0ULL) && !_buildError.hasError() ) {
-                _buildError = Error("pointer not %d-byte aligned at %.*s+0x%llX, fix alignment or disable chained fixups",
-                                    minNext, (int)segment.segName.size(), segment.segName.data(), segOffset);
-            }
-        }
-    }
-
-    // append import table and string pool
-    memcpy(&_bytes[header->imports_offset], importsTableStart, importsTableSize);
-    memcpy(&_bytes[header->symbols_offset], &stringPool[0], stringPool.size());
-
-    _fixupsHeader = (dyld_chained_fixups_header*)(&_bytes[0]);
-    _fixupsSize   = _bytes.size();
-}
-
-
-uint32_t ChainedFixups::addSymbolString(CString symbolName, std::vector<char>& pool)
-{
-    uint32_t symbolOffset = (uint32_t)pool.size();
-    // end+1 to copy also the null-terminator
-    pool.insert(pool.end(), symbolName.begin(), symbolName.end()+1);
-    return symbolOffset;
-}
-
-
-#endif
+        callback(this->parseChainEntry(fixupLoc, seg, prefLoadAddr, segOffsetTable), stop);
+        if ( (nextLoc != nullptr) && (endPage != nullptr) ) {
+            if ( nextLoc > endPage )
+                break; // error: chain went off end of page
+        }
+    }
+}
+
+
+// copy of dyld_chained_ptr_arm64e_rebase that allows 4-byte strides
+struct __attribute__((packed)) unaligned_dyld_chained_ptr_arm64e_rebase
+{
+    uint64_t    target   : 43,
+                high8    :  8,
+                next     : 11,    // 4 or 8-byte stide
+                bind     :  1,    // == 0
+                auth     :  1;    // == 0
+};
+
+// copy of dyld_chained_ptr_arm64e_auth_rebase that allows 4-byte strides
+struct __attribute__((packed)) unaligned_dyld_chained_ptr_arm64e_auth_rebase
+{
+    uint64_t    target    : 32,   // runtimeOffset
+                diversity : 16,
+                addrDiv   :  1,
+                key       :  2,
+                next      : 11,    // 4 or 8-byte stide
+                bind      :  1,    // == 0
+                auth      :  1;    // == 1
+};
 
 
 //
@@ -929,14 +420,14 @@
     int32_t          bindMinEmbeddableAddend(bool authenticated) const override  { return (authenticated ? 0 : -0x3FFFF); }
     
     const void*      nextLocation(const void* loc) const override {
-        const dyld_chained_ptr_arm64e_rebase* ptr = (dyld_chained_ptr_arm64e_rebase*)loc;
+        const unaligned_dyld_chained_ptr_arm64e_rebase* ptr = (unaligned_dyld_chained_ptr_arm64e_rebase*)loc;
         if ( ptr->next == 0 )
             return nullptr;
         return (void*)((uint8_t*)loc + ptr->next * stride());
     }
     
-    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress=0) const override {
-       if ( ((dyld_chained_ptr_arm64e_rebase*)loc)->bind ) {
+    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress, std::span<const uint64_t> segOffsetTable) const override {
+        if ( ((unaligned_dyld_chained_ptr_arm64e_rebase*)loc)->bind ) {
             if ( bindBitCount() == 24 ) {
                 const dyld_chained_ptr_arm64e_auth_bind24* authBind24Ptr = (dyld_chained_ptr_arm64e_auth_bind24*)loc;
                 const dyld_chained_ptr_arm64e_bind24*      bind24Ptr     = (dyld_chained_ptr_arm64e_bind24*)loc;
@@ -955,8 +446,8 @@
             }
         }
         else {
-            const dyld_chained_ptr_arm64e_auth_rebase* authRebasePtr = (dyld_chained_ptr_arm64e_auth_rebase*)loc;
-            const dyld_chained_ptr_arm64e_rebase*      rebasePtr     = (dyld_chained_ptr_arm64e_rebase*)loc;
+            const unaligned_dyld_chained_ptr_arm64e_auth_rebase* authRebasePtr = (unaligned_dyld_chained_ptr_arm64e_auth_rebase*)loc;
+            const dyld_chained_ptr_arm64e_rebase*                rebasePtr     = (dyld_chained_ptr_arm64e_rebase*)loc;
             if ( authRebasePtr->auth )
                return Fixup(loc, seg, authRebasePtr->target, authRebasePtr->key, authRebasePtr->addrDiv, authRebasePtr->diversity);
             else if ( unauthRebaseIsVmAddr() )
@@ -965,8 +456,26 @@
                 return Fixup(loc, seg, ((uint64_t)(rebasePtr->high8) << 56) | rebasePtr->target);
         }
     }
-#if BUILDING_MACHO_WRITER
-    void             writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress) const override {
+
+    static int64_t   signExtendedAddend(dyld_chained_ptr_arm64e_bind24* bind)
+    {
+        uint64_t addend19 = bind->addend;
+        if ( addend19 & 0x40000 )
+            return addend19 | 0xFFFFFFFFFFFC0000ULL;
+        else
+            return addend19;
+    }
+
+    static int64_t   signExtendedAddend(dyld_chained_ptr_arm64e_bind* bind)
+    {
+        uint64_t addend27     = bind->addend;
+        uint64_t top8Bits     = addend27 & 0x00007F80000ULL;
+        uint64_t bottom19Bits = addend27 & 0x0000007FFFFULL;
+        uint64_t newValue     = (top8Bits << 13) | (((uint64_t)(bottom19Bits << 37) >> 37) & 0x00FFFFFFFFFFFFFF);
+        return newValue;
+    }
+
+    Error             writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*>) const override {
         intptr_t delta = (nextLoc == nullptr) ? 0 : ((uint8_t*)nextLoc - (uint8_t*)fixup.location);
         if ( fixup.isBind ) {
             if ( bindBitCount() == 24 ) {
@@ -980,9 +489,13 @@
                     authBind24Ptr->diversity = fixup.auth.diversity;
                     authBind24Ptr->zero      = 0;
                     authBind24Ptr->ordinal   = fixup.bind.bindOrdinal;
-                    assert(authBind24Ptr->next*stride() == delta);
-                    assert(authBind24Ptr->ordinal == fixup.bind.bindOrdinal);
-                    assert(fixup.bind.embeddedAddend == 0);
+                    // validate things fit into bit fields
+                    if ( authBind24Ptr->next*stride() != delta )
+                        return badChainDistance(fixup, delta);
+                    if ( authBind24Ptr->ordinal != fixup.bind.bindOrdinal )
+                        return badBindOrdinal(fixup);
+                    if ( fixup.bind.embeddedAddend != 0 )
+                        return badAddend(fixup, fixup.bind.embeddedAddend);
                 }
                 else {
                     dyld_chained_ptr_arm64e_bind24* bind24Ptr = (dyld_chained_ptr_arm64e_bind24*)fixup.location;
@@ -992,9 +505,12 @@
                     bind24Ptr->addend   = fixup.bind.embeddedAddend;
                     bind24Ptr->zero     = 0;
                     bind24Ptr->ordinal  = fixup.bind.bindOrdinal;
-                    assert(bind24Ptr->addend == fixup.bind.embeddedAddend);
-                    assert(bind24Ptr->next*stride() == delta);
-                    assert(bind24Ptr->ordinal == fixup.bind.bindOrdinal);
+                    if ( signExtendedAddend(bind24Ptr) != fixup.bind.embeddedAddend )
+                        return badAddend(fixup, fixup.bind.embeddedAddend);
+                    if ( bind24Ptr->next*stride() != delta )
+                        return badChainDistance(fixup, delta);
+                    if ( bind24Ptr->ordinal != fixup.bind.bindOrdinal )
+                        return badBindOrdinal(fixup);
                 }
             }
             else {
@@ -1008,9 +524,12 @@
                     authBindPtr->diversity = fixup.auth.diversity;
                     authBindPtr->zero      = 0;
                     authBindPtr->ordinal   = fixup.bind.bindOrdinal;
-                    assert(authBindPtr->next*stride() == delta);
-                    assert(authBindPtr->ordinal == fixup.bind.bindOrdinal);
-                    assert(fixup.bind.embeddedAddend == 0);
+                    if ( authBindPtr->next*stride() != delta )
+                        return badChainDistance(fixup, delta);
+                    if ( authBindPtr->ordinal != fixup.bind.bindOrdinal )
+                        return badBindOrdinal(fixup);
+                    if ( fixup.bind.embeddedAddend != 0 )
+                        return badAddend(fixup, fixup.bind.embeddedAddend);
                 }
                 else {
                     dyld_chained_ptr_arm64e_bind* bindPtr = (dyld_chained_ptr_arm64e_bind*)fixup.location;
@@ -1020,15 +539,18 @@
                     bindPtr->addend   = fixup.bind.embeddedAddend;
                     bindPtr->zero     = 0;
                     bindPtr->ordinal  = fixup.bind.bindOrdinal;
-                    assert(bindPtr->addend == fixup.bind.embeddedAddend);
-                    assert(bindPtr->next*stride() == delta);
-                    assert(bindPtr->ordinal == fixup.bind.bindOrdinal);
+                    if ( signExtendedAddend(bindPtr) != fixup.bind.embeddedAddend )
+                        return badAddend(fixup, fixup.bind.embeddedAddend);
+                    if ( bindPtr->next*stride() != delta )
+                        return badChainDistance(fixup, delta);
+                    if ( bindPtr->ordinal != fixup.bind.bindOrdinal )
+                        return badBindOrdinal(fixup);
                 }
             }
         }
         else {
             if ( fixup.authenticated ) {
-                dyld_chained_ptr_arm64e_auth_rebase* authRebasePtr = (dyld_chained_ptr_arm64e_auth_rebase*)fixup.location;
+                unaligned_dyld_chained_ptr_arm64e_auth_rebase* authRebasePtr = (unaligned_dyld_chained_ptr_arm64e_auth_rebase*)fixup.location;
                 authRebasePtr->auth      = true;
                 authRebasePtr->bind      = false;
                 authRebasePtr->next      = delta/stride();
@@ -1036,11 +558,13 @@
                 authRebasePtr->addrDiv   = fixup.auth.usesAddrDiversity;
                 authRebasePtr->diversity = fixup.auth.diversity;
                 authRebasePtr->target    = fixup.rebase.targetVmOffset;
-                assert(authRebasePtr->next*stride() == delta);
-                assert(authRebasePtr->target == fixup.rebase.targetVmOffset);
-            }
+                if ( authRebasePtr->next*stride() != delta )
+                    return badChainDistance(fixup, delta);
+                if ( authRebasePtr->target != fixup.rebase.targetVmOffset )
+                    return badVmOffset(fixup);
+           }
             else {
-                dyld_chained_ptr_arm64e_rebase* rebasePtr = (dyld_chained_ptr_arm64e_rebase*)fixup.location;
+                unaligned_dyld_chained_ptr_arm64e_rebase* rebasePtr = (unaligned_dyld_chained_ptr_arm64e_rebase*)fixup.location;
                 uint8_t   high8 = (fixup.rebase.targetVmOffset >> 56);
                 uint64_t  low56 = (fixup.rebase.targetVmOffset & 0x00FFFFFFFFFFFFFFULL);
                 rebasePtr->auth     = false;
@@ -1048,12 +572,18 @@
                 rebasePtr->next     = delta/stride();
                 rebasePtr->high8    = high8;
                 rebasePtr->target   = low56 + (this->unauthRebaseIsVmAddr() ? preferedLoadAddress : 0);
-                assert(rebasePtr->next*stride() == delta);
-                assert(rebasePtr->target == (low56 + (this->unauthRebaseIsVmAddr() ? preferedLoadAddress : 0)));
+                if ( rebasePtr->next*stride() != delta )
+                    return badChainDistance(fixup, delta);
+                if ( rebasePtr->target != (low56 + (this->unauthRebaseIsVmAddr() ? preferedLoadAddress : 0)) ) {
+                    if ( this->unauthRebaseIsVmAddr() )
+                        return badVmAddr(fixup, preferedLoadAddress);
+                    else
+                        return badVmOffset(fixup);
+                }
             }
         }
-    }
-#endif
+        return Error::none();
+    }
 
 protected:
     virtual uint32_t bindBitCount() const = 0;
@@ -1070,6 +600,7 @@
     uint16_t        value() const override                 { return DYLD_CHAINED_PTR_ARM64E; }
     const char*     name() const override                  { return "DYLD_CHAINED_PTR_ARM64E"; }
     const char*     description() const override           { return "authenticated arm64e, 8-byte stride, target vmadddr"; }
+    uint32_t        ptrAlignmentSize() const override      { return 8; } // arm64e userspace requires 8-byte ptr alignment
 protected:
     uint32_t        bindBitCount() const override          { return 16; }
     uint32_t        stride() const override                { return 8; }
@@ -1080,7 +611,7 @@
 //
 // MARK: --- PointerFormat_DYLD_CHAINED_PTR_ARM64E_KERNEL ---
 //
-class VIS_HIDDEN PointerFormat_DYLD_CHAINED_PTR_ARM64E_KERNEL : public PointerFormat_Generic_arm64e
+class VIS_HIDDEN  __attribute__((__packed__)) PointerFormat_DYLD_CHAINED_PTR_ARM64E_KERNEL : public PointerFormat_Generic_arm64e
 {
 public:
     uint16_t        value() const override                 { return DYLD_CHAINED_PTR_ARM64E_KERNEL; }
@@ -1103,6 +634,7 @@
     uint16_t        value() const override                 { return DYLD_CHAINED_PTR_ARM64E_USERLAND; }
     const char*     name() const override                  { return "DYLD_CHAINED_PTR_ARM64E_USERLAND"; }
     const char*     description() const override           { return "authenticated arm64e, 8-byte stride, target vmoffset"; }
+    uint32_t        ptrAlignmentSize() const override      { return 8; } // arm64e userspace requires 8-byte ptr alignment
 protected:
     uint32_t        bindBitCount() const override          { return 16; }
     uint32_t        stride() const override                { return 8; }
@@ -1119,6 +651,7 @@
     uint16_t        value() const override                 { return DYLD_CHAINED_PTR_ARM64E_USERLAND24; }
     const char*     name() const override                  { return "DYLD_CHAINED_PTR_ARM64E_USERLAND24"; }
     const char*     description() const override           { return "authenticated arm64e, 8-byte stride, target vmoffset"; }
+    uint32_t        ptrAlignmentSize() const override      { return 8; } // arm64e userspace requires 8-byte ptr alignment
 protected:
     uint32_t        bindBitCount() const override          { return 24; }
     uint32_t        stride() const override                { return 8; }
@@ -1134,13 +667,188 @@
 public:
     uint16_t         value() const override                { return DYLD_CHAINED_PTR_ARM64E_FIRMWARE; }
     const char*      name() const override                 { return "DYLD_CHAINED_PTR_ARM64E_FIRMWARE"; }
-    const char*      description() const override          { return "authenticated arm64e, 4-byte stride, target vmoffset"; }
+    const char*      description() const override          { return "authenticated arm64e, 4-byte stride, target vmaddr"; }
     bool             is64() const override                 { return true; }
 protected:
     uint32_t        bindBitCount() const override          { return 16; }
     uint32_t        stride() const override                { return 4; }
     bool            unauthRebaseIsVmAddr() const override  { return true; }
 };
+
+
+//
+// MARK: --- PointerFormat_DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE ---
+//
+class VIS_HIDDEN PointerFormat_DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE : public PointerFormat_Generic_arm64e
+{
+public:
+    bool            supportsBinds() const override         { return false; }
+    uint16_t        value() const override                 { return DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE; }
+    const char*     name() const override                  { return "PointerFormat_DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE"; }
+    const char*     description() const override           { return "arm64e shared cache, 8-byte stride, target vmoffset"; }
+    uint32_t        ptrAlignmentSize() const override      { return 8; } // arm64e userspace requires 8-byte ptr alignment
+    uint64_t        maxRebaseTargetOffset(bool auth) const override    { return 0x3FFFFFFFFULL; }
+    Error           writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*>) const override;
+protected:
+    uint32_t        bindBitCount() const override          { return 0; }
+    uint32_t        stride() const override                { return 8; }
+    bool            unauthRebaseIsVmAddr() const override  { return false; }
+
+    const void*     nextLocation(const void* loc) const override {
+        const dyld_chained_ptr_arm64e_shared_cache_rebase* ptr = (dyld_chained_ptr_arm64e_shared_cache_rebase*)loc;
+        if ( ptr->next == 0 )
+            return nullptr;
+        return (void*)((uint8_t*)loc + ptr->next * stride());
+    }
+
+    Fixup           parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress, std::span<const uint64_t> segOffsetTable) const override {
+        const dyld_chained_ptr_arm64e_shared_cache_auth_rebase*     authRebasePtr = (dyld_chained_ptr_arm64e_shared_cache_auth_rebase*)loc;
+        const dyld_chained_ptr_arm64e_shared_cache_rebase*          rebasePtr     = (dyld_chained_ptr_arm64e_shared_cache_rebase*)loc;
+        if ( authRebasePtr->auth ) {
+            uint8_t key = (authRebasePtr->keyIsData ? ptrauth_key_asda : ptrauth_key_asia);
+            return Fixup(loc, seg, authRebasePtr->runtimeOffset, key, authRebasePtr->addrDiv, authRebasePtr->diversity);
+        }
+        else {
+            return Fixup(loc, seg, ((uint64_t)(rebasePtr->high8) << 56) | rebasePtr->runtimeOffset);
+        }
+    }
+
+};
+
+Error PointerFormat_DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE::writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*>) const
+{
+    if ( fixup.isBind )
+        return Error ("shared cache fixup formate does not support binds");
+    intptr_t delta = (nextLoc == nullptr) ? 0 : ((uint8_t*)nextLoc - (uint8_t*)fixup.location);
+    if ( fixup.authenticated ) {
+        dyld_chained_ptr_arm64e_shared_cache_auth_rebase* authRebasePtr = (dyld_chained_ptr_arm64e_shared_cache_auth_rebase*)fixup.location;
+        authRebasePtr->auth          = 1;
+        authRebasePtr->next          = (uint32_t)(delta/8);
+        authRebasePtr->keyIsData     = (fixup.auth.key == ptrauth_key_asia ? 0 : 1);
+        authRebasePtr->addrDiv       = fixup.auth.usesAddrDiversity;
+        authRebasePtr->diversity     = fixup.auth.diversity;
+        authRebasePtr->runtimeOffset = (fixup.rebase.targetVmOffset & 0x3FFFFFFFFULL);
+        if ( authRebasePtr->next*8 != delta )
+            return badChainDistance(fixup, delta);
+        if ( authRebasePtr->runtimeOffset != fixup.rebase.targetVmOffset )
+            return badVmOffset(fixup);
+    }
+    else {
+        dyld_chained_ptr_arm64e_shared_cache_rebase* rebasePtr = (dyld_chained_ptr_arm64e_shared_cache_rebase*)fixup.location;
+        rebasePtr->auth          = 0;
+        rebasePtr->next          = (uint32_t)(delta/8);
+        rebasePtr->unused        = 0;
+        rebasePtr->high8         = ((fixup.rebase.targetVmOffset >> 56) & 0xFF);
+        rebasePtr->runtimeOffset = (fixup.rebase.targetVmOffset & 0x3FFFFFFFFULL);
+        if ( rebasePtr->next*8 != delta )
+            return badChainDistance(fixup, delta);
+        uint64_t targetFromEncoding = (rebasePtr->runtimeOffset | ((uint64_t)rebasePtr->high8 << 56));
+        if ( targetFromEncoding != fixup.rebase.targetVmOffset )
+            return badVmOffset(fixup);
+    }
+    return Error::none();
+}
+
+
+//
+// MARK: --- PointerFormat_DYLD_CHAINED_PTR_ARM64E_SEGMENTED ---
+//
+class VIS_HIDDEN PointerFormat_DYLD_CHAINED_PTR_ARM64E_SEGMENTED : public PointerFormat_Generic_arm64e
+{
+public:
+    bool             supportsBinds() const override        { return false; }
+    uint16_t         value() const override                { return DYLD_CHAINED_PTR_ARM64E_SEGMENTED; }
+    const char*      name() const override                 { return "DYLD_CHAINED_PTR_ARM64E_SEGMENTED"; }
+    const char*      description() const override          { return "authenticated arm64e, 8-byte stride, target segIndex/offset8"; }
+    bool             is64() const override                 { return true; }
+    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress, std::span<const uint64_t> segOffsetTable) const override;
+    Error            writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*>) const override;
+    const void*      nextLocation(const void* loc) const override;
+    uint64_t         maxRebaseTargetOffset(bool authenticated) const override    { return 0x0FFFFFFF;  }
+protected:
+    uint32_t        bindBitCount() const override          { return 0; }
+    uint32_t        stride() const override                { return 4; }
+    bool            unauthRebaseIsVmAddr() const override  { return true; }
+};
+
+const void* PointerFormat_DYLD_CHAINED_PTR_ARM64E_SEGMENTED::nextLocation(const void* loc) const
+{
+    const dyld_chained_ptr_arm64e_segmented_rebase* ptr = (dyld_chained_ptr_arm64e_segmented_rebase*)loc;
+    if ( ptr->next == 0 )
+        return nullptr;
+    return (void*)((uint8_t*)loc + ptr->next * stride());
+}
+
+Fixup PointerFormat_DYLD_CHAINED_PTR_ARM64E_SEGMENTED::parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress, std::span<const uint64_t> segOffsetTable) const
+{
+    const dyld_chained_ptr_arm64e_auth_segmented_rebase* authSegRebasePtr = (dyld_chained_ptr_arm64e_auth_segmented_rebase*)loc;
+    const dyld_chained_ptr_arm64e_segmented_rebase*      segRebasePtr     = (dyld_chained_ptr_arm64e_segmented_rebase*)loc;
+    if ( authSegRebasePtr->auth ) {
+        uint64_t targetVMOffset = segOffsetTable[authSegRebasePtr->targetSegIndex] + authSegRebasePtr->targetSegOffset;
+        return Fixup(loc, seg, targetVMOffset, authSegRebasePtr->key, authSegRebasePtr->addrDiv, authSegRebasePtr->diversity);
+    }
+    else {
+        uint64_t targetVMOffset = segOffsetTable[segRebasePtr->targetSegIndex] + segRebasePtr->targetSegOffset;
+        return Fixup(loc, seg, targetVMOffset);
+    }
+}
+
+static bool findSegIndexAndOffset(std::span<const MappedSegment*> segments, uint64_t vmOffset, uint8_t& segIndex, uint64_t& segOffset)
+{
+    int index=0;
+    for (const MappedSegment* seg : segments) {
+        if ( (seg->runtimeOffset <= vmOffset) && (vmOffset <= (seg->runtimeOffset+seg->runtimeSize)) ) {
+            segIndex  = index;
+            segOffset = vmOffset - seg->runtimeOffset;
+            return true;
+        }
+        ++index;
+    }
+    return false;
+}
+
+
+Error PointerFormat_DYLD_CHAINED_PTR_ARM64E_SEGMENTED::writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*> segments) const
+{
+    intptr_t delta = (nextLoc == nullptr) ? 0 : ((uint8_t*)nextLoc - (uint8_t*)fixup.location);
+    if ( fixup.isBind )
+        return Error("firmware format does not support binds");
+    uint8_t  segIndex;
+    uint64_t segOffset;
+    bool found = findSegIndexAndOffset(segments, fixup.rebase.targetVmOffset, segIndex, segOffset);
+    if ( !found )
+        return Error("target vm address not in any segment");
+
+    if ( fixup.authenticated ) {
+        //fprintf(stderr, "key=%d, addr=%d, div=0x%04X\n", fixup.auth.key, fixup.auth.usesAddrDiversity, fixup.auth.diversity);
+        dyld_chained_ptr_arm64e_auth_segmented_rebase* authRebasePtr = (dyld_chained_ptr_arm64e_auth_segmented_rebase*)fixup.location;
+        authRebasePtr->auth             = true;
+        authRebasePtr->next             = (uint32_t)(delta/stride());
+        authRebasePtr->key              = fixup.auth.key;
+        authRebasePtr->addrDiv          = fixup.auth.usesAddrDiversity;
+        authRebasePtr->diversity        = fixup.auth.diversity;
+        authRebasePtr->targetSegIndex   = segIndex;
+        authRebasePtr->targetSegOffset  = (uint32_t)segOffset;
+        if ( authRebasePtr->next*stride() != delta )
+            return badChainDistance(fixup, delta);
+        if ( (authRebasePtr->targetSegIndex != segIndex) || (authRebasePtr->targetSegOffset != segOffset) )
+            return badSegIndexOrOffset(fixup, segIndex, segOffset);
+    }
+    else {
+        //fprintf(stderr, "segIndex=%d, segOffset=0x%0llX\n", segIndex, segOffset);
+        dyld_chained_ptr_arm64e_segmented_rebase* rebasePtr = (dyld_chained_ptr_arm64e_segmented_rebase*)fixup.location;
+        rebasePtr->auth             = false;
+        rebasePtr->next             = (uint32_t)(delta/stride());
+        rebasePtr->padding          = 0;
+        rebasePtr->targetSegIndex   = segIndex;
+        rebasePtr->targetSegOffset  = (uint32_t)segOffset;
+        if ( rebasePtr->next*stride() != delta )
+            return badChainDistance(fixup, delta);
+        if ( (rebasePtr->targetSegIndex != segIndex) || (rebasePtr->targetSegOffset != segOffset) )
+            return badSegIndexOrOffset(fixup, segIndex, segOffset);
+    }
+    return Error::none();
+}
 
 
 //
@@ -1169,7 +877,7 @@
         return (void*)((uint8_t*)loc + ptr->next * 4);
     }
     
-    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress=0) const override {
+    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress, std::span<const uint64_t> segOffsetTable) const override {
         const dyld_chained_ptr_64_rebase*  rebasePtr = (dyld_chained_ptr_64_rebase*)loc;
         const dyld_chained_ptr_64_bind*    bindPtr   = (dyld_chained_ptr_64_bind*)loc;
         if ( bindPtr->bind )
@@ -1179,8 +887,8 @@
         else
             return Fixup(loc, seg, ((uint64_t)(rebasePtr->high8) << 56) | rebasePtr->target);
     }
-#if BUILDING_MACHO_WRITER
-    void             writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress) const override {
+
+    Error            writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*>) const override {
         intptr_t delta = (nextLoc == nullptr) ? 0 : ((uint8_t*)nextLoc - (uint8_t*)fixup.location);
         if ( fixup.isBind ) {
             dyld_chained_ptr_64_bind*  bindPtr = (dyld_chained_ptr_64_bind*)fixup.location;
@@ -1189,9 +897,12 @@
             bindPtr->reserved = 0;
             bindPtr->addend   = fixup.bind.embeddedAddend;
             bindPtr->ordinal  = fixup.bind.bindOrdinal;
-            assert(bindPtr->addend == fixup.bind.embeddedAddend);
-            assert(bindPtr->next*4 == delta);
-            assert(bindPtr->ordinal == fixup.bind.bindOrdinal);
+            if ( bindPtr->addend != fixup.bind.embeddedAddend )
+                return badAddend(fixup, fixup.bind.embeddedAddend);
+            if ( bindPtr->next*4 != delta )
+                return badChainDistance(fixup, delta);
+            if ( bindPtr->ordinal != fixup.bind.bindOrdinal )
+                return badBindOrdinal(fixup);
         }
         else if ( unauthRebaseIsVmAddr() ) {
             dyld_chained_ptr_64_rebase* rebasePtr = (dyld_chained_ptr_64_rebase*)fixup.location;
@@ -1202,9 +913,11 @@
             rebasePtr->reserved = 0;
             rebasePtr->high8    = high8;
             rebasePtr->target   = low56+preferedLoadAddress;
-            assert(rebasePtr->next*4 == delta);
-            assert(rebasePtr->target == (low56+preferedLoadAddress));
-        }
+            if ( rebasePtr->next*4 != delta )
+                return badChainDistance(fixup, delta);
+            if ( rebasePtr->target != (low56+preferedLoadAddress) )
+                return badVmAddr(fixup, preferedLoadAddress);
+       }
         else {
             dyld_chained_ptr_64_rebase* rebasePtr = (dyld_chained_ptr_64_rebase*)fixup.location;
             uint8_t   high8 = (fixup.rebase.targetVmOffset >> 56);
@@ -1214,11 +927,15 @@
             rebasePtr->reserved = 0;
             rebasePtr->high8    = high8;
             rebasePtr->target   = low56;
-            assert(rebasePtr->next*4 == delta);
-            assert(rebasePtr->target == low56);
-        }
-    }
-#endif
+            if ( rebasePtr->next*4 != delta )
+                return badChainDistance(fixup, delta);
+            uint64_t targetFromEncoding = (low56 | ((uint64_t)rebasePtr->high8 << 56));
+            if ( targetFromEncoding != fixup.rebase.targetVmOffset )
+                return badVmOffset(fixup);
+        }
+        return Error::none();
+    }
+
 protected:
     virtual bool    unauthRebaseIsVmAddr() const  { return true; }
 };
@@ -1250,7 +967,7 @@
         return (void*)((uint8_t*)loc + ptr->next * 4);
     }
     
-    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress=0) const override {
+    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress, std::span<const uint64_t> segOffsetTable) const override {
         const dyld_chained_ptr_32_rebase*  rebasePtr = (dyld_chained_ptr_32_rebase*)loc;
         const dyld_chained_ptr_32_bind*    bindPtr   = (dyld_chained_ptr_32_bind*)loc;
         if ( bindPtr->bind )
@@ -1258,8 +975,8 @@
         else
             return Fixup(loc, seg, rebasePtr->target);
     }
-#if BUILDING_MACHO_WRITER
-    void             writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress) const override {
+
+    Error            writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*>) const override {
         intptr_t delta = (nextLoc == nullptr) ? 0 : ((uint8_t*)nextLoc - (uint8_t*)fixup.location);
         if ( fixup.isBind ) {
             dyld_chained_ptr_32_bind* bindPtr = (dyld_chained_ptr_32_bind*)fixup.location;
@@ -1267,9 +984,12 @@
             bindPtr->next     = (uint32_t)(delta/4);
             bindPtr->addend   = fixup.bind.embeddedAddend;
             bindPtr->ordinal  = fixup.bind.bindOrdinal;
-            assert(bindPtr->next*4 == delta);
-            assert(bindPtr->addend == fixup.bind.embeddedAddend);
-            assert(bindPtr->ordinal == fixup.bind.bindOrdinal);
+            if ( bindPtr->next*4 != delta )
+                return badChainDistance(fixup, delta);
+            if ( bindPtr->addend != fixup.bind.embeddedAddend )
+                return badAddend(fixup, fixup.bind.embeddedAddend);
+            if ( bindPtr->ordinal != fixup.bind.bindOrdinal )
+                return badBindOrdinal(fixup);
         }
         else {
             dyld_chained_ptr_32_rebase*  rebasePtr = (dyld_chained_ptr_32_rebase*)fixup.location;
@@ -1277,11 +997,14 @@
             rebasePtr->next     = (uint32_t)(delta/4);
             uint64_t target = fixup.rebase.targetVmOffset+preferedLoadAddress;
             rebasePtr->target   = (uint32_t)target;
-            assert(rebasePtr->next*4 == delta);
-            assert(rebasePtr->target == target);
-        }
-    }
-#endif
+            if ( rebasePtr->next*4 != delta )
+                return badChainDistance(fixup, delta);
+            if ( rebasePtr->target != target )
+                return badVmOffset(fixup);
+        }
+        return Error::none();
+    }
+
 };
 
 
@@ -1311,20 +1034,22 @@
         return (void*)((uint8_t*)loc + ptr->next * 4);
     }
     
-    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress=0) const override {
+    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress, std::span<const uint64_t> segOffsetTable) const override {
         const dyld_chained_ptr_32_cache_rebase*  rebasePtr = (dyld_chained_ptr_32_cache_rebase*)loc;
         return Fixup(loc, seg, rebasePtr->target);
     }
-#if BUILDING_MACHO_WRITER
-    void             writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress) const override {
+
+    Error            writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*>) const override {
         intptr_t delta = (nextLoc == nullptr) ? 0 : ((uint8_t*)nextLoc - (uint8_t*)fixup.location);
         dyld_chained_ptr_32_cache_rebase*  rebasePtr = (dyld_chained_ptr_32_cache_rebase*)fixup.location;
         rebasePtr->next     = (uint32_t)(delta/4);
         rebasePtr->target   = (uint32_t)fixup.rebase.targetVmOffset;
-        assert(rebasePtr->next*4 == delta);
-        assert(rebasePtr->target == fixup.rebase.targetVmOffset);
-    }
-#endif
+        if ( rebasePtr->next*4 != delta )
+            return badChainDistance(fixup, delta);
+        if ( rebasePtr->target != fixup.rebase.targetVmOffset )
+            return badVmOffset(fixup);
+        return Error::none();
+    }
 };
 
 
@@ -1355,20 +1080,22 @@
         return (void*)((uint8_t*)loc + ptr->next * 4);
     }
     
-    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress=0) const override {
+    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress, std::span<const uint64_t> segOffsetTable) const override {
         const dyld_chained_ptr_32_firmware_rebase*  rebasePtr = (dyld_chained_ptr_32_firmware_rebase*)loc;
-        return Fixup(loc, seg, rebasePtr->target);
-    }
-#if BUILDING_MACHO_WRITER
-    void             writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress) const override {
+        return Fixup(loc, seg, rebasePtr->target - preferedLoadAddress);
+    }
+
+    Error            writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*>) const override {
         intptr_t delta = (nextLoc == nullptr) ? 0 : ((uint8_t*)nextLoc - (uint8_t*)fixup.location);
         dyld_chained_ptr_32_firmware_rebase*  rebasePtr = (dyld_chained_ptr_32_firmware_rebase*)fixup.location;
         rebasePtr->next     = (uint32_t)(delta/4);
         rebasePtr->target   = (uint32_t)fixup.rebase.targetVmOffset;
-        assert(rebasePtr->next*4 == delta);
-        assert(rebasePtr->target == fixup.rebase.targetVmOffset);
-    }
-#endif
+        if ( rebasePtr->next*4 != delta )
+            return badChainDistance(fixup, delta);
+        if ( rebasePtr->target != fixup.rebase.targetVmOffset )
+            return badVmOffset(fixup);
+        return Error::none();
+    }
 };
 
 
@@ -1412,7 +1139,7 @@
         return (void*)((uint8_t*)loc + ptr->next * 4);
     }
     
-    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress=0) const override {
+    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress, std::span<const uint64_t> segOffsetTable) const override {
         const dyld_chained_ptr_64_kernel_cache_rebase* rebasePtr = (dyld_chained_ptr_64_kernel_cache_rebase*)loc;
        if ( rebasePtr->isAuth )
            return Fixup(loc, seg, rebasePtr->target, rebasePtr->key, rebasePtr->addrDiv, rebasePtr->diversity);
@@ -1420,11 +1147,11 @@
            return Fixup(loc, seg, rebasePtr->target);
 
     }
-#if BUILDING_MACHO_WRITER
-    void             writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress) const override {
+
+    Error            writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*>) const override {
         intptr_t delta = (nextLoc == nullptr) ? 0 : ((uint8_t*)nextLoc - (uint8_t*)fixup.location);
         dyld_chained_ptr_64_kernel_cache_rebase* rebasePtr = (dyld_chained_ptr_64_kernel_cache_rebase*)fixup.location;
-      
+
         rebasePtr->isAuth     = fixup.authenticated ;
         rebasePtr->next       = delta/4;
         rebasePtr->key        = fixup.auth.key;
@@ -1432,10 +1159,12 @@
         rebasePtr->diversity  = fixup.auth.diversity;
         rebasePtr->cacheLevel = 0;  // FIXME
         rebasePtr->target     = fixup.rebase.targetVmOffset;
-        assert(rebasePtr->next*4 == delta);
-        assert(rebasePtr->target == fixup.rebase.targetVmOffset);
-    }
-#endif
+        if ( rebasePtr->next*4 != delta )
+            return badChainDistance(fixup, delta);
+        if ( rebasePtr->target != fixup.rebase.targetVmOffset )
+            return badVmOffset(fixup);
+        return Error::none();
+    }
 };
 
 
@@ -1465,7 +1194,7 @@
         return (void*)((uint8_t*)loc + ptr->next);
     }
 
-    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress=0) const override {
+    Fixup            parseChainEntry(const void* loc, const MappedSegment* seg, uint64_t preferedLoadAddress, std::span<const uint64_t> segOffsetTable) const override {
         const dyld_chained_ptr_64_kernel_cache_rebase* rebasePtr = (dyld_chained_ptr_64_kernel_cache_rebase*)loc;
        if ( rebasePtr->isAuth )
            return Fixup(loc, seg, rebasePtr->target, rebasePtr->key, rebasePtr->addrDiv, rebasePtr->diversity);
@@ -1473,8 +1202,8 @@
            return Fixup(loc, seg, rebasePtr->target);
 
     }
-#if BUILDING_MACHO_WRITER
-    void             writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress) const override {
+
+    Error            writeChainEntry(const Fixup& fixup, const void* nextLoc, uint64_t preferedLoadAddress, std::span<const MappedSegment*>) const override {
         intptr_t delta = (nextLoc == nullptr) ? 0 : ((uint8_t*)nextLoc - (uint8_t*)fixup.location);
         dyld_chained_ptr_64_kernel_cache_rebase* rebasePtr = (dyld_chained_ptr_64_kernel_cache_rebase*)fixup.location;
 
@@ -1485,34 +1214,86 @@
         rebasePtr->diversity  = 0;
         rebasePtr->cacheLevel = 0;  // FIXME
         rebasePtr->target     = fixup.rebase.targetVmOffset;
-        assert(rebasePtr->next == delta);
-        assert(rebasePtr->target == fixup.rebase.targetVmOffset);
-    }
-#endif
-};
+        if ( rebasePtr->next != delta )
+            return badChainDistance(fixup, delta);
+        if ( rebasePtr->target != fixup.rebase.targetVmOffset )
+            return badVmOffset(fixup);
+        return Error::none();
+    }
+};
+
+Error ChainedFixups::PointerFormat::badChainDistance(const Fixup& fixup, intptr_t delta) const
+{
+    return Error("distance between fixups (%ld) is not encodable in chain for fixup at %.*s+0x%0lX",
+                 delta, (int)fixup.segment->segName.size(), fixup.segment->segName.data(),
+                 (uintptr_t)fixup.location - (uintptr_t)fixup.segment->content);
+}
+
+Error ChainedFixups::PointerFormat::badBindOrdinal(const Fixup& fixup) const
+{
+    return Error("bind ordinal (%u) too large in fixup at %.*s+0x%0lX",
+                 fixup.bind.bindOrdinal, (int)fixup.segment->segName.size(), fixup.segment->segName.data(),
+                 (uintptr_t)fixup.location - (uintptr_t)fixup.segment->content);
+}
+
+Error ChainedFixups::PointerFormat::badVmOffset(const Fixup& fixup) const
+{
+    return Error("vmOffset (0x%0llX) cannot fit in fixup at %.*s+0x%0lX",
+                 fixup.rebase.targetVmOffset, (int)fixup.segment->segName.size(), fixup.segment->segName.data(),
+                 (uintptr_t)fixup.location - (uintptr_t)fixup.segment->content);
+}
+
+Error ChainedFixups::PointerFormat::badVmAddr(const Fixup& fixup, uint64_t baseAddress) const
+{
+    return Error("vmAddress (0x%0llX) cannot fit in fixup at %.*s+0x%0lX",
+                 fixup.rebase.targetVmOffset+baseAddress, (int)fixup.segment->segName.size(), fixup.segment->segName.data(),
+                 (uintptr_t)fixup.location - (uintptr_t)fixup.segment->content);
+}
+
+Error ChainedFixups::PointerFormat::badAddend(const Fixup& fixup, int64_t addend) const
+{
+    return Error("addend (%lld) cannot fit in fixup at %.*s+0x%0lX",
+                 addend, (int)fixup.segment->segName.size(), fixup.segment->segName.data(),
+                 (uintptr_t)fixup.location - (uintptr_t)fixup.segment->content);
+}
+
+Error ChainedFixups::PointerFormat::badSegIndexOrOffset(const Fixup& fixup, uint8_t segIndex, uint64_t segOffset) const
+{
+    return Error("segIndex (%d) and segOffset (0x%0llX) cannot fit in fixup at %.*s+0x%0lX",
+                 segIndex, segOffset, (int)fixup.segment->segName.size(), fixup.segment->segName.data(),
+                 (uintptr_t)fixup.location - (uintptr_t)fixup.segment->content);
+}
 
 
 bool ChainedFixups::PointerFormat::valid(uint16_t pointer_format)
 {
-    return (pointer_format <= DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE);
-}
+    return (pointer_format <= DYLD_CHAINED_PTR_ARM64E_SEGMENTED);
+}
+
+uint32_t ChainedFixups::PointerFormat::ptrAlignmentSize() const
+{
+    // most formats, including 64-bit, allow a 4-byte pointer alignment
+    return 4;
+}
+
+static const constinit PointerFormat_DYLD_CHAINED_PTR_ARM64E              p1;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_64                  p2;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_32                  p3;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_32_CACHE            p4;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_32_FIRMWARE         p5;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_64_OFFSET           p6;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_ARM64E_KERNEL       p7;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_64_KERNEL_CACHE     p8;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_ARM64E_USERLAND     p9;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_ARM64E_FIRMWARE     p10;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE p11;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_ARM64E_USERLAND24   p12;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE p13;
+static const constinit PointerFormat_DYLD_CHAINED_PTR_ARM64E_SEGMENTED    p14;
 
 
 const ChainedFixups::PointerFormat& ChainedFixups::PointerFormat::make(uint16_t pointer_format)
 {
-    static const PointerFormat_DYLD_CHAINED_PTR_ARM64E              p1;
-    static const PointerFormat_DYLD_CHAINED_PTR_64                  p2;
-    static const PointerFormat_DYLD_CHAINED_PTR_32                  p3;
-    static const PointerFormat_DYLD_CHAINED_PTR_32_CACHE            p4;
-    static const PointerFormat_DYLD_CHAINED_PTR_32_FIRMWARE         p5;
-    static const PointerFormat_DYLD_CHAINED_PTR_64_OFFSET           p6;
-    static const PointerFormat_DYLD_CHAINED_PTR_ARM64E_KERNEL       p7;
-    static const PointerFormat_DYLD_CHAINED_PTR_64_KERNEL_CACHE     p8;
-    static const PointerFormat_DYLD_CHAINED_PTR_ARM64E_USERLAND     p9;
-    static const PointerFormat_DYLD_CHAINED_PTR_ARM64E_FIRMWARE     p10;
-    static const PointerFormat_DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE p11;
-    static const PointerFormat_DYLD_CHAINED_PTR_ARM64E_USERLAND24   p12;
-
     switch (pointer_format) {
         case DYLD_CHAINED_PTR_ARM64E:
             return p1;
@@ -1538,10 +1319,13 @@
             return p11;
         case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
             return p12;
+        case DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE:
+            return p13;
+        case DYLD_CHAINED_PTR_ARM64E_SEGMENTED:
+            return p14;
     }
     assert("unknown pointer format");
     return p1;
 }
 
-
 } // namespace mach_o