Loading...
cache_builder/SubCache.cpp dyld-1122.1 dyld-1235.2
--- dyld/dyld-1122.1/cache_builder/SubCache.cpp
+++ dyld/dyld-1235.2/cache_builder/SubCache.cpp
@@ -25,6 +25,7 @@
 #include "BuilderConfig.h"
 #include "BuilderOptions.h"
 #include "CacheDylib.h"
+#include "Chunk.h"
 #include "CodeSigningTypes.h"
 #include "DyldSharedCache.h"
 #include "SubCache.h"
@@ -54,24 +55,59 @@
 // MARK: --- SharedCacheBuilder::Region methods ---
 
 uint32_t Region::initProt() const
+{
+    uint32_t initProt = 0;
+    switch ( this->kind ) {
+        case Region::Kind::text:
+            initProt = VM_PROT_READ | VM_PROT_EXECUTE;
+            break;
+        case Region::Kind::data:
+        case Region::Kind::auth:
+            initProt = VM_PROT_READ | VM_PROT_WRITE;
+            break;
+        case Region::Kind::dataConst:
+        case Region::Kind::authConst:
+        case Region::Kind::tproConst:
+        case Region::Kind::tproAuthConst:
+            initProt = VM_PROT_READ;
+            break;
+        case Region::Kind::readOnly:
+        case Region::Kind::linkedit:
+            initProt = VM_PROT_READ;
+            break;
+        case Region::Kind::dynamicConfig:
+            // HACK: This is not actually mapped, but it is used in vm calculations
+            initProt = VM_PROT_READ;
+            break;
+        case Region::Kind::unmapped:
+        case Region::Kind::codeSignature:
+            // This isn't mapped, so we should never ask for its initProt
+            assert(0);
+            break;
+        case Region::Kind::numKinds:
+            assert(0);
+            break;
+    }
+
+    return initProt;
+}
+
+uint32_t Region::maxProt() const
 {
     uint32_t maxProt = 0;
     switch ( this->kind ) {
         case Region::Kind::text:
             maxProt = VM_PROT_READ | VM_PROT_EXECUTE;
             break;
+        case Region::Kind::tproConst:
         case Region::Kind::data:
+        case Region::Kind::dataConst:
+        case Region::Kind::tproAuthConst:
+        case Region::Kind::auth:
+        case Region::Kind::authConst:
             maxProt = VM_PROT_READ | VM_PROT_WRITE;
             break;
-        case Region::Kind::dataConst:
-            maxProt = VM_PROT_READ;
-            break;
-        case Region::Kind::auth:
-            maxProt = VM_PROT_READ | VM_PROT_WRITE;
-            break;
-        case Region::Kind::authConst:
-            maxProt = VM_PROT_READ;
-            break;
+        case Region::Kind::readOnly:
         case Region::Kind::linkedit:
             maxProt = VM_PROT_READ;
             break;
@@ -92,45 +128,6 @@
     return maxProt;
 }
 
-uint32_t Region::maxProt() const
-{
-    uint32_t maxProt = 0;
-    switch ( this->kind ) {
-        case Region::Kind::text:
-            maxProt = VM_PROT_READ | VM_PROT_EXECUTE;
-            break;
-        case Region::Kind::data:
-            maxProt = VM_PROT_READ | VM_PROT_WRITE;
-            break;
-        case Region::Kind::dataConst:
-            maxProt = VM_PROT_READ | VM_PROT_WRITE;
-            break;
-        case Region::Kind::auth:
-            maxProt = VM_PROT_READ | VM_PROT_WRITE;
-            break;
-        case Region::Kind::authConst:
-            maxProt = VM_PROT_READ | VM_PROT_WRITE;
-            break;
-        case Region::Kind::linkedit:
-            maxProt = VM_PROT_READ;
-            break;
-        case Region::Kind::dynamicConfig:
-            // HACK: This is not actually mapped, but it is used in vm calculations
-            maxProt = VM_PROT_READ;
-            break;
-        case Region::Kind::unmapped:
-        case Region::Kind::codeSignature:
-            // This isn't mapped, so we should never ask for its maxprot
-            assert(0);
-            break;
-        case Region::Kind::numKinds:
-            assert(0);
-            break;
-    }
-
-    return maxProt;
-}
-
 bool Region::canContainAuthPointers() const
 {
     bool result = false;
@@ -139,14 +136,19 @@
             // We should never ask this region if it has auth content
             assert(0);
             break;
+        case Region::Kind::tproConst:
+        case Region::Kind::tproAuthConst:
         case Region::Kind::data:
         case Region::Kind::dataConst:
+            // Note TPRO never contains authenticated fixups, so its name is wrong
+            // but its really there to be placed next to the AUTH regions
             result = false;
             break;
         case Region::Kind::auth:
         case Region::Kind::authConst:
             result = true;
             break;
+        case Region::Kind::readOnly:
         case Region::Kind::linkedit:
         case Region::Kind::dynamicConfig:
             // We should never ask this region if it has auth content
@@ -170,10 +172,13 @@
 {
     switch ( this->kind ) {
         case Region::Kind::text:
+        case Region::Kind::tproConst:
         case Region::Kind::data:
         case Region::Kind::dataConst:
+        case Region::Kind::tproAuthConst:
         case Region::Kind::auth:
         case Region::Kind::authConst:
+        case Region::Kind::readOnly:
         case Region::Kind::linkedit:
             return true;
         case Region::Kind::unmapped:
@@ -190,10 +195,13 @@
 {
     switch ( this->kind ) {
         case Region::Kind::text:
+        case Region::Kind::tproConst:
         case Region::Kind::data:
         case Region::Kind::dataConst:
+        case Region::Kind::tproAuthConst:
         case Region::Kind::auth:
         case Region::Kind::authConst:
+        case Region::Kind::readOnly:
         case Region::Kind::linkedit:
         case Region::Kind::dynamicConfig:
             return true;
@@ -221,10 +229,16 @@
             bool nextIsRW = (next.initProt() & VM_PROT_WRITE) != 0;
             return nextIsRW;
         }
+        case Region::Kind::tproConst:
+        case Region::Kind::tproAuthConst:
         case Region::Kind::data:
         case Region::Kind::auth: {
             // HACK: Remove once we have rdar://96315050
             if ( (this->kind == Region::Kind::auth) && (next.kind == Region::Kind::authConst) )
+                return false;
+
+            // Don't add adding from data to tproConst as we didn't have padding from data to auth before
+            if ( (next.kind == Kind::tproConst) || (next.kind == Kind::tproAuthConst) )
                 return false;
 
             // Add padding if DATA is adjacent to something immutable, eg TEXT/DATA_CONST
@@ -233,12 +247,17 @@
         }
         case Region::Kind::dataConst:
         case Region::Kind::authConst: {
+            // Always add padding from DATA_CONST to TPRO_CONST as TPRO_CONST will actually be dirtied
+            if ( (next.kind == Kind::tproConst) || (next.kind == Kind::tproAuthConst) )
+                return true;
+
             // Don't add padding if *_CONST is next to *_CONST, otherwise add padding
             bool nextInitIsRO = (next.initProt() & VM_PROT_WRITE) == 0;
             bool nextMaxIsRW = (next.maxProt() & VM_PROT_WRITE) != 0;
             bool nextIsDataConst = nextInitIsRO & nextMaxIsRW;
             return !nextIsDataConst;
         }
+        case Region::Kind::readOnly:
         case Region::Kind::linkedit:
         case Region::Kind::dynamicConfig: {
             // Add padding if LINKEDIT is adjacent to something that is mutable,
@@ -321,11 +340,14 @@
         switch ( region.kind ) {
             case cache_builder::Region::Kind::text:
                 break;
+            case cache_builder::Region::Kind::tproConst:
+            case cache_builder::Region::Kind::tproAuthConst:
             case cache_builder::Region::Kind::dataConst:
             case cache_builder::Region::Kind::data:
             case cache_builder::Region::Kind::auth:
             case cache_builder::Region::Kind::authConst:
                 return true;
+            case Region::Kind::readOnly:
             case cache_builder::Region::Kind::linkedit:
             case cache_builder::Region::Kind::unmapped:
             case cache_builder::Region::Kind::dynamicConfig:
@@ -344,11 +366,14 @@
             continue;
         switch ( region.kind ) {
             case cache_builder::Region::Kind::text:
+            case cache_builder::Region::Kind::tproConst:
+            case cache_builder::Region::Kind::tproAuthConst:
             case cache_builder::Region::Kind::dataConst:
             case cache_builder::Region::Kind::data:
             case cache_builder::Region::Kind::auth:
             case cache_builder::Region::Kind::authConst:
                 break;
+            case Region::Kind::readOnly:
             case cache_builder::Region::Kind::linkedit:
                 return true;
             case cache_builder::Region::Kind::unmapped:
@@ -700,6 +725,20 @@
     this->regions[(uint32_t)Region::Kind::text].chunks.push_back(chunk);
 }
 
+void SubCache::addTPROConstChunk(const BuilderConfig& config, Chunk* chunk)
+{
+    // Rosetta can't handle an extra shared cache mapping, so use DATA instead
+    // We use DATA instead of DATA_CONST because we don't want the MemoryManager
+    // and DataConstWriter to both mprotect the same pages.  But the DataConstWriter
+    // will ignore everything in DATA while the MemoryManager will find TPRO there
+    if ( config.layout.discontiguous.has_value() )
+        this->addDataChunk(chunk);
+    else if ( config.layout.hasAuthRegion )
+        this->regions[(uint32_t)Region::Kind::tproAuthConst].chunks.push_back(chunk);
+    else
+        this->regions[(uint32_t)Region::Kind::tproConst].chunks.push_back(chunk);
+}
+
 void SubCache::addDataChunk(Chunk* chunk)
 {
     this->regions[(uint32_t)Region::Kind::data].chunks.push_back(chunk);
@@ -720,6 +759,15 @@
     this->regions[(uint32_t)Region::Kind::authConst].chunks.push_back(chunk);
 }
 
+void SubCache::addReadOnlyChunk(const BuilderConfig& config, Chunk* chunk)
+{
+    // Rosetta can't handle an extra shared cache mapping, so use __TEXT instead
+    if ( config.layout.discontiguous.has_value() )
+        this->addTextChunk(chunk);
+    else
+        this->regions[(uint32_t)Region::Kind::readOnly].chunks.push_back(chunk);
+}
+
 void SubCache::addLinkeditChunk(Chunk* chunk)
 {
     this->regions[(uint32_t)Region::Kind::linkedit].chunks.push_back(chunk);
@@ -733,22 +781,6 @@
 void SubCache::addCodeSignatureChunk(Chunk* chunk)
 {
     this->regions[(uint32_t)Region::Kind::codeSignature].chunks.push_back(chunk);
-}
-
-// HACK: We need to insert the libobjc __TEXT first so that its before all the other OBJC_RO chunks
-void SubCache::addObjCTextChunk(Chunk* chunk)
-{
-    std::vector<Chunk*>& chunks = this->regions[(uint32_t)Region::Kind::text].chunks;
-    chunks.insert(chunks.begin(), chunk);
-}
-
-// ObjC optimizations need to add read-only chunks.  For now these are added to the start
-// of TEXT, so that they are in the same subCache when split by One Cache.  In future we want to
-// move these to LINKEDIT
-void SubCache::addObjCReadOnlyChunk(Chunk* chunk)
-{
-    std::vector<Chunk*>& chunks = this->regions[(uint32_t)Region::Kind::text].chunks;
-    chunks.insert(chunks.begin(), chunk);
 }
 
 // All objc optimizations need to be contiguous, but that means if any need AUTH then all do
@@ -762,18 +794,17 @@
     }
 }
 
-void SubCache::addDylib(CacheDylib& cacheDylib, bool addLinkedit)
+void SubCache::addDylib(const BuilderConfig& config, CacheDylib& cacheDylib)
 {
     for ( DylibSegmentChunk& segmentInfo : cacheDylib.segments ) {
         switch ( segmentInfo.kind ) {
             case Chunk::Kind::dylibText:
-                if ( cacheDylib.installName == "/usr/lib/libobjc.A.dylib" )
-                    this->addObjCTextChunk(&segmentInfo);
-                else
-                    this->addTextChunk(&segmentInfo);
+                this->addTextChunk(&segmentInfo);
+                break;
+            case Chunk::Kind::tproDataConst:
+                this->addTPROConstChunk(config, &segmentInfo);
                 break;
             case Chunk::Kind::dylibData:
-            case Chunk::Kind::dylibDataConstWorkaround:
                 this->addDataChunk(&segmentInfo);
                 break;
             case Chunk::Kind::dylibDataConst:
@@ -787,15 +818,13 @@
                     this->addDataChunk(&segmentInfo);
                 break;
             case Chunk::Kind::dylibAuth:
-            case Chunk::Kind::dylibAuthConstWorkaround:
                 this->addAuthChunk(&segmentInfo);
                 break;
             case Chunk::Kind::dylibAuthConst:
                 this->addAuthConstChunk(&segmentInfo);
                 break;
             case Chunk::Kind::dylibReadOnly:
-                // FIXME: Read-only data should really be in a read-only mapping.
-                this->addTextChunk(&segmentInfo);
+                this->addReadOnlyChunk(config, &segmentInfo);
                 break;
             case Chunk::Kind::dylibLinkedit:
                 // Skip adding here.  We'll do this in addLinkeditFromDylib()
@@ -806,8 +835,7 @@
         }
     }
 
-    if ( addLinkedit )
-        this->addLinkeditFromDylib(cacheDylib);
+    this->addLinkeditFromDylib(cacheDylib);
 }
 
 // Linkedit is stored in Chunks in its own array on the dylib.  This adds it to the subCache.
@@ -821,16 +849,98 @@
         this->addLinkeditChunk(&chunk);
 }
 
-void SubCache::addCacheHeaderChunk(const std::span<CacheDylib> cacheDylibs)
+void SubCache::forEachTPRORegionInData(SubCache* mainSubCache, std::span<SubCache*> subCaches,
+                                       void (^callback)(Region& region, const Chunk* firstChunk, const Chunk* lastChunk))
+{
+    // Find all subcaches with TPRO regions, or on x86_64 its those with DATA in them
+    auto runOnSubCache = ^(SubCache* subCache) {
+        for ( Region& region : subCache->regions ) {
+            if ( region.kind != Region::Kind::data )
+                continue;
+
+            const Chunk* firstTPROChunk = nullptr;
+            const Chunk* lastTPROChunk = nullptr;
+            for ( const Chunk* chunk : region.chunks ) {
+                if ( chunk->isTPROChunk() ) {
+                    if ( !firstTPROChunk ) {
+                        firstTPROChunk = chunk;
+                        lastTPROChunk = chunk;
+                    } else {
+                        lastTPROChunk = chunk;
+                    }
+                } else if ( firstTPROChunk ) {
+                    // HACK: If we find an alignment chunk then add it as-if its a TPRO chunk. This is to
+                    // make sure callers can get page-aligned TPRO regions
+                    if ( const AlignChunk* alignChunk = chunk->isAlignChunk() ) {
+                        if ( alignChunk->alignment() >= 4_KB ) {
+                            lastTPROChunk = chunk;
+                            continue;
+                        }
+                    }
+
+                    // Found a non-tpro chunk.  Break here and do the callback outside the chunk loop
+                    // We don't need to continue the loop as we sorted all the TPRO to be adjacent
+                    break;
+                }
+            }
+
+            if ( firstTPROChunk ) {
+                // Ended with a tpro chunk.  Make the callback for it
+                callback(region, firstTPROChunk, lastTPROChunk);
+            }
+        }
+    };
+
+    runOnSubCache(mainSubCache);
+    for ( SubCache* subCache : subCaches )
+        runOnSubCache(subCache);
+}
+
+static void forEachTPRORegion(const BuilderConfig& config, SubCache* mainSubCache,
+                              std::span<SubCache*> subCaches,
+                              void (^callback)(CacheVMAddress firstChunkAddress, CacheVMAddress lastChunkAddress, CacheVMSize lastChunkSize))
+{
+    // Find all subcaches with TPRO regions, or on x86_64 its those with DATA in them
+    if ( config.layout.tproIsInData ) {
+        SubCache::forEachTPRORegionInData(mainSubCache, subCaches, ^(Region& region, const Chunk *firstChunk, const Chunk *lastChunk) {
+            callback(firstChunk->cacheVMAddress, lastChunk->cacheVMAddress, lastChunk->cacheVMSize);
+        });
+        return;
+    }
+
+    auto runOnSubCache = ^(const SubCache* subCache) {
+        for ( const Region& region : subCache->regions ) {
+            if ( (region.kind == Region::Kind::tproConst) || (region.kind == Region::Kind::tproAuthConst) )
+                callback(region.subCacheVMAddress, region.subCacheVMAddress, region.subCacheVMSize);
+        }
+    };
+
+    runOnSubCache(mainSubCache);
+    for ( const SubCache* subCache : subCaches )
+        runOnSubCache(subCache);
+}
+
+static uint32_t numTPRORegions(const BuilderConfig& config, SubCache* mainSubCache,
+                               std::span<SubCache*> subCaches)
+{
+    __block uint32_t count = 0;
+    forEachTPRORegion(config, mainSubCache, subCaches, ^(CacheVMAddress, CacheVMAddress, CacheVMSize) {
+        ++count;
+    });
+    return count;
+}
+
+void SubCache::addCacheHeaderChunk(const BuilderConfig& config, const std::span<CacheDylib> cacheDylibs)
 {
     // calculate size of header info and where first dylib's mach_header should start
     uint64_t numMappings = this->regions.size();
     uint64_t startOffset = sizeof(dyld_cache_header) + (numMappings * sizeof(dyld_cache_mapping_info));
     startOffset += numMappings * sizeof(dyld_cache_mapping_and_slide_info);
 
-    // Only the main cache has a list of subCaches to write
+    // Only the main cache has a list of subCaches and TPRO mappings to write
     if ( this->isMainCache() ) {
         startOffset += sizeof(dyld_subcache_entry) * this->subCaches.size();
+        startOffset += sizeof(dyld_cache_tpro_mapping_info) * numTPRORegions(config, this, this->subCaches);
     }
 
     if ( this->needsCacheHeaderImageList() ) {
@@ -869,6 +979,12 @@
     // we sort the Chunk's.  For now just add placeholder(s) and we'll update the
     // size in calculateSlideInfoSize()
 
+    // TPRO_CONST
+    if ( !this->regions[(uint32_t)Region::Kind::tproConst].chunks.empty() ) {
+        this->tproConstSlideInfo = std::make_unique<SlideInfoChunk>();
+        addLinkeditChunk(this->tproConstSlideInfo.get());
+    }
+
     // DATA
     if ( !this->regions[(uint32_t)Region::Kind::data].chunks.empty() ) {
         this->dataSlideInfo = std::make_unique<SlideInfoChunk>();
@@ -881,6 +997,12 @@
         addLinkeditChunk(this->dataConstSlideInfo.get());
     }
 
+    // TPRO_CONST (on arm64e)
+    if ( !this->regions[(uint32_t)Region::Kind::tproAuthConst].chunks.empty() ) {
+        this->tproAuthConstSlideInfo = std::make_unique<SlideInfoChunk>();
+        addLinkeditChunk(this->tproAuthConstSlideInfo.get());
+    }
+
     // AUTH
     if ( !this->regions[(uint32_t)Region::Kind::auth].chunks.empty() ) {
         this->authSlideInfo = std::make_unique<SlideInfoChunk>();
@@ -914,7 +1036,7 @@
     this->addLinkeditChunk(this->objcOptsHeader.get());
 }
 
-void SubCache::addObjCHeaderInfoReadOnlyChunk(ObjCOptimizer& objcOptimizer)
+void SubCache::addObjCHeaderInfoReadOnlyChunk(const BuilderConfig& config, ObjCOptimizer& objcOptimizer)
 {
     this->objcHeaderInfoRO = std::make_unique<ObjCHeaderInfoReadOnlyChunk>();
     this->objcHeaderInfoRO->cacheVMSize         = CacheVMSize(objcOptimizer.headerInfoReadOnlyByteSize);
@@ -922,10 +1044,10 @@
 
     objcOptimizer.headerInfoReadOnlyChunk = this->objcHeaderInfoRO.get();
 
-    this->addObjCReadOnlyChunk(this->objcHeaderInfoRO.get());
-}
-
-void SubCache::addObjCImageInfoChunk(ObjCOptimizer& objcOptimizer)
+    this->addReadOnlyChunk(config, this->objcHeaderInfoRO.get());
+}
+
+void SubCache::addObjCImageInfoChunk(const BuilderConfig& config, ObjCOptimizer& objcOptimizer)
 {
     this->objcImageInfo = std::make_unique<ObjCImageInfoChunk>();
     this->objcImageInfo->cacheVMSize         = CacheVMSize(objcOptimizer.imageInfoSize);
@@ -933,10 +1055,10 @@
 
     objcOptimizer.imageInfoChunk = this->objcImageInfo.get();
 
-    this->addObjCReadOnlyChunk(this->objcImageInfo.get());
-}
-
-void SubCache::addObjCSelectorStringsChunk(ObjCSelectorOptimizer& objCSelectorOptimizer)
+    this->addReadOnlyChunk(config, this->objcImageInfo.get());
+}
+
+void SubCache::addObjCSelectorStringsChunk(const BuilderConfig& config, ObjCSelectorOptimizer& objCSelectorOptimizer)
 {
     this->objcSelectorStrings = std::make_unique<ObjCStringsChunk>();
     this->objcSelectorStrings->cacheVMSize      = CacheVMSize(objCSelectorOptimizer.selectorStringsTotalByteSize);
@@ -944,10 +1066,10 @@
 
     objCSelectorOptimizer.selectorStringsChunk = this->objcSelectorStrings.get();
 
-    this->addObjCReadOnlyChunk(this->objcSelectorStrings.get());
-}
-
-void SubCache::addObjCSelectorHashTableChunk(ObjCSelectorOptimizer& objCSelectorOptimizer)
+    this->addReadOnlyChunk(config, this->objcSelectorStrings.get());
+}
+
+void SubCache::addObjCSelectorHashTableChunk(const BuilderConfig& config, ObjCSelectorOptimizer& objCSelectorOptimizer)
 {
     this->objcSelectorsHashTable = std::make_unique<ObjCSelectorHashTableChunk>();
     this->objcSelectorsHashTable->cacheVMSize       = CacheVMSize(objCSelectorOptimizer.selectorHashTableTotalByteSize);
@@ -955,10 +1077,10 @@
 
     objCSelectorOptimizer.selectorHashTableChunk = this->objcSelectorsHashTable.get();
 
-    this->addObjCReadOnlyChunk(this->objcSelectorsHashTable.get());
-}
-
-void SubCache::addObjCClassNameStringsChunk(ObjCClassOptimizer& objcClassOptimizer)
+    this->addReadOnlyChunk(config, this->objcSelectorsHashTable.get());
+}
+
+void SubCache::addObjCClassNameStringsChunk(const BuilderConfig& config, ObjCClassOptimizer& objcClassOptimizer)
 {
     this->objcClassNameStrings = std::make_unique<ObjCStringsChunk>();
     this->objcClassNameStrings->cacheVMSize         = CacheVMSize(objcClassOptimizer.nameStringsTotalByteSize);
@@ -966,10 +1088,10 @@
 
     objcClassOptimizer.classNameStringsChunk = this->objcClassNameStrings.get();
 
-    this->addObjCReadOnlyChunk(this->objcClassNameStrings.get());
-}
-
-void SubCache::addObjCClassHashTableChunk(ObjCClassOptimizer& objcClassOptimizer)
+    this->addReadOnlyChunk(config, this->objcClassNameStrings.get());
+}
+
+void SubCache::addObjCClassHashTableChunk(const BuilderConfig& config, ObjCClassOptimizer& objcClassOptimizer)
 {
     this->objcClassesHashTable = std::make_unique<ObjCClassHashTableChunk>();
     this->objcClassesHashTable->cacheVMSize         = CacheVMSize(objcClassOptimizer.classHashTableTotalByteSize);
@@ -977,10 +1099,10 @@
 
     objcClassOptimizer.classHashTableChunk = this->objcClassesHashTable.get();
 
-    this->addObjCReadOnlyChunk(this->objcClassesHashTable.get());
-}
-
-void SubCache::addObjCProtocolNameStringsChunk(ObjCProtocolOptimizer& objcProtocolOptimizer)
+    this->addReadOnlyChunk(config, this->objcClassesHashTable.get());
+}
+
+void SubCache::addObjCProtocolNameStringsChunk(const BuilderConfig& config, ObjCProtocolOptimizer& objcProtocolOptimizer)
 {
     this->objcProtocolNameStrings = std::make_unique<ObjCStringsChunk>();
     this->objcProtocolNameStrings->cacheVMSize      = CacheVMSize(objcProtocolOptimizer.nameStringsTotalByteSize);
@@ -988,10 +1110,10 @@
 
     objcProtocolOptimizer.protocolNameStringsChunk = this->objcProtocolNameStrings.get();
 
-    this->addObjCReadOnlyChunk(this->objcProtocolNameStrings.get());
-}
-
-void SubCache::addObjCProtocolHashTableChunk(ObjCProtocolOptimizer& objcProtocolOptimizer)
+    this->addReadOnlyChunk(config, this->objcProtocolNameStrings.get());
+}
+
+void SubCache::addObjCProtocolHashTableChunk(const BuilderConfig& config, ObjCProtocolOptimizer& objcProtocolOptimizer)
 {
     this->objcProtocolsHashTable = std::make_unique<ObjCProtocolHashTableChunk>();
     this->objcProtocolsHashTable->cacheVMSize       = CacheVMSize(objcProtocolOptimizer.protocolHashTableTotalByteSize);
@@ -999,10 +1121,10 @@
 
     objcProtocolOptimizer.protocolHashTableChunk = this->objcProtocolsHashTable.get();
 
-    this->addObjCReadOnlyChunk(this->objcProtocolsHashTable.get());
-}
-
-void SubCache::addObjCProtocolSwiftDemangledNamesChunk(ObjCProtocolOptimizer& objcProtocolOptimizer)
+    this->addReadOnlyChunk(config, this->objcProtocolsHashTable.get());
+}
+
+void SubCache::addObjCProtocolSwiftDemangledNamesChunk(const BuilderConfig& config, ObjCProtocolOptimizer& objcProtocolOptimizer)
 {
     this->objcSwiftDemangledNameStrings = std::make_unique<ObjCStringsChunk>();
     this->objcSwiftDemangledNameStrings->cacheVMSize        = CacheVMSize(objcProtocolOptimizer.swiftDemangledNameStringsTotalByteSize);
@@ -1010,7 +1132,7 @@
 
     objcProtocolOptimizer.swiftDemangledNameStringsChunk = this->objcSwiftDemangledNameStrings.get();
 
-    this->addObjCReadOnlyChunk(this->objcSwiftDemangledNameStrings.get());
+    this->addReadOnlyChunk(config, this->objcSwiftDemangledNameStrings.get());
 }
 
 void SubCache::addObjCCanonicalProtocolsChunk(const BuilderConfig& config,
@@ -1047,7 +1169,7 @@
     objcCategoryOptimizer.categoriesChunk = this->objcCategories.get();
 
     // Add objc categories
-    addObjCReadOnlyChunk(this->objcCategories.get());
+    addReadOnlyChunk(config, this->objcCategories.get());
 }
 
 void SubCache::addCacheTrieChunk(DylibTrieOptimizer& dylibTrieOptimizer)
@@ -1156,6 +1278,18 @@
     opt.foreignTypeConformancesHashTable = this->swiftForeignTypeHashTable.get();
 
     this->addLinkeditChunk(this->swiftForeignTypeHashTable.get());
+}
+
+void SubCache::addSwiftPrespecializedMetadataPointerTableChunks(SwiftProtocolConformanceOptimizer& opt)
+{
+    for ( PointerHashTableOptimizerInfo& tableInfo : opt.prespecializedMetadataHashTables ) {
+        PointerHashTableChunk* chunk = this->pointerHashTables.emplace_back(std::make_unique<PointerHashTableChunk>()).get();
+        chunk->cacheVMSize       = CacheVMSize(tableInfo.size);
+        chunk->subCacheFileSize  = CacheFileSize(tableInfo.size);
+
+        tableInfo.chunk = chunk;
+        this->addLinkeditChunk(chunk);
+    }
 }
 
 void SubCache::addUnmappedSymbols(const BuilderConfig& config, UnmappedSymbolsOptimizer& opt)
@@ -1255,6 +1389,26 @@
             case Region::Kind::text:
                 flags    = this->isStubsCache() ? DYLD_CACHE_MAPPING_TEXT_STUBS : 0;
                 break;
+            case Region::Kind::tproConst:
+                flags    = DYLD_CACHE_MAPPING_CONST_DATA | DYLD_CACHE_MAPPING_CONST_TPRO_DATA;
+
+                // Get the slide info
+                if ( this->tproConstSlideInfo ) {
+                    slideInfoFileOffset = this->tproConstSlideInfo->subCacheFileOffset;
+                    slideInfoFileSize   = this->tproConstSlideInfo->usedFileSize;
+                }
+                break;
+            case Region::Kind::tproAuthConst:
+                // Note, we don't set AUTH here.  TPRO_CONST doesn't actually contain authenticated fixups, ie, is just data
+                // but we still give it its own Region so that we can place it adjacent to dirty data
+                flags    = DYLD_CACHE_MAPPING_CONST_DATA | DYLD_CACHE_MAPPING_CONST_TPRO_DATA;
+
+                // Get the slide info
+                if ( this->tproAuthConstSlideInfo ) {
+                    slideInfoFileOffset = this->tproAuthConstSlideInfo->subCacheFileOffset;
+                    slideInfoFileSize   = this->tproAuthConstSlideInfo->usedFileSize;
+                }
+                break;
             case Region::Kind::data:
                 flags    = 0;
 
@@ -1290,6 +1444,9 @@
                     slideInfoFileOffset = this->authConstSlideInfo->subCacheFileOffset;
                     slideInfoFileSize   = this->authConstSlideInfo->usedFileSize;
                 }
+                break;
+            case Region::Kind::readOnly:
+                flags    = DYLD_CACHE_READ_ONLY_DATA;
                 break;
             case Region::Kind::linkedit:
                 flags    = 0;
@@ -1424,12 +1581,14 @@
     dyldCacheHeader->cacheAtlasSize                = 0; // set later only on the main cache file
     dyldCacheHeader->dynamicDataOffset             = 0; // set later only on the main cache file
     dyldCacheHeader->dynamicDataMaxSize            = 0; // set later only on the main cache file
+    dyldCacheHeader->tproMappingsOffset            = 0; // set later only on the main cache file
+    dyldCacheHeader->tproMappingsCount             = 0; // set later only on the main cache file
 
     // Fill in old mappings
     // And new mappings which also have slide info
     this->writeCacheHeaderMappings();
 
-    this->addCacheHeaderImageInfo(options, cacheDylibs);
+    this->addCacheHeaderImageInfo(options, config, cacheDylibs);
 }
 
 void SubCache::addMainCacheHeaderInfo(const BuilderOptions& options, const BuilderConfig& config,
@@ -1463,7 +1622,7 @@
     if ( !objcOptimizer.objcDylibs.empty() ) {
         const auto& opt = swiftProtocolConformanceOpt;
         dyldCacheHeader->swiftOptsOffset = (opt.optsHeaderChunk->cacheVMAddress - cacheBaseAddress).rawValue();
-        dyldCacheHeader->objcOptsSize    = opt.optsHeaderChunk->subCacheFileSize.rawValue();
+        dyldCacheHeader->swiftOptsSize   = opt.optsHeaderChunk->subCacheFileSize.rawValue();
     }
 
     dyldCacheHeader->patchInfoAddr = patchTableOptimizer.patchTableChunk->cacheVMAddress.rawValue();
@@ -1508,6 +1667,25 @@
         dyldCacheHeader->dynamicDataOffset  = (dynamicConfig->cacheVMAddress - cacheBaseAddress).rawValue();
         dyldCacheHeader->dynamicDataMaxSize = dynamicConfig->cacheVMSize.rawValue();
     }
+
+    // Find all the TPRO regions
+    if ( dyldCacheHeader->tproMappingsCount != 0 ) {
+        assert(cacheHeaderChunk.subCacheFileOffset.rawValue() == 0);
+        auto* mappings = (dyld_cache_tpro_mapping_info*)((uint8_t*)dyldCacheHeader + dyldCacheHeader->tproMappingsOffset);
+        __block uint32_t index = 0;
+        forEachTPRORegion(config, this, this->subCaches, ^(CacheVMAddress firstChunkAddress, CacheVMAddress lastChunkAddress, CacheVMSize lastChunkSize) {
+            assert(index < dyldCacheHeader->tproMappingsCount);
+
+            CacheVMAddress vmAddr = firstChunkAddress;
+            CacheVMSize vmSize = (lastChunkAddress - firstChunkAddress) + lastChunkSize;
+
+            dyld_cache_tpro_mapping_info* mapping = & mappings[index];
+            mapping->unslidAddress = vmAddr.rawValue();
+            mapping->size = vmSize.rawValue();
+
+            ++index;
+        });
+    }
 }
 
 void SubCache::addSymbolsCacheHeaderInfo(const UnmappedSymbolsOptimizer& optimizer)
@@ -1531,6 +1709,7 @@
 }
 
 void SubCache::addCacheHeaderImageInfo(const BuilderOptions& options,
+                                       const BuilderConfig& config,
                                        const std::span<CacheDylib> cacheDylibs)
 {
     if ( !this->needsCacheHeaderImageList() )
@@ -1548,9 +1727,14 @@
     dyldCacheHeader->subCacheArrayOffset = (uint32_t)(dyldCacheHeader->imagesTextOffset + sizeof(dyld_cache_image_text_info) * cacheDylibs.size());
     dyldCacheHeader->subCacheArrayCount  = (uint32_t)this->subCaches.size();
 
+    // Always set the TPRO offset, but the count will only be set in the main cache
+    dyldCacheHeader->tproMappingsOffset = (uint32_t)(dyldCacheHeader->subCacheArrayOffset + sizeof(dyld_subcache_entry) * dyldCacheHeader->subCacheArrayCount);
+    if ( this->isMainCache() )
+        dyldCacheHeader->tproMappingsCount = numTPRORegions(config, this, this->subCaches);
+
     // calculate start of text image array and trailing string pool
     auto*    textImages   = (dyld_cache_image_text_info*)((uint8_t*)dyldCacheHeader + dyldCacheHeader->imagesTextOffset);
-    uint32_t stringOffset = (uint32_t)(dyldCacheHeader->subCacheArrayOffset + sizeof(dyld_subcache_entry) * dyldCacheHeader->subCacheArrayCount);
+    uint32_t stringOffset = (uint32_t)(dyldCacheHeader->tproMappingsOffset + sizeof(dyld_cache_tpro_mapping_info) * dyldCacheHeader->tproMappingsCount);
 
     // write text image array and image names pool at same time
     for ( const CacheDylib& cacheDylib : cacheDylibs ) {
@@ -1794,6 +1978,9 @@
     __block MachOFile::ChainedFixupPointerOnDisk* lastFixup = nullptr;
     __block uint64_t lastPageIndex = ~0ULL;
     for ( Chunk* chunk : region.chunks ) {
+        if ( chunk->isAlignChunk() )
+            continue;
+
         SlidChunk* slidChunk = chunk->isSlidChunk();
 
         slidChunk->tracker.forEachFixup(^(void *loc, bool& stop) {
@@ -1970,6 +2157,110 @@
     return Error();
 }
 
+Error SubCache::computeSlideInfoV5(const BuilderConfig&           config,
+                                   cache_builder::SlideInfoChunk* slideChunk,
+                                   Region&                        region)
+{
+    __block Diagnostics diag;
+
+    bool canContainAuthPointers = region.canContainAuthPointers();
+
+    assert((region.subCacheVMSize.rawValue() % config.slideInfo.slideInfoPageSize) == 0);
+    dyld_cache_slide_info5* info    = (dyld_cache_slide_info5*)slideChunk->subCacheBuffer;
+    info->version                   = 5;
+    info->page_size                 = config.slideInfo.slideInfoPageSize;
+    info->page_starts_count         = (uint32_t)region.subCacheVMSize.rawValue() / config.slideInfo.slideInfoPageSize;
+    info->value_add                 = config.layout.cacheBaseAddress.rawValue();
+
+    assert((sizeof(dyld_cache_slide_info5) + (info->page_starts_count * sizeof(uint16_t))) <= slideChunk->cacheVMSize.rawValue());
+
+    std::fill(&info->page_starts[0], &info->page_starts[info->page_starts_count], DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE);
+
+    // Walk each fixup in each segment.  Every time we cross a page, add a page start
+    __block dyld_cache_slide_pointer5* lastFixup     = nullptr;
+    __block uint64_t                   lastPageIndex = ~0ULL;
+    for ( Chunk* chunk : region.chunks ) {
+        SlidChunk* slidChunk = chunk->isSlidChunk();
+
+        slidChunk->tracker.forEachFixup(^(void *loc, bool& stop) {
+            // V5 fixups must be 8-byte aligned
+            assert(((uint64_t)loc % 8) == 0);
+
+            VMOffset       vmOffsetInSegment((uint64_t)loc - (uint64_t)slidChunk->subCacheBuffer);
+            CacheVMAddress fixupVMAddr = slidChunk->cacheVMAddress + vmOffsetInSegment;
+            uint64_t       pageIndex   = (fixupVMAddr - region.subCacheVMAddress).rawValue() / info->page_size;
+
+            // If we are on a new page, then start a new chain
+            if ( pageIndex != lastPageIndex ) {
+                uint64_t vmOffsetInPage      = fixupVMAddr.rawValue() % info->page_size;
+                info->page_starts[pageIndex] = vmOffsetInPage;
+            }
+            else {
+                // Patch the previous fixup on this page to point to this one
+                lastFixup->auth.next = ((uint64_t)loc - (uint64_t)lastFixup) / 8;
+            }
+
+            dyld_cache_slide_pointer5* fixupLocation = (dyld_cache_slide_pointer5*)loc;
+
+            // Convert this fixup from the chained format in the cache builder, to the version we want in the cache file
+            CacheVMAddress vmAddr = Fixup::Cache64::getCacheVMAddressFromLocation(config.layout.cacheBaseAddress,
+                                                                                  loc);
+
+            uint8_t high8 = Fixup::Cache64::getHigh8(loc);
+            VMOffset cacheVMOffset = vmAddr - config.layout.cacheBaseAddress;
+
+            uint16_t    authDiversity  = 0;
+            bool        authIsAddr     = false;
+            uint8_t     authKey        = 0;
+            if ( Fixup::Cache64::hasAuthData(loc, authDiversity, authIsAddr, authKey) ) {
+                // Authenticated value
+                assert(high8 == 0);
+                assert(canContainAuthPointers);
+                assert((authKey == 0) || (authKey == 2)); // IA (0) or DA (2)
+
+                fixupLocation->auth.runtimeOffset       = cacheVMOffset.rawValue();
+                fixupLocation->auth.diversity           = authDiversity;
+                fixupLocation->auth.addrDiv             = authIsAddr ? 1 : 0;
+                fixupLocation->auth.keyIsData           = (authKey == 2) ? 1 : 0;
+                fixupLocation->auth.next                = 0;
+                fixupLocation->auth.auth                = 1;
+
+                if ( fixupLocation->auth.runtimeOffset != cacheVMOffset.rawValue() ) {
+                    diag.error("cache offset 0x%llx exceeds v5 format", cacheVMOffset.rawValue());
+                    stop = true;
+                    return;
+                }
+            } else {
+                // Unauthenticated value
+                fixupLocation->regular.runtimeOffset    = cacheVMOffset.rawValue();
+                fixupLocation->regular.high8            = high8;
+                fixupLocation->regular.next             = 0;
+                fixupLocation->regular.unused           = 0;
+                fixupLocation->regular.auth             = 0;
+
+                if ( fixupLocation->regular.runtimeOffset != cacheVMOffset.rawValue() ) {
+                    diag.error("cache offset 0x%llx exceeds v5 format", cacheVMOffset.rawValue());
+                    stop = true;
+                    return;
+                }
+            }
+
+            lastFixup     = fixupLocation;
+            lastPageIndex = pageIndex;
+        });
+        if ( diag.hasError() )
+            break;
+    }
+
+    if ( diag.hasError() )
+        return Error("could not build slide info because: %s", diag.errorMessageCStr());
+
+    // V5 doesn't deduplicate content like V1, so the used size is the original size too
+    slideChunk->usedFileSize = slideChunk->subCacheFileSize;
+
+    return Error();
+}
+
 Error SubCache::computeSlideInfoForRegion(const BuilderConfig&           config,
                                           cache_builder::SlideInfoChunk* slideChunk,
                                           Region&                        region)
@@ -1981,6 +2272,8 @@
             return computeSlideInfoV2(config, slideChunk, region);
         case cache_builder::SlideInfo::SlideInfoFormat::v3:
             return computeSlideInfoV3(config, slideChunk, region);
+        case cache_builder::SlideInfo::SlideInfoFormat::v5:
+            return computeSlideInfoV5(config, slideChunk, region);
     }
 
     return Error();
@@ -1991,8 +2284,10 @@
     Diagnostics diag;
 
     for ( Chunk* chunk : region.chunks ) {
+        if ( chunk->isAlignChunk() )
+            continue;
+
         SlidChunk* slidChunk = chunk->isSlidChunk();
-
         slidChunk->tracker.forEachFixup(^(void *loc, bool& stop) {
             if ( config.layout.is64 ) {
                 CacheVMAddress vmAddr = Fixup::Cache64::getCacheVMAddressFromLocation(config.layout.cacheBaseAddress, loc);
@@ -2019,6 +2314,12 @@
             case Region::Kind::text:
                 // No slide info for text
                 break;
+            case Region::Kind::tproConst:
+                if ( config.slideInfo.slideInfoFormat.has_value() )
+                    err = computeSlideInfoForRegion(config, this->tproConstSlideInfo.get(), region);
+                else
+                    err = convertChainsToVMAddresses(config, region);
+                break;
             case Region::Kind::data:
                 if ( config.slideInfo.slideInfoFormat.has_value() )
                     err = computeSlideInfoForRegion(config, this->dataSlideInfo.get(), region);
@@ -2031,6 +2332,12 @@
                 else
                     err = convertChainsToVMAddresses(config, region);
                 break;
+            case Region::Kind::tproAuthConst:
+                if ( config.slideInfo.slideInfoFormat.has_value() )
+                    err = computeSlideInfoForRegion(config, this->tproAuthConstSlideInfo.get(), region);
+                else
+                    err = convertChainsToVMAddresses(config, region);
+                break;
             case Region::Kind::auth:
                 if ( config.slideInfo.slideInfoFormat.has_value() )
                     err = computeSlideInfoForRegion(config, this->authSlideInfo.get(), region);
@@ -2043,8 +2350,9 @@
                 else
                     err = convertChainsToVMAddresses(config, region);
                 break;
+            case Region::Kind::readOnly:
             case Region::Kind::linkedit:
-                // No slide info for text
+                // No slide info for linkedit
                 break;
             case Region::Kind::unmapped:
             case Region::Kind::dynamicConfig: