Loading...
--- /dev/null
+++ dyld/dyld-1241.17/cache_builder/SubCache.cpp
@@ -0,0 +1,2387 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
+*
+* Copyright (c) 2017 Apple Inc. All rights reserved.
+*
+* @APPLE_LICENSE_HEADER_START@
+*
+* This file contains Original Code and/or Modifications of Original Code
+* as defined in and that are subject to the Apple Public Source License
+* Version 2.0 (the 'License'). You may not use this file except in
+* compliance with the License. Please obtain a copy of the License at
+* http://www.opensource.apple.com/apsl/ and read it before using this
+* file.
+*
+* The Original Code and all software distributed under the License are
+* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+* Please see the License for the specific language governing rights and
+* limitations under the License.
+*
+* @APPLE_LICENSE_HEADER_END@
+*/
+
+#include "BuilderConfig.h"
+#include "BuilderOptions.h"
+#include "CacheDylib.h"
+#include "Chunk.h"
+#include "CodeSigningTypes.h"
+#include "DyldSharedCache.h"
+#include "SubCache.h"
+#include "JSONWriter.h"
+
+#include "dyld_cache_format.h"
+
+#include <CommonCrypto/CommonHMAC.h>
+#include <CommonCrypto/CommonDigest.h>
+#include <CommonCrypto/CommonDigestSPI.h>
+
+using dyld3::GradedArchs;
+using dyld3::MachOFile;
+
+using error::Error;
+
+using namespace cache_builder;
+
+static inline uint64_t alignPage(uint64_t value)
+{
+ // Align to 16KB even on x86_64. That makes it easier for arm64 machines to map in the cache.
+ const uint64_t MinRegionAlignment = 0x4000;
+
+ return ((value + MinRegionAlignment - 1) & (-MinRegionAlignment));
+}
+
+// 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::readOnly:
+ 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;
+ switch ( this->kind ) {
+ case Region::Kind::text:
+ // 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
+ assert(0);
+ break;
+ case Region::Kind::unmapped:
+ case Region::Kind::codeSignature:
+ // We should never ask this region if it has auth content
+ assert(0);
+ break;
+ case Region::Kind::numKinds:
+ assert(0);
+ break;
+ }
+
+ return result;
+}
+
+// Returns true if the given Region should be saved as a Mapping in the shared cache
+bool Region::needsSharedCacheMapping() const
+{
+ 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:
+ case Region::Kind::dynamicConfig:
+ case Region::Kind::codeSignature:
+ return false;
+ case Region::Kind::numKinds:
+ assert(0);
+ }
+}
+
+// Returns true if the given Region has content that requires reserved address space
+bool Region::needsSharedCacheReserveAddressSpace() const
+{
+ 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;
+ case Region::Kind::unmapped:
+ case Region::Kind::codeSignature:
+ return false;
+ case Region::Kind::numKinds:
+ assert(0);
+ }
+}
+
+// Returns true if we need 32MB of padding between regions. This benefits page tables
+// where we don't want something like TEXT and DATA on the same pages, as then that needs extra
+// page table entries.
+bool Region::needsRegionPadding(const Region& next) const
+{
+ if ( !this->needsSharedCacheMapping() || !next.needsSharedCacheMapping() )
+ return false;
+
+ switch ( this->kind ) {
+ case Region::Kind::text: {
+ // Add padding if TEXT is adjacent to something that is mutable,
+ // ie, next to DATA/etc.
+ // Note we want TEXT to be adjacent to DATA_CONST as we don't expect DATA_CONST to change
+ 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
+ bool nextIsRO = (next.initProt() & VM_PROT_WRITE) == 0;
+ return nextIsRO;
+ }
+ 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,
+ // ie, next to DATA/DATA_CONST/etc
+ bool nextIsRW = (next.maxProt() & VM_PROT_WRITE) != 0;
+ return nextIsRW;
+ }
+ case Region::Kind::unmapped:
+ case Region::Kind::codeSignature:
+ return false;
+ case Region::Kind::numKinds:
+ assert(0);
+ }
+}
+
+
+
+// MARK: --- SubCache methods ---
+
+SubCache::SubCache(Kind kind)
+ : kind(kind)
+{
+ memset(&this->uuidString[0], '\0', sizeof(this->uuidString));
+
+ // Start every subCache with all the regions it might need
+ uint32_t maxSize = (uint32_t)Region::Kind::numKinds;
+ this->regions.reserve(maxSize);
+ for ( uint32_t i = 0; i != (uint32_t)Region::Kind::numKinds; ++i )
+ this->regions.emplace_back((Region::Kind)i);
+}
+
+SubCache SubCache::makeMainCache(const BuilderOptions& options, bool isDevelopment)
+{
+ Kind kind = isDevelopment ? Kind::mainDevelopment : Kind::mainCustomer;
+ SubCache subCache(kind);
+
+ // If we are a universal cache, then .development actually gets a suffix
+ if ( options.kind == CacheKind::universal ) {
+ subCache.fileSuffix = isDevelopment ? ".development" : "";
+ } else {
+ subCache.fileSuffix = "";
+ }
+
+ return subCache;
+}
+
+SubCache SubCache::makeSubCache(const BuilderOptions& options)
+{
+ SubCache subCache(Kind::subUniversal);
+
+ // We'll set this later, after subCaches have been split for universal caches
+ subCache.fileSuffix = "unset";
+
+ return subCache;
+}
+
+SubCache SubCache::makeStubsCache(const BuilderOptions& options, bool isDevelopment)
+{
+ Kind kind = isDevelopment ? Kind::stubsDevelopment : Kind::stubsCustomer;
+ SubCache subCache(kind);
+
+ // We'll set this later, after subCaches have been split for universal caches
+ subCache.fileSuffix = "unset";
+
+ return subCache;
+}
+
+SubCache SubCache::makeSymbolsCache()
+{
+ SubCache subCache(Kind::symbols);
+ subCache.fileSuffix = ".symbols";
+ return subCache;
+}
+
+static bool hasDataRegion(std::span<Region> regions)
+{
+ for ( const Region& region : regions ) {
+ if ( region.chunks.empty() )
+ continue;
+ 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:
+ case cache_builder::Region::Kind::codeSignature:
+ case cache_builder::Region::Kind::numKinds:
+ break;
+ }
+ }
+ return false;
+}
+
+static bool hasLinkeditRegion(std::span<Region> regions)
+{
+ for ( const Region& region : regions ) {
+ if ( region.chunks.empty() )
+ 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:
+ case cache_builder::Region::Kind::dynamicConfig:
+ case cache_builder::Region::Kind::codeSignature:
+ case cache_builder::Region::Kind::numKinds:
+ break;
+ }
+ }
+ return false;
+}
+
+void SubCache::setSuffix(dyld3::Platform platform, bool forceDevelopmentSubCacheSuffix,
+ size_t subCacheIndex)
+{
+ assert(this->isSubCache() || this->isStubsCache());
+ assert(subCacheIndex > 0);
+
+ const char* dataSuffix = forceDevelopmentSubCacheSuffix ? ".development.dylddata" : ".dylddata";
+ const char* linkeditSuffix = forceDevelopmentSubCacheSuffix ? ".development.dyldlinkedit" : ".dyldlinkedit";
+ const char* subCacheSuffix = forceDevelopmentSubCacheSuffix ? ".development" : "";
+
+ if ( platform == dyld3::Platform::macOS ) {
+ // macOS never has a .development suffix
+ this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex);
+ } else if ( platform == dyld3::Platform::driverKit ) {
+ // driverKit never has a .development suffix
+ this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex);
+ } else if ( this->isStubsDevelopmentCache() ) {
+ // Dev stubs always have a suffix
+ this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex) + ".development";
+ } else if ( this->isStubsCustomerCache() ) {
+ // Customer stubs never have a suffix
+ this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex);
+ } else if ( hasDataRegion(this->regions) ) {
+ // Data only subcaches have their own suffix
+ this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex) + dataSuffix;
+ } else if ( hasLinkeditRegion(this->regions) ) {
+ // Linkedit only subcaches have their own suffix
+ this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex) + linkeditSuffix;
+ } else {
+ this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex) + subCacheSuffix;
+ }
+}
+
+static std::string getCodeSigningIdentifier(const BuilderOptions& options)
+{
+ std::string cacheIdentifier = "com.apple.dyld.cache.";
+ cacheIdentifier += options.archs.name();
+ if ( options.dylibsRemovedFromDisk ) {
+ switch ( options.kind ) {
+ case CacheKind::development:
+ cacheIdentifier += ".development";
+ break;
+ case CacheKind::universal:
+ cacheIdentifier += ".universal";
+ break;
+ }
+ }
+
+ return cacheIdentifier;
+}
+struct CodeSignatureLayout
+{
+ // These fields configure the code signature
+ bool agile = false;
+ uint8_t dscHashType = 0;
+ uint8_t dscHashSize = 0;
+ uint32_t dscDigestFormat = 0;
+
+ // These describe the layout of the signature
+ uint32_t blobCount = 0;
+ uint32_t slotCount = 0;
+ uint32_t xSlotCount = 0;
+ size_t idOffset = 0;
+ size_t hashOffset = 0;
+ size_t hash256Offset = 0;
+ size_t cdSize = 0;
+ size_t cd256Size = 0;
+ size_t reqsSize = 0;
+ size_t cmsSize = 0;
+ size_t cdOffset = 0;
+ size_t cd256Offset = 0;
+ size_t reqsOffset = 0;
+ size_t cmsOffset = 0;
+ size_t sbSize = 0;
+ size_t sigSize = 0;
+};
+
+static CodeSignatureLayout getCodeSignatureLayout(const BuilderOptions& options,
+ const BuilderConfig& config,
+ CacheFileSize subCacheSize)
+{
+ CodeSignatureLayout layout;
+
+ const uint32_t pageSize = config.codeSign.pageSize;
+ assert((subCacheSize.rawValue() % pageSize) == 0);
+
+ layout.agile = false;
+
+ // select which codesigning hash
+ switch ( config.codeSign.mode ) {
+ case CodeSign::Mode::agile:
+ layout.agile = true;
+ // Fall through to SHA1, because the main code directory remains SHA1 for compatibility.
+ [[clang::fallthrough]];
+ case CodeSign::Mode::onlySHA1:
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ layout.dscHashType = CS_HASHTYPE_SHA1;
+ layout.dscHashSize = CS_HASH_SIZE_SHA1;
+ layout.dscDigestFormat = kCCDigestSHA1;
+#pragma clang diagnostic pop
+ break;
+ case CodeSign::Mode::onlySHA256:
+ layout.dscHashType = CS_HASHTYPE_SHA256;
+ layout.dscHashSize = CS_HASH_SIZE_SHA256;
+ layout.dscDigestFormat = kCCDigestSHA256;
+ break;
+ }
+
+ std::string cacheIdentifier = getCodeSigningIdentifier(options);
+
+ // layout code signature contents
+ size_t idSize = cacheIdentifier.size()+1; // +1 for terminating 0
+ layout.blobCount = layout.agile ? 4 : 3;
+ layout.slotCount = (uint32_t)(subCacheSize.rawValue() / pageSize);
+ layout.xSlotCount = CSSLOT_REQUIREMENTS;
+ layout.idOffset = offsetof(CS_CodeDirectory, end_withExecSeg);
+ layout.hashOffset = layout.idOffset + idSize + layout.dscHashSize * layout.xSlotCount;
+ layout.hash256Offset = layout.idOffset + idSize + CS_HASH_SIZE_SHA256 * layout.xSlotCount;
+ layout.cdSize = layout.hashOffset + (layout.slotCount * layout.dscHashSize);
+ layout.cd256Size = layout.agile ? layout.hash256Offset + (layout.slotCount * CS_HASH_SIZE_SHA256) : 0;
+ layout.reqsSize = 12;
+ layout.cmsSize = sizeof(CS_Blob);
+ layout.cdOffset = sizeof(CS_SuperBlob) + layout.blobCount * sizeof(CS_BlobIndex);
+ layout.cd256Offset = layout.cdOffset + layout.cdSize;
+ layout.reqsOffset = layout.cd256Offset + layout.cd256Size; // equals cdOffset + cdSize if not agile
+ layout.cmsOffset = layout.reqsOffset + layout.reqsSize;
+ layout.sbSize = layout.cmsOffset + layout.cmsSize;
+ layout.sigSize = alignPage(layout.sbSize); // keep whole cache 16KB aligned
+
+ return layout;
+}
+
+void SubCache::setCodeSignatureSize(const BuilderOptions& options, const BuilderConfig& config,
+ CacheFileSize estimatedSize)
+{
+ CodeSignatureLayout estimatedLayout = getCodeSignatureLayout(options, config, estimatedSize);
+
+ this->codeSignature->cacheVMSize = CacheVMSize(0ULL);
+ this->codeSignature->subCacheFileSize = CacheFileSize((uint64_t)estimatedLayout.sigSize);
+}
+
+void SubCache::codeSign(Diagnostics& diag, const BuilderOptions& options, const BuilderConfig& config)
+{
+ CodeSignatureChunk& cacheCodeSignatureChunk = *this->codeSignature.get();
+ const uint32_t pageSize = config.codeSign.pageSize;
+
+ uint64_t subCacheBufferSize = 0;
+ for ( const Region& region : this->regions ) {
+ // Skip the code signature. We don't need to measure it as we are computing it
+ if ( region.kind == Region::Kind::codeSignature )
+ continue;
+
+ subCacheBufferSize = std::max(subCacheBufferSize, (region.subCacheFileOffset + region.subCacheFileSize).rawValue());
+ }
+
+ CodeSignatureLayout layout = getCodeSignatureLayout(options, config,
+ CacheFileSize(subCacheBufferSize));
+
+ if ( layout.sigSize > cacheCodeSignatureChunk.subCacheFileSize.rawValue() ) {
+ diag.error("Overflow in code signature size");
+ return;
+ }
+
+ // create overall code signature which is a superblob
+ CS_SuperBlob* sb = reinterpret_cast<CS_SuperBlob*>(cacheCodeSignatureChunk.subCacheBuffer);
+ sb->magic = htonl(CSMAGIC_EMBEDDED_SIGNATURE);
+ sb->length = htonl(layout.sbSize);
+ sb->count = htonl(layout.blobCount);
+ sb->index[0].type = htonl(CSSLOT_CODEDIRECTORY);
+ sb->index[0].offset = htonl(layout.cdOffset);
+ sb->index[1].type = htonl(CSSLOT_REQUIREMENTS);
+ sb->index[1].offset = htonl(layout.reqsOffset);
+ sb->index[2].type = htonl(CSSLOT_CMS_SIGNATURE);
+ sb->index[2].offset = htonl(layout.cmsOffset);
+ if ( layout.agile ) {
+ sb->index[3].type = htonl(CSSLOT_ALTERNATE_CODEDIRECTORIES + 0);
+ sb->index[3].offset = htonl(layout.cd256Offset);
+ }
+
+ // fill in empty requirements
+ CS_RequirementsBlob* reqs = (CS_RequirementsBlob*)(((char*)sb) + layout.reqsOffset);
+ reqs->magic = htonl(CSMAGIC_REQUIREMENTS);
+ reqs->length = htonl(sizeof(CS_RequirementsBlob));
+ reqs->data = 0;
+
+ // initialize fixed fields of Code Directory
+ CS_CodeDirectory* cd = (CS_CodeDirectory*)(((char*)sb) + layout.cdOffset);
+ cd->magic = htonl(CSMAGIC_CODEDIRECTORY);
+ cd->length = htonl(layout.cdSize);
+ cd->version = htonl(0x20400); // supports exec segment
+ cd->flags = htonl(kSecCodeSignatureAdhoc);
+ cd->hashOffset = htonl(layout.hashOffset);
+ cd->identOffset = htonl(layout.idOffset);
+ cd->nSpecialSlots = htonl(layout.xSlotCount);
+ cd->nCodeSlots = htonl(layout.slotCount);
+ cd->codeLimit = htonl(subCacheBufferSize);
+ cd->hashSize = layout.dscHashSize;
+ cd->hashType = layout.dscHashType;
+ cd->platform = 0; // not platform binary
+ cd->pageSize = __builtin_ctz(pageSize); // log2(CS_PAGE_SIZE);
+ cd->spare2 = 0; // unused (must be zero)
+ cd->scatterOffset = 0; // not supported anymore
+ cd->teamOffset = 0; // no team ID
+ cd->spare3 = 0; // unused (must be zero)
+ cd->codeLimit64 = 0; // falls back to codeLimit
+
+ // executable segment info
+ cd->execSegBase = 0;
+ cd->execSegLimit = 0;
+ cd->execSegFlags = 0; // not a main binary
+
+ for ( const Region& region : this->regions ) {
+ if ( region.kind == Region::Kind::text ) {
+ cd->execSegBase = htonll(region.subCacheFileOffset.rawValue()); // base of TEXT segment
+ cd->execSegLimit = htonll(region.subCacheFileSize.rawValue()); // size of TEXT segment
+ }
+ }
+
+ std::string cacheIdentifier = getCodeSigningIdentifier(options);
+
+ // initialize dynamic fields of Code Directory
+ strcpy((char*)cd + layout.idOffset, cacheIdentifier.c_str());
+
+ // add special slot hashes
+ uint8_t* hashSlot = (uint8_t*)cd + layout.hashOffset;
+ uint8_t* reqsHashSlot = &hashSlot[-CSSLOT_REQUIREMENTS * layout.dscHashSize];
+ CCDigest(layout.dscDigestFormat, (uint8_t*)reqs, sizeof(CS_RequirementsBlob), reqsHashSlot);
+
+ CS_CodeDirectory* cd256;
+ uint8_t* hash256Slot;
+ uint8_t* reqsHash256Slot;
+ if ( layout.agile ) {
+ // Note that the assumption here is that the size up to the hashes is the same as for
+ // sha1 code directory, and that they come last, after everything else.
+
+ cd256 = (CS_CodeDirectory*)(((char*)sb) + layout.cd256Offset);
+ cd256->magic = htonl(CSMAGIC_CODEDIRECTORY);
+ cd256->length = htonl(layout.cd256Size);
+ cd256->version = htonl(0x20400); // supports exec segment
+ cd256->flags = htonl(kSecCodeSignatureAdhoc);
+ cd256->hashOffset = htonl(layout.hash256Offset);
+ cd256->identOffset = htonl(layout.idOffset);
+ cd256->nSpecialSlots = htonl(layout.xSlotCount);
+ cd256->nCodeSlots = htonl(layout.slotCount);
+ cd256->codeLimit = htonl(subCacheBufferSize);
+ cd256->hashSize = CS_HASH_SIZE_SHA256;
+ cd256->hashType = CS_HASHTYPE_SHA256;
+ cd256->platform = 0; // not platform binary
+ cd256->pageSize = __builtin_ctz(pageSize); // log2(CS_PAGE_SIZE);
+ cd256->spare2 = 0; // unused (must be zero)
+ cd256->scatterOffset = 0; // not supported anymore
+ cd256->teamOffset = 0; // no team ID
+ cd256->spare3 = 0; // unused (must be zero)
+ cd256->codeLimit64 = 0; // falls back to codeLimit
+
+ // executable segment info
+ cd256->execSegBase = cd->execSegBase;
+ cd256->execSegLimit = cd->execSegLimit;
+ cd256->execSegFlags = cd->execSegFlags;
+
+ // initialize dynamic fields of Code Directory
+ strcpy((char*)cd256 + layout.idOffset, cacheIdentifier.c_str());
+
+ // add special slot hashes
+ hash256Slot = (uint8_t*)cd256 + layout.hash256Offset;
+ reqsHash256Slot = &hash256Slot[-CSSLOT_REQUIREMENTS * CS_HASH_SIZE_SHA256];
+ CCDigest(kCCDigestSHA256, (uint8_t*)reqs, sizeof(CS_RequirementsBlob), reqsHash256Slot);
+ }
+ else {
+ cd256 = NULL;
+ hash256Slot = NULL;
+ reqsHash256Slot = NULL;
+ }
+
+ // fill in empty CMS blob for ad-hoc signing
+ CS_Blob* cms = (CS_Blob*)(((char*)sb) + layout.cmsOffset);
+ cms->magic = htonl(CSMAGIC_BLOBWRAPPER);
+ cms->length = htonl(sizeof(CS_Blob));
+
+ // alter header of cache to record size and location of code signature
+ // do this *before* hashing each page
+ Chunk& cacheHeaderChunk = *this->cacheHeader.get();
+ dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)cacheHeaderChunk.subCacheBuffer;
+ dyldCacheHeader->codeSignatureOffset = cacheCodeSignatureChunk.subCacheFileOffset.rawValue();
+ dyldCacheHeader->codeSignatureSize = cacheCodeSignatureChunk.subCacheFileSize.rawValue();
+
+ auto codeSignPage = ^(size_t pageIndex) {
+ const uint8_t* code = this->buffer + (pageIndex * pageSize);
+
+ CCDigest(layout.dscDigestFormat, code, pageSize, hashSlot + (pageIndex * layout.dscHashSize));
+
+ if ( layout.agile ) {
+ CCDigest(kCCDigestSHA256, code, pageSize, hash256Slot + (pageIndex * CS_HASH_SIZE_SHA256));
+ }
+ };
+
+ // compute hashes
+ dispatch_apply(layout.slotCount, DISPATCH_APPLY_AUTO, ^(size_t i) {
+ codeSignPage(i);
+ });
+
+ // Now that we have a code signature, compute a cache UUID by hashing the code signature blob
+ {
+ uint8_t* uuidLoc = dyldCacheHeader->uuid;
+ assert(uuid_is_null(uuidLoc));
+ static_assert(offsetof(dyld_cache_header, uuid) / CS_PAGE_SIZE_4K == 0, "uuid is expected in the first page of the cache");
+ uint8_t fullDigest[CC_SHA256_DIGEST_LENGTH];
+ CC_SHA256((const void*)cd, (unsigned)layout.cdSize, fullDigest);
+ memcpy(uuidLoc, fullDigest, 16);
+ // <rdar://problem/6723729> uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats
+ uuidLoc[6] = (uuidLoc[6] & 0x0F) | (3 << 4);
+ uuidLoc[8] = (uuidLoc[8] & 0x3F) | 0x80;
+
+ // Now codesign page 0 again, because we modified it by setting uuid in header
+ codeSignPage(0);
+ }
+
+ // hash of entire code directory (cdHash) uses same hash as each page
+ uint8_t fullCdHash[layout.dscHashSize];
+ CCDigest(layout.dscDigestFormat, (const uint8_t*)cd, layout.cdSize, fullCdHash);
+ // Note: cdHash is defined as first 20 bytes of hash
+ memcpy(this->cdHash, fullCdHash, 20);
+
+ // Set the UUID string in the subcache
+ uuid_unparse_upper(dyldCacheHeader->uuid, this->uuidString);
+}
+
+void SubCache::addStubsChunk(Chunk* chunk)
+{
+ assert(chunk->isStubsChunk());
+ this->regions[(uint32_t)Region::Kind::text].chunks.push_back(chunk);
+}
+
+void SubCache::addTextChunk(Chunk* chunk)
+{
+ 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);
+}
+
+void SubCache::addDataConstChunk(Chunk* chunk)
+{
+ this->regions[(uint32_t)Region::Kind::dataConst].chunks.push_back(chunk);
+}
+
+void SubCache::addAuthChunk(Chunk* chunk)
+{
+ this->regions[(uint32_t)Region::Kind::auth].chunks.push_back(chunk);
+}
+
+void SubCache::addAuthConstChunk(Chunk* chunk)
+{
+ 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);
+}
+
+void SubCache::addUnmappedChunk(Chunk* chunk)
+{
+ this->regions[(uint32_t)Region::Kind::unmapped].chunks.push_back(chunk);
+}
+
+void SubCache::addCodeSignatureChunk(Chunk* chunk)
+{
+ this->regions[(uint32_t)Region::Kind::codeSignature].chunks.push_back(chunk);
+}
+
+// All objc optimizations need to be contiguous, but that means if any need AUTH then all do
+void SubCache::addObjCReadWriteChunk(const BuilderConfig& config, Chunk* chunk)
+{
+ if ( config.layout.hasAuthRegion ) {
+ addAuthChunk(chunk);
+ }
+ else {
+ addDataChunk(chunk);
+ }
+}
+
+void SubCache::addDylib(const BuilderConfig& config, CacheDylib& cacheDylib)
+{
+ for ( DylibSegmentChunk& segmentInfo : cacheDylib.segments ) {
+ switch ( segmentInfo.kind ) {
+ case Chunk::Kind::dylibText:
+ this->addTextChunk(&segmentInfo);
+ break;
+ case Chunk::Kind::tproDataConst:
+ this->addTPROConstChunk(config, &segmentInfo);
+ break;
+ case Chunk::Kind::dylibData:
+ this->addDataChunk(&segmentInfo);
+ break;
+ case Chunk::Kind::dylibDataConst:
+ this->addDataConstChunk(&segmentInfo);
+ break;
+ case Chunk::Kind::dylibDataDirty:
+ // On arm64e, dataDirty goes in to auth
+ if ( cacheDylib.inputMF->isArch("arm64e") )
+ this->addAuthChunk(&segmentInfo);
+ else
+ this->addDataChunk(&segmentInfo);
+ break;
+ case Chunk::Kind::dylibAuth:
+ this->addAuthChunk(&segmentInfo);
+ break;
+ case Chunk::Kind::dylibAuthConst:
+ this->addAuthConstChunk(&segmentInfo);
+ break;
+ case Chunk::Kind::dylibReadOnly:
+ this->addReadOnlyChunk(config, &segmentInfo);
+ break;
+ case Chunk::Kind::dylibLinkedit:
+ // Skip adding here. We'll do this in addLinkeditFromDylib()
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ }
+
+ this->addLinkeditFromDylib(cacheDylib);
+}
+
+// Linkedit is stored in Chunks in its own array on the dylib. This adds it to the subCache.
+void SubCache::addLinkeditFromDylib(CacheDylib& cacheDylib)
+{
+ for ( DylibSegmentChunk& segmentInfo : cacheDylib.segments ) {
+ if ( segmentInfo.kind == Chunk::Kind::dylibLinkedit )
+ this->addLinkeditChunk(&segmentInfo);
+ }
+ for ( LinkeditDataChunk& chunk : cacheDylib.linkeditChunks )
+ this->addLinkeditChunk(&chunk);
+}
+
+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 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() ) {
+ startOffset += sizeof(dyld_cache_image_info) * cacheDylibs.size();
+ startOffset += sizeof(dyld_cache_image_text_info) * cacheDylibs.size();
+ for ( const CacheDylib& cacheDylib : cacheDylibs ) {
+ startOffset += cacheDylib.installName.size() + 1;
+ }
+ }
+
+ //fprintf(stderr, "%s total header size = 0x%08lX\n", _options.archName.c_str(), startOffset);
+ startOffset = alignPage(startOffset);
+
+ this->cacheHeader = std::make_unique<CacheHeaderChunk>();
+ this->cacheHeader->cacheVMSize = CacheVMSize(startOffset);
+ this->cacheHeader->subCacheFileSize = CacheFileSize(startOffset);
+
+ // Add this to the correct region
+ this->addTextChunk(this->cacheHeader.get());
+}
+
+void SubCache::addObjCHeaderInfoReadWriteChunk(const BuilderConfig& config, ObjCOptimizer& objcOptimizer)
+{
+ this->objcHeaderInfoRW = std::make_unique<ObjCHeaderInfoReadWriteChunk>();
+ this->objcHeaderInfoRW->cacheVMSize = CacheVMSize(objcOptimizer.headerInfoReadWriteByteSize);
+ this->objcHeaderInfoRW->subCacheFileSize = CacheFileSize(objcOptimizer.headerInfoReadWriteByteSize);
+
+ objcOptimizer.headerInfoReadWriteChunk = this->objcHeaderInfoRW.get();
+
+ addObjCReadWriteChunk(config, this->objcHeaderInfoRW.get());
+}
+
+void SubCache::addSlideInfoChunks()
+{
+ // We can't compute the size yet. Due to alignment, the size of the RW Region can grow after
+ // 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>();
+ addLinkeditChunk(this->dataSlideInfo.get());
+ }
+
+ // DATA_CONST
+ if ( !this->regions[(uint32_t)Region::Kind::dataConst].chunks.empty() ) {
+ this->dataConstSlideInfo = std::make_unique<SlideInfoChunk>();
+ 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>();
+ addLinkeditChunk(this->authSlideInfo.get());
+ }
+
+ // AUTH_CONST
+ if ( !this->regions[(uint32_t)Region::Kind::authConst].chunks.empty() ) {
+ this->authConstSlideInfo = std::make_unique<SlideInfoChunk>();
+ addLinkeditChunk(this->authConstSlideInfo.get());
+ }
+}
+
+void SubCache::addCodeSignatureChunk()
+{
+ // We can't compute the size yet. Due to alignment, the size of the Region's can grow after
+ // we sort the Section's. For now just add a placeholder and we'll update the size in calculateCodeSignatureSize()
+ this->codeSignature = std::make_unique<CodeSignatureChunk>();
+
+ addCodeSignatureChunk(this->codeSignature.get());
+}
+
+void SubCache::addObjCOptsHeaderChunk(ObjCOptimizer& objcOptimizer)
+{
+ this->objcOptsHeader = std::make_unique<ObjCOptsHeaderChunk>();
+ this->objcOptsHeader->cacheVMSize = CacheVMSize(objcOptimizer.optsHeaderByteSize);
+ this->objcOptsHeader->subCacheFileSize = CacheFileSize(objcOptimizer.optsHeaderByteSize);
+
+ objcOptimizer.optsHeaderChunk = this->objcOptsHeader.get();
+
+ this->addLinkeditChunk(this->objcOptsHeader.get());
+}
+
+void SubCache::addObjCHeaderInfoReadOnlyChunk(const BuilderConfig& config, ObjCOptimizer& objcOptimizer)
+{
+ this->objcHeaderInfoRO = std::make_unique<ObjCHeaderInfoReadOnlyChunk>();
+ this->objcHeaderInfoRO->cacheVMSize = CacheVMSize(objcOptimizer.headerInfoReadOnlyByteSize);
+ this->objcHeaderInfoRO->subCacheFileSize = CacheFileSize(objcOptimizer.headerInfoReadOnlyByteSize);
+
+ objcOptimizer.headerInfoReadOnlyChunk = this->objcHeaderInfoRO.get();
+
+ 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);
+ this->objcImageInfo->subCacheFileSize = CacheFileSize(objcOptimizer.imageInfoSize);
+
+ objcOptimizer.imageInfoChunk = this->objcImageInfo.get();
+
+ 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);
+ this->objcSelectorStrings->subCacheFileSize = CacheFileSize(objCSelectorOptimizer.selectorStringsTotalByteSize);
+
+ objCSelectorOptimizer.selectorStringsChunk = this->objcSelectorStrings.get();
+
+ 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);
+ this->objcSelectorsHashTable->subCacheFileSize = CacheFileSize(objCSelectorOptimizer.selectorHashTableTotalByteSize);
+
+ objCSelectorOptimizer.selectorHashTableChunk = this->objcSelectorsHashTable.get();
+
+ 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);
+ this->objcClassNameStrings->subCacheFileSize = CacheFileSize(objcClassOptimizer.nameStringsTotalByteSize);
+
+ objcClassOptimizer.classNameStringsChunk = this->objcClassNameStrings.get();
+
+ 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);
+ this->objcClassesHashTable->subCacheFileSize = CacheFileSize(objcClassOptimizer.classHashTableTotalByteSize);
+
+ objcClassOptimizer.classHashTableChunk = this->objcClassesHashTable.get();
+
+ 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);
+ this->objcProtocolNameStrings->subCacheFileSize = CacheFileSize(objcProtocolOptimizer.nameStringsTotalByteSize);
+
+ objcProtocolOptimizer.protocolNameStringsChunk = this->objcProtocolNameStrings.get();
+
+ 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);
+ this->objcProtocolsHashTable->subCacheFileSize = CacheFileSize(objcProtocolOptimizer.protocolHashTableTotalByteSize);
+
+ objcProtocolOptimizer.protocolHashTableChunk = this->objcProtocolsHashTable.get();
+
+ 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);
+ this->objcSwiftDemangledNameStrings->subCacheFileSize = CacheFileSize(objcProtocolOptimizer.swiftDemangledNameStringsTotalByteSize);
+
+ objcProtocolOptimizer.swiftDemangledNameStringsChunk = this->objcSwiftDemangledNameStrings.get();
+
+ this->addReadOnlyChunk(config, this->objcSwiftDemangledNameStrings.get());
+}
+
+void SubCache::addObjCCanonicalProtocolsChunk(const BuilderConfig& config,
+ ObjCProtocolOptimizer& objcProtocolOptimizer)
+{
+ this->objcCanonicalProtocols = std::make_unique<ObjCCanonicalProtocolsChunk>();
+ this->objcCanonicalProtocols->cacheVMSize = CacheVMSize(objcProtocolOptimizer.canonicalProtocolsTotalByteSize);
+ this->objcCanonicalProtocols->subCacheFileSize = CacheFileSize(objcProtocolOptimizer.canonicalProtocolsTotalByteSize);
+
+ objcProtocolOptimizer.canonicalProtocolsChunk = this->objcCanonicalProtocols.get();
+
+ // Add canonical objc protocols
+ addObjCReadWriteChunk(config, this->objcCanonicalProtocols.get());
+}
+
+void SubCache::addObjCIMPCachesChunk(ObjCIMPCachesOptimizer& objcIMPCachesOptimizer)
+{
+ this->objcIMPCaches = std::make_unique<ObjCIMPCachesChunk>();
+ this->objcIMPCaches->cacheVMSize = CacheVMSize(objcIMPCachesOptimizer.impCachesTotalByteSize);
+ this->objcIMPCaches->subCacheFileSize = CacheFileSize(objcIMPCachesOptimizer.impCachesTotalByteSize);
+
+ objcIMPCachesOptimizer.impCachesChunk = this->objcIMPCaches.get();
+
+ this->addLinkeditChunk(this->objcIMPCaches.get());
+}
+
+void SubCache::addObjCCategoriesChunk(const BuilderConfig& config,
+ ObjCCategoryOptimizer& objcCategoryOptimizer)
+{
+ this->objcCategories = std::make_unique<ObjCPreAttachedCategoriesChunk>();
+ this->objcCategories->cacheVMSize = CacheVMSize(objcCategoryOptimizer.categoriesTotalByteSize);
+ this->objcCategories->subCacheFileSize = CacheFileSize(objcCategoryOptimizer.categoriesTotalByteSize);
+
+ objcCategoryOptimizer.categoriesChunk = this->objcCategories.get();
+
+ // Add objc categories
+ addReadOnlyChunk(config, this->objcCategories.get());
+}
+
+void SubCache::addCacheTrieChunk(DylibTrieOptimizer& dylibTrieOptimizer)
+{
+ this->cacheDylibsTrie = std::make_unique<CacheTrieChunk>(Chunk::Kind::cacheDylibsTrie);
+ this->cacheDylibsTrie->cacheVMSize = CacheVMSize((uint64_t)dylibTrieOptimizer.dylibsTrie.size());
+ this->cacheDylibsTrie->subCacheFileSize = CacheFileSize((uint64_t)dylibTrieOptimizer.dylibsTrie.size());
+
+ dylibTrieOptimizer.dylibsTrieChunk = this->cacheDylibsTrie.get();
+
+ this->addLinkeditChunk(this->cacheDylibsTrie.get());
+}
+
+void SubCache::addPatchTableChunk(PatchTableOptimizer& patchTableOptimizer)
+{
+ // We can't compute the size yet. We need to know how many fixups we have
+ // And yet we have an estimate, so we'll use it
+
+ this->patchTable = std::make_unique<PatchTableChunk>();
+ this->patchTable->cacheVMSize = CacheVMSize(patchTableOptimizer.patchTableTotalByteSize);
+ this->patchTable->subCacheFileSize = CacheFileSize(patchTableOptimizer.patchTableTotalByteSize);
+
+ patchTableOptimizer.patchTableChunk = this->patchTable.get();
+
+ this->addLinkeditChunk(this->patchTable.get());
+}
+
+void SubCache::addCacheDylibsLoaderChunk(PrebuiltLoaderBuilder& builder)
+{
+ // We can't compute the size yet.
+ // And yet we have an estimate, so we'll use it
+
+ this->cacheDylibsLoaders = std::make_unique<PrebuiltLoaderChunk>(Chunk::Kind::dylibPrebuiltLoaders);
+ this->cacheDylibsLoaders->cacheVMSize = CacheVMSize(builder.cacheDylibsLoaderSize);
+ this->cacheDylibsLoaders->subCacheFileSize = CacheFileSize(builder.cacheDylibsLoaderSize);
+
+ builder.cacheDylibsLoaderChunk = this->cacheDylibsLoaders.get();
+
+ this->addLinkeditChunk(this->cacheDylibsLoaders.get());
+}
+
+void SubCache::addExecutableLoaderChunk(PrebuiltLoaderBuilder& builder)
+{
+ // We can't compute the size yet.
+ // And yet we have an estimate, so we'll use it
+
+ this->executableLoaders = std::make_unique<PrebuiltLoaderChunk>(Chunk::Kind::executablePrebuiltLoaders);
+ this->executableLoaders->cacheVMSize = CacheVMSize(builder.executablesLoaderSize);
+ this->executableLoaders->subCacheFileSize = CacheFileSize(builder.executablesLoaderSize);
+
+ builder.executablesLoaderChunk = this->executableLoaders.get();
+
+ this->addLinkeditChunk(this->executableLoaders.get());
+}
+
+void SubCache::addExecutablesTrieChunk(PrebuiltLoaderBuilder& builder)
+{
+ this->executablesTrie = std::make_unique<CacheTrieChunk>(Chunk::Kind::cacheExecutablesTrie);
+ this->executablesTrie->cacheVMSize = CacheVMSize(builder.executablesTrieSize);
+ this->executablesTrie->subCacheFileSize = CacheFileSize(builder.executablesTrieSize);
+
+ builder.executableTrieChunk = this->executablesTrie.get();
+
+ this->addLinkeditChunk(this->executablesTrie.get());
+}
+
+void SubCache::addSwiftOptsHeaderChunk(SwiftProtocolConformanceOptimizer& opt)
+{
+ this->swiftOptsHeader = std::make_unique<SwiftOptsHeaderChunk>();
+ this->swiftOptsHeader->cacheVMSize = CacheVMSize(opt.optsHeaderByteSize);
+ this->swiftOptsHeader->subCacheFileSize = CacheFileSize(opt.optsHeaderByteSize);
+
+ opt.optsHeaderChunk = this->swiftOptsHeader.get();
+
+ this->addLinkeditChunk(this->swiftOptsHeader.get());
+}
+
+void SubCache::addSwiftTypeHashTableChunk(SwiftProtocolConformanceOptimizer& opt)
+{
+ this->swiftTypeHashTable = std::make_unique<SwiftProtocolConformancesHashTableChunk>();
+ this->swiftTypeHashTable->cacheVMSize = CacheVMSize(opt.typeConformancesHashTableSize);
+ this->swiftTypeHashTable->subCacheFileSize = CacheFileSize(opt.typeConformancesHashTableSize);
+
+ opt.typeConformancesHashTable = this->swiftTypeHashTable.get();
+
+ this->addLinkeditChunk(this->swiftTypeHashTable.get());
+}
+
+void SubCache::addSwiftMetadataHashTableChunk(SwiftProtocolConformanceOptimizer& opt)
+{
+ this->swiftMetadataHashTable = std::make_unique<SwiftProtocolConformancesHashTableChunk>();
+ this->swiftMetadataHashTable->cacheVMSize = CacheVMSize(opt.metadataConformancesHashTableSize);
+ this->swiftMetadataHashTable->subCacheFileSize = CacheFileSize(opt.metadataConformancesHashTableSize);
+
+ opt.metadataConformancesHashTable = this->swiftMetadataHashTable.get();
+
+ this->addLinkeditChunk(this->swiftMetadataHashTable.get());
+}
+
+void SubCache::addSwiftForeignHashTableChunk(SwiftProtocolConformanceOptimizer& opt)
+{
+ this->swiftForeignTypeHashTable = std::make_unique<SwiftProtocolConformancesHashTableChunk>();
+ this->swiftForeignTypeHashTable->cacheVMSize = CacheVMSize(opt.foreignTypeConformancesHashTableSize);
+ this->swiftForeignTypeHashTable->subCacheFileSize = CacheFileSize(opt.foreignTypeConformancesHashTableSize);
+
+ 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)
+{
+ assert(this->kind == Kind::symbols);
+
+ // Add the unmapped symbol data
+ uint64_t unmappedSymbolsFileSize = 0;
+ unmappedSymbolsFileSize += sizeof(dyld_cache_local_symbols_info);
+ unmappedSymbolsFileSize += sizeof(dyld_cache_local_symbols_entry_64) * opt.symbolInfos.size();
+ opt.unmappedSymbolsChunk.cacheVMSize = CacheVMSize(0ULL);
+ opt.unmappedSymbolsChunk.subCacheFileSize = CacheFileSize(unmappedSymbolsFileSize);
+
+ uint64_t nlistFileSize = 0;
+ if ( config.layout.is64 )
+ nlistFileSize += sizeof(struct nlist_64) * opt.symbolNlistChunk.nlist64.size();
+ else
+ nlistFileSize += sizeof(struct nlist) * opt.symbolNlistChunk.nlist32.size();
+ opt.symbolNlistChunk.cacheVMSize = CacheVMSize(0ULL);
+ opt.symbolNlistChunk.subCacheFileSize = CacheFileSize(nlistFileSize);
+
+ uint64_t symbolStringsSize = opt.stringBufferSize;
+ opt.symbolStringsChunk.cacheVMSize = CacheVMSize(0ULL);
+ opt.symbolStringsChunk.subCacheFileSize = CacheFileSize(symbolStringsSize);
+
+ this->addUnmappedChunk(&opt.unmappedSymbolsChunk);
+ this->addUnmappedChunk(&opt.symbolNlistChunk);
+ this->addUnmappedChunk(&opt.symbolStringsChunk);
+}
+
+void SubCache::addDynamicConfigChunk()
+{
+ dynamicConfig = std::make_unique<DynamicConfigChunk>();
+ dynamicConfig->cacheVMSize = CacheVMSize(16_KB);
+ dynamicConfig->subCacheFileSize = CacheFileSize(0ULL);
+ this->regions[(uint32_t)Region::Kind::dynamicConfig].chunks.push_back(dynamicConfig.get());
+}
+
+// When building SubCache's, we start off with all the Regions. This removes any which didn't
+// get any content
+void SubCache::removeEmptyRegions()
+{
+ auto unusedRegion = [](const Region& region) {
+ return region.chunks.empty();
+ };
+ this->regions.erase(std::remove_if(this->regions.begin(), this->regions.end(),
+ unusedRegion), this->regions.end());
+}
+
+uint64_t SubCache::getCacheType(const BuilderOptions& options)
+{
+ switch ( options.kind ) {
+ case CacheKind::development:
+ return kDyldSharedCacheTypeDevelopment;
+ case CacheKind::universal:
+ return kDyldSharedCacheTypeUniversal;
+ }
+}
+
+uint32_t SubCache::getCacheSubType() const
+{
+ switch ( this->kind ) {
+ case Kind::mainDevelopment:
+ case Kind::stubsDevelopment:
+ return kDyldSharedCacheTypeDevelopment;
+ case Kind::mainCustomer:
+ case Kind::stubsCustomer:
+ return kDyldSharedCacheTypeProduction;
+ case Kind::subUniversal:
+ case Kind::symbols:
+ return kDyldSharedCacheTypeProduction;
+ }
+}
+
+void SubCache::writeCacheHeaderMappings()
+{
+ Chunk& cacheHeaderChunk = *this->cacheHeader.get();
+ dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)cacheHeaderChunk.subCacheBuffer;
+
+ assert(cacheHeaderChunk.subCacheFileOffset.rawValue() == 0);
+ auto* mappings = (dyld_cache_mapping_info*)((uint8_t*)dyldCacheHeader + dyldCacheHeader->mappingOffset);
+ auto* slidableMappings = (dyld_cache_mapping_and_slide_info*)((uint8_t*)dyldCacheHeader + dyldCacheHeader->mappingWithSlideOffset);
+
+ for ( const Region& region : this->regions ) {
+
+ // Skip Region's like the code signature which doesn't get a mapping
+ if ( !region.needsSharedCacheMapping() )
+ continue;
+
+ const uint32_t initProt = region.initProt();
+ const uint32_t maxProt = region.maxProt();
+ uint32_t flags = 0;
+ CacheFileOffset slideInfoFileOffset(0ULL);
+ CacheFileSize slideInfoFileSize(0ULL);
+
+ switch ( region.kind ) {
+ 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;
+
+ // Get the slide info
+ if ( this->dataSlideInfo ) {
+ slideInfoFileOffset = this->dataSlideInfo->subCacheFileOffset;
+ slideInfoFileSize = this->dataSlideInfo->usedFileSize;
+ }
+ break;
+ case Region::Kind::dataConst:
+ flags = DYLD_CACHE_MAPPING_CONST_DATA;
+
+ // Get the slide info
+ if ( this->dataConstSlideInfo ) {
+ slideInfoFileOffset = this->dataConstSlideInfo->subCacheFileOffset;
+ slideInfoFileSize = this->dataConstSlideInfo->usedFileSize;
+ }
+ break;
+ case Region::Kind::auth:
+ flags = DYLD_CACHE_MAPPING_AUTH_DATA;
+
+ // Get the slide info
+ if ( this->authSlideInfo ) {
+ slideInfoFileOffset = this->authSlideInfo->subCacheFileOffset;
+ slideInfoFileSize = this->authSlideInfo->usedFileSize;
+ }
+ break;
+ case Region::Kind::authConst:
+ flags = DYLD_CACHE_MAPPING_AUTH_DATA | DYLD_CACHE_MAPPING_CONST_DATA;
+
+ // Get the slide info
+ if ( this->authConstSlideInfo ) {
+ 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;
+ break;
+ case Region::Kind::dynamicConfig:
+ flags = DYLD_CACHE_DYNAMIC_CONFIG_DATA;
+ 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;
+ }
+
+ mappings->address = region.subCacheVMAddress.rawValue();
+ mappings->fileOffset = region.subCacheFileOffset.rawValue();
+ mappings->size = region.subCacheFileSize.rawValue();
+ mappings->maxProt = maxProt;
+ mappings->initProt = initProt;
+
+ slidableMappings->address = region.subCacheVMAddress.rawValue();
+ slidableMappings->fileOffset = region.subCacheFileOffset.rawValue();
+ slidableMappings->size = region.subCacheFileSize.rawValue();
+ slidableMappings->maxProt = maxProt;
+ slidableMappings->initProt = initProt;
+ slidableMappings->slideInfoFileOffset = slideInfoFileOffset.rawValue();
+ slidableMappings->slideInfoFileSize = slideInfoFileSize.rawValue();
+ slidableMappings->flags = flags;
+
+ ++mappings;
+ ++slidableMappings;
+ }
+}
+
+void SubCache::writeCacheHeader(const BuilderOptions& options, const BuilderConfig& config,
+ const std::span<CacheDylib> cacheDylibs)
+{
+ Chunk& cacheHeaderChunk = *this->cacheHeader.get();
+ dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)cacheHeaderChunk.subCacheBuffer;
+
+ // "dyld_v1" + spaces + archName(), with enough spaces to pad to 15 bytes
+ std::string magic = "dyld_v1";
+ magic.append(15 - magic.length() - strlen(options.archs.name()), ' ');
+ magic.append(options.archs.name());
+ assert(magic.length() == 15);
+
+ // Num of mappings depends on cache layout.
+ // For a regular/large cache its probably 1 each of TEXT, DATA, DATA_CONST, and LINKEDIT
+ // For a split cache, most files are a single mapping.
+ uint32_t mappingCount = 0;
+ for ( const Region& region : this->regions ) {
+ if ( region.needsSharedCacheMapping() )
+ ++mappingCount;
+ }
+ assert(mappingCount <= DyldSharedCache::MaxMappings);
+
+ // fill in header
+ memcpy(dyldCacheHeader->magic, magic.c_str(), 16);
+ dyldCacheHeader->mappingOffset = sizeof(dyld_cache_header);
+ dyldCacheHeader->mappingCount = mappingCount;
+ dyldCacheHeader->mappingWithSlideOffset = (uint32_t)(dyldCacheHeader->mappingOffset + mappingCount * sizeof(dyld_cache_mapping_and_slide_info));
+ dyldCacheHeader->mappingWithSlideCount = mappingCount;
+ dyldCacheHeader->imagesOffsetOld = 0; // no longer used
+ dyldCacheHeader->imagesCountOld = 0; // no longer used
+ dyldCacheHeader->imagesOffset = 0; // set later on all cache files
+ dyldCacheHeader->imagesCount = 0; // set later on all cache files
+ dyldCacheHeader->dyldBaseAddress = 0; // unused
+ dyldCacheHeader->codeSignatureOffset = 0; // set later on all cache files in codeSign()
+ dyldCacheHeader->codeSignatureSize = 0; // set later on all cache files in codeSign()
+ dyldCacheHeader->slideInfoOffsetUnused = 0; // no longer used
+ dyldCacheHeader->slideInfoSizeUnused = 0; // no longer used
+ dyldCacheHeader->localSymbolsOffset = 0;
+ dyldCacheHeader->localSymbolsSize = 0;
+ dyldCacheHeader->cacheType = getCacheType(options);
+ dyldCacheHeader->dyldInCacheMH = 0; // set later only on the main cache file
+ dyldCacheHeader->dyldInCacheEntry = 0; // set later only on the main cache file
+ bzero(dyldCacheHeader->uuid, 16); // overwritten later by recomputeCacheUUID()
+ dyldCacheHeader->branchPoolsOffset = 0; // no longer used
+ dyldCacheHeader->branchPoolsCount = 0; // no longer used
+ dyldCacheHeader->imagesTextOffset = 0;
+ dyldCacheHeader->imagesTextCount = 0;
+ dyldCacheHeader->patchInfoAddr = 0;
+ dyldCacheHeader->patchInfoSize = 0;
+ dyldCacheHeader->otherImageGroupAddrUnused = 0; // no longer used
+ dyldCacheHeader->otherImageGroupSizeUnused = 0; // no longer used
+ dyldCacheHeader->progClosuresAddr = 0; // no longer used
+ dyldCacheHeader->progClosuresSize = 0; // no longer used
+ dyldCacheHeader->progClosuresTrieAddr = 0; // no longer used
+ dyldCacheHeader->progClosuresTrieSize = 0; // no longer used
+ dyldCacheHeader->platform = (uint8_t)options.platform;
+ dyldCacheHeader->formatVersion = 0; //dyld3::closure::kFormatVersion;
+ dyldCacheHeader->dylibsExpectedOnDisk = !options.dylibsRemovedFromDisk;
+ dyldCacheHeader->simulator = options.isSimulator();
+ dyldCacheHeader->locallyBuiltCache = options.isLocallyBuiltCache;
+ dyldCacheHeader->builtFromChainedFixups = false; // no longer used
+ dyldCacheHeader->sharedRegionStart = this->subCacheVMAddress.rawValue();
+ dyldCacheHeader->sharedRegionSize = 0;
+ dyldCacheHeader->maxSlide = 0; // overwritten later in build if the cache supports ASLR
+ dyldCacheHeader->dylibsImageArrayAddr = 0; // no longer used
+ dyldCacheHeader->dylibsImageArraySize = 0; // no longer used
+ dyldCacheHeader->dylibsTrieAddr = 0; // set later only on the main cache file
+ dyldCacheHeader->dylibsTrieSize = 0; // set later only on the main cache file
+ dyldCacheHeader->otherImageArrayAddr = 0; // no longer used
+ dyldCacheHeader->otherImageArraySize = 0; // no longer used
+ dyldCacheHeader->otherTrieAddr = 0; // no longer used
+ dyldCacheHeader->otherTrieSize = 0; // no longer used
+ dyldCacheHeader->dylibsPBLStateArrayAddrUnused = 0; // no longer used
+ dyldCacheHeader->dylibsPBLSetAddr = 0; // set later only on the main cache file
+ dyldCacheHeader->programsPBLSetPoolAddr = 0; // set later only on the main cache file
+ dyldCacheHeader->programsPBLSetPoolSize = 0; // set later only on the main cache file
+ dyldCacheHeader->programTrieAddr = 0; // set later only on the main cache file
+ dyldCacheHeader->programTrieSize = 0; // set later only on the main cache file
+ dyldCacheHeader->osVersion = 0; // set later only on the main cache file
+ dyldCacheHeader->altPlatform = 0; // set later only on the main cache file
+ dyldCacheHeader->altOsVersion = 0; // set later only on the main cache file
+ dyldCacheHeader->swiftOptsOffset = 0; // set later only on the main cache file
+ dyldCacheHeader->swiftOptsSize = 0; // set later only on the main cache file
+ dyldCacheHeader->subCacheArrayOffset = 0;
+ dyldCacheHeader->subCacheArrayCount = 0;
+ bzero(dyldCacheHeader->symbolFileUUID, 16); // overwritten later after measuring the local symbols file
+ dyldCacheHeader->rosettaReadOnlyAddr = this->rosettaReadOnlyAddr;
+ dyldCacheHeader->rosettaReadOnlySize = this->rosettaReadOnlySize;
+ dyldCacheHeader->rosettaReadWriteAddr = this->rosettaReadWriteAddr;
+ dyldCacheHeader->rosettaReadWriteSize = this->rosettaReadWriteSize;
+ dyldCacheHeader->cacheSubType = getCacheSubType();
+ dyldCacheHeader->objcOptsOffset = 0; // set later only on the main cache file
+ dyldCacheHeader->objcOptsSize = 0; // set later only on the main cache file
+ dyldCacheHeader->cacheAtlasOffset = 0; // set later only on the main cache file
+ 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, config, cacheDylibs);
+}
+
+void SubCache::addMainCacheHeaderInfo(const BuilderOptions& options, const BuilderConfig& config,
+ const std::span<CacheDylib> cacheDylibs,
+ CacheVMSize totalVMSize, uint64_t maxSlide,
+ uint32_t osVersion, uint32_t altPlatform, uint32_t altOsVersion,
+ CacheVMAddress dyldInCacheUnslidAddr,
+ CacheVMAddress dyldInCacheEntryUnslidAddr,
+ const DylibTrieOptimizer& dylibTrieOptimizer,
+ const ObjCOptimizer& objcOptimizer,
+ const SwiftProtocolConformanceOptimizer& swiftProtocolConformanceOpt,
+ const PatchTableOptimizer& patchTableOptimizer,
+ const PrebuiltLoaderBuilder& prebuiltLoaderBuilder)
+{
+ const CacheVMAddress cacheBaseAddress = config.layout.cacheBaseAddress;
+
+ Chunk& cacheHeaderChunk = *this->cacheHeader.get();
+ dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)cacheHeaderChunk.subCacheBuffer;
+
+ // The first subCache knows the size of buffer to allocate to contain all other subCaches
+ dyldCacheHeader->sharedRegionSize = totalVMSize.rawValue();
+
+ dyldCacheHeader->dylibsTrieAddr = dylibTrieOptimizer.dylibsTrieChunk->cacheVMAddress.rawValue();
+ dyldCacheHeader->dylibsTrieSize = dylibTrieOptimizer.dylibsTrieChunk->subCacheFileSize.rawValue();
+
+ if ( !objcOptimizer.objcDylibs.empty() ) {
+ dyldCacheHeader->objcOptsOffset = (objcOptimizer.optsHeaderChunk->cacheVMAddress - cacheBaseAddress).rawValue();
+ dyldCacheHeader->objcOptsSize = objcOptimizer.optsHeaderChunk->subCacheFileSize.rawValue();
+ }
+
+ if ( !objcOptimizer.objcDylibs.empty() ) {
+ const auto& opt = swiftProtocolConformanceOpt;
+ dyldCacheHeader->swiftOptsOffset = (opt.optsHeaderChunk->cacheVMAddress - cacheBaseAddress).rawValue();
+ dyldCacheHeader->swiftOptsSize = opt.optsHeaderChunk->subCacheFileSize.rawValue();
+ }
+
+ dyldCacheHeader->patchInfoAddr = patchTableOptimizer.patchTableChunk->cacheVMAddress.rawValue();
+ dyldCacheHeader->patchInfoSize = patchTableOptimizer.patchTableChunk->subCacheFileSize.rawValue();
+
+ dyldCacheHeader->dylibsPBLSetAddr = prebuiltLoaderBuilder.cacheDylibsLoaderChunk->cacheVMAddress.rawValue();
+ dyldCacheHeader->programsPBLSetPoolAddr = prebuiltLoaderBuilder.executablesLoaderChunk->cacheVMAddress.rawValue();
+ dyldCacheHeader->programsPBLSetPoolSize = prebuiltLoaderBuilder.executablesLoaderChunk->subCacheFileSize.rawValue();
+ dyldCacheHeader->programTrieAddr = prebuiltLoaderBuilder.executableTrieChunk->cacheVMAddress.rawValue();
+ dyldCacheHeader->programTrieSize = (uint32_t)prebuiltLoaderBuilder.executableTrieChunk->subCacheFileSize.rawValue();
+
+ dyldCacheHeader->dyldInCacheMH = dyldInCacheUnslidAddr.rawValue();
+ dyldCacheHeader->dyldInCacheEntry = dyldInCacheEntryUnslidAddr.rawValue();
+
+ dyldCacheHeader->osVersion = osVersion;
+ dyldCacheHeader->altPlatform = altPlatform;
+ dyldCacheHeader->altOsVersion = altOsVersion;
+
+ // record max slide now that final size is established
+ dyldCacheHeader->maxSlide = maxSlide;
+
+ // TODO: Build the atlas
+ dyldCacheHeader->cacheAtlasOffset = 0; // set later only on the main cache file
+ dyldCacheHeader->cacheAtlasSize = 0; // set later only on the main cache file
+
+ // The main cache has offsets to all the caches
+ if ( !this->subCaches.empty() ) {
+ // The first subCache has an array of UUIDs for all other subCaches
+ // This should run after addCacheHeaderImageInfo(), which sets up te offsets
+ assert(dyldCacheHeader->subCacheArrayOffset != 0);
+ assert(dyldCacheHeader->subCacheArrayCount == this->subCaches.size());
+ auto* subCacheEntries = (dyld_subcache_entry*)((uint8_t*)dyldCacheHeader + dyldCacheHeader->subCacheArrayOffset);
+ for ( uint32_t index = 0; index != this->subCaches.size(); ++index ) {
+ const SubCache* subCache = this->subCaches[index];
+ subCacheEntries[index].cacheVMOffset = (subCache->subCacheVMAddress - cacheBaseAddress).rawValue();
+ strncpy(subCacheEntries[index].fileSuffix, subCache->fileSuffix.data(),
+ sizeof(dyld_subcache_entry::fileSuffix));
+ }
+ dyldCacheHeader->dynamicDataOffset = (this->subCaches.back()->dynamicConfig->cacheVMAddress - cacheBaseAddress).rawValue();
+ dyldCacheHeader->dynamicDataMaxSize = this->subCaches.back()->dynamicConfig->cacheVMSize.rawValue();
+ } else {
+ 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)
+{
+ Chunk& cacheHeaderChunk = *this->cacheHeader.get();
+ dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)cacheHeaderChunk.subCacheBuffer;
+
+ assert(kind == SubCache::Kind::symbols);
+
+ // In the symbols cache, fill in the offset to the symbol info
+ // FIXME: The implicit order of these chunks is not ideal
+ assert(optimizer.unmappedSymbolsChunk.kind < optimizer.symbolNlistChunk.kind);
+ assert(optimizer.symbolNlistChunk.kind < optimizer.symbolStringsChunk.kind);
+
+ CacheFileOffset start = optimizer.unmappedSymbolsChunk.subCacheFileOffset;
+ CacheFileOffset end = optimizer.symbolStringsChunk.subCacheFileOffset + optimizer.symbolStringsChunk.subCacheFileSize;
+ uint64_t size = end.rawValue() - start.rawValue();
+
+ dyldCacheHeader->localSymbolsOffset = start.rawValue();
+ dyldCacheHeader->localSymbolsSize = size;
+}
+
+void SubCache::addCacheHeaderImageInfo(const BuilderOptions& options,
+ const BuilderConfig& config,
+ const std::span<CacheDylib> cacheDylibs)
+{
+ if ( !this->needsCacheHeaderImageList() )
+ return;
+
+ Chunk& cacheHeaderChunk = *this->cacheHeader.get();
+ dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)cacheHeaderChunk.subCacheBuffer;
+
+ // Work out where everything will be in the header
+ dyldCacheHeader->imagesOffset = (uint32_t)(dyldCacheHeader->mappingWithSlideOffset + dyldCacheHeader->mappingWithSlideCount * sizeof(dyld_cache_mapping_and_slide_info));
+ dyldCacheHeader->imagesCount = (uint32_t)cacheDylibs.size();
+ dyldCacheHeader->imagesTextOffset = dyldCacheHeader->imagesOffset + sizeof(dyld_cache_image_info) * dyldCacheHeader->imagesCount;
+ dyldCacheHeader->imagesTextCount = cacheDylibs.size();
+
+ 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->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 ) {
+ cacheDylib.inputMF->getUuid(textImages->uuid);
+ textImages->loadAddress = cacheDylib.cacheLoadAddress.rawValue();
+ textImages->textSegmentSize = (uint32_t)cacheDylib.segments.front().cacheVMSize.rawValue();
+ textImages->pathOffset = stringOffset;
+ const char* installName = cacheDylib.installName.data();
+ ::strcpy((char*)dyldCacheHeader + stringOffset, installName);
+ stringOffset += (uint32_t)cacheDylib.installName.size() + 1;
+ ++textImages;
+ }
+
+ // fill in image table. This has to be after the above loop so that the install names are within 32-bits of the first shared cache
+ textImages = (dyld_cache_image_text_info*)((uint8_t*)dyldCacheHeader + dyldCacheHeader->imagesTextOffset);
+ dyld_cache_image_info* images = (dyld_cache_image_info*)((uint8_t*)dyldCacheHeader + dyldCacheHeader->imagesOffset);
+ for ( const CacheDylib& cacheDylib : cacheDylibs ) {
+ images->address = cacheDylib.cacheLoadAddress.rawValue();
+ if ( options.dylibsRemovedFromDisk ) {
+ images->modTime = 0;
+ images->inode = 0;
+ }
+ else {
+ images->modTime = cacheDylib.inputFile->mtime;
+ images->inode = cacheDylib.inputFile->inode;
+ }
+ images->pathFileOffset = (uint32_t)textImages->pathOffset;
+ ++images;
+ ++textImages;
+ }
+
+ // make sure header did not overflow
+ assert(stringOffset <= cacheHeaderChunk.cacheVMSize.rawValue());
+}
+
+bool SubCache::isMainCache() const
+{
+ switch ( this->kind ) {
+ case Kind::mainDevelopment:
+ case Kind::mainCustomer:
+ return true;
+ case Kind::stubsDevelopment:
+ case Kind::stubsCustomer:
+ case Kind::subUniversal:
+ case Kind::symbols:
+ return false;
+ }
+}
+
+bool SubCache::isMainDevelopmentCache() const
+{
+ return this->kind == Kind::mainDevelopment;
+}
+
+bool SubCache::isMainCustomerCache() const
+{
+ return this->kind == Kind::mainCustomer;
+}
+
+bool SubCache::isSymbolsCache() const
+{
+ switch ( this->kind ) {
+ case Kind::symbols:
+ return true;
+ case Kind::mainDevelopment:
+ case Kind::mainCustomer:
+ case Kind::stubsDevelopment:
+ case Kind::stubsCustomer:
+ case Kind::subUniversal:
+ return false;
+ }
+}
+
+bool SubCache::isSubCache() const
+{
+ switch ( this->kind ) {
+ case Kind::subUniversal:
+ return true;
+ case Kind::mainDevelopment:
+ case Kind::mainCustomer:
+ case Kind::stubsDevelopment:
+ case Kind::stubsCustomer:
+ case Kind::symbols:
+ return false;
+ }
+}
+
+bool SubCache::isStubsCache() const
+{
+ switch ( this->kind ) {
+ case Kind::stubsDevelopment:
+ case Kind::stubsCustomer:
+ return true;
+ case Kind::mainDevelopment:
+ case Kind::mainCustomer:
+ case Kind::subUniversal:
+ case Kind::symbols:
+ return false;
+ }
+}
+
+bool SubCache::isStubsDevelopmentCache() const
+{
+ return this->kind == Kind::stubsDevelopment;
+}
+
+bool SubCache::isStubsCustomerCache() const
+{
+ return this->kind == Kind::stubsCustomer;
+}
+
+bool SubCache::needsCacheHeaderImageList() const
+{
+ // Symbols and stubs files don't need an image list
+ // We'd like to not add the image list to subcaches, only the main cache, but Rosetta needs
+ // the image list on subCaches.
+ switch ( this->kind ) {
+ case Kind::mainDevelopment:
+ case Kind::mainCustomer:
+ case Kind::subUniversal:
+ return true;
+ case Kind::stubsDevelopment:
+ case Kind::stubsCustomer:
+ case Kind::symbols:
+ return false;
+ }
+}
+
+static void set_toc(dyld_cache_slide_info* info, unsigned index, uint16_t value)
+{
+ ((uint16_t*)(((uint8_t*)info) + info->toc_offset))[index] = value;
+}
+
+Error SubCache::computeSlideInfoV1(const BuilderConfig& config,
+ cache_builder::SlideInfoChunk* slideChunk,
+ Region& region)
+{
+ // build one 512-byte bitmap per page (16384) of DATA
+
+ // fill in fixed info
+ assert((region.subCacheVMSize.rawValue() % config.slideInfo.slideInfoPageSize) == 0);
+
+ // Create a bitmap for all pages in this region
+ const long bitmapSize = (region.subCacheVMSize.rawValue()) / (4*8);
+ uint8_t* bitmap = (uint8_t*)calloc(bitmapSize, 1);
+ for ( Chunk* chunk : region.chunks ) {
+ SlidChunk* slidChunk = chunk->isSlidChunk();
+
+ VMOffset chunkOffsetInRegion = chunk->cacheVMAddress - region.subCacheVMAddress;
+ slidChunk->tracker.forEachFixup(^(void *loc, bool& stop) {
+ uint64_t offsetInChunk = (uint64_t)loc - (uint64_t)chunk->subCacheBuffer;
+ uint64_t offsetInRegion = chunkOffsetInRegion.rawValue() + offsetInChunk;
+
+ // Convert the location to the slide info format. In this case its just an unslid vmAddr
+ CacheVMAddress vmAddr = Fixup::Cache32::getCacheVMAddressFromLocation(config.layout.cacheBaseAddress,
+ loc);
+
+ *(uint32_t*)loc = (uint32_t)vmAddr.rawValue();
+
+ // Set the byte corresponding to this fixup
+ long byteIndex = offsetInRegion / (4*8);
+ long bitInByte = (offsetInRegion % 32) >> 2;
+ bitmap[byteIndex] |= (1 << bitInByte);
+ });
+ }
+
+ // allocate worst case size block of all slide info
+ const unsigned entry_size = 4096/(8*4); // 8 bits per byte, possible pointer every 4 bytes.
+ const unsigned toc_count = (unsigned)bitmapSize/entry_size;
+ dyld_cache_slide_info* slideInfo = (dyld_cache_slide_info*)slideChunk->subCacheBuffer;
+ slideInfo->version = 1;
+ slideInfo->toc_offset = sizeof(dyld_cache_slide_info);
+ slideInfo->toc_count = toc_count;
+ slideInfo->entries_offset = (slideInfo->toc_offset+2*toc_count+127)&(-128);
+ slideInfo->entries_count = 0;
+ slideInfo->entries_size = entry_size;
+ // append each unique entry
+ const dyld_cache_slide_info_entry* bitmapAsEntries = (dyld_cache_slide_info_entry*)bitmap;
+ dyld_cache_slide_info_entry* const entriesInSlidInfo = (dyld_cache_slide_info_entry*)((char*)slideInfo+slideInfo->entries_offset);
+ int entry_count = 0;
+ for ( unsigned i = 0; i < toc_count; ++i ) {
+ const dyld_cache_slide_info_entry* thisEntry = &bitmapAsEntries[i];
+ // see if it is same as one already added
+ bool found = false;
+ for (int j=0; j < entry_count; ++j) {
+ if ( memcmp(thisEntry, &entriesInSlidInfo[j], entry_size) == 0 ) {
+ set_toc(slideInfo, i, j);
+ found = true;
+ break;
+ }
+ }
+ if ( !found ) {
+ // append to end
+ memcpy(&entriesInSlidInfo[entry_count], thisEntry, entry_size);
+ set_toc(slideInfo, i, entry_count++);
+ }
+ }
+ slideInfo->entries_count = entry_count;
+ ::free((void*)bitmap);
+
+ // Update slide info size as we may not need all the space we allocated
+ CacheFileSize slideInfoSize((uint64_t)slideInfo->entries_offset + (entry_count * entry_size));
+ if ( slideInfoSize > slideChunk->subCacheFileSize ) {
+ return Error("kernel slide info overflow buffer");
+ }
+
+ slideChunk->usedFileSize = slideInfoSize;
+
+ return Error();
+}
+
+Error SubCache::computeSlideInfoV2(const BuilderConfig& config,
+ cache_builder::SlideInfoChunk* slideChunk,
+ Region& region)
+{
+ __block Diagnostics diag;
+
+ assert((region.subCacheVMSize.rawValue() % config.slideInfo.slideInfoPageSize) == 0);
+ dyld_cache_slide_info2* info = (dyld_cache_slide_info2*)slideChunk->subCacheBuffer;
+ info->version = 2;
+ info->page_size = config.slideInfo.slideInfoPageSize;
+ info->page_starts_offset = sizeof(dyld_cache_slide_info2);
+ info->page_starts_count = (uint32_t)region.subCacheVMSize.rawValue() / config.slideInfo.slideInfoPageSize;
+ info->page_extras_offset = 0;
+ info->page_extras_count = 0;
+ info->delta_mask = config.slideInfo.slideInfoDeltaMask;
+ info->value_add = config.slideInfo.slideInfoValueAdd.rawValue();
+
+ assert((sizeof(dyld_cache_slide_info2) + (info->page_starts_count * sizeof(uint16_t))) <= slideChunk->cacheVMSize.rawValue());
+
+ uint16_t* pageStartsBuffer = (uint16_t*)((char*)info + info->page_starts_offset);
+ std::fill(&pageStartsBuffer[0], &pageStartsBuffer[info->page_starts_count], DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE);
+
+ const uint64_t deltaMask = info->delta_mask;
+ const uint64_t valueMask = ~deltaMask;
+ const uint64_t valueAdd = info->value_add;
+ const unsigned deltaShift = __builtin_ctzll(deltaMask) - 2;
+ const uint32_t maxDelta = (uint32_t)(deltaMask >> deltaShift);
+
+ // Walk each fixup in each segment. Every time we cross a page, add a page start
+ __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) {
+ VMOffset vmOffsetInSegment((uint64_t)loc - (uint64_t)slidChunk->subCacheBuffer);
+ assert((vmOffsetInSegment.rawValue() + 8) <= slidChunk->cacheVMSize.rawValue());
+ CacheVMAddress fixupVMAddr = slidChunk->cacheVMAddress + vmOffsetInSegment;
+ uint64_t pageIndex = (fixupVMAddr - region.subCacheVMAddress).rawValue() / info->page_size;
+
+ // Make sure we never cross a page
+ uint64_t highBytesPageIndex = ((fixupVMAddr + VMOffset(4ULL)) - region.subCacheVMAddress).rawValue() / info->page_size;
+ if ( pageIndex != highBytesPageIndex ) {
+ diag.error("Fixup crosses page boundary");
+ stop = true;
+ return;
+ }
+
+ // If we are on a new page, then start a new chain
+ if ( pageIndex != lastPageIndex ) {
+ uint64_t vmOffsetInPage = fixupVMAddr.rawValue() % info->page_size;
+
+ // Note first location in the page is a word offset, not a byte offset
+ pageStartsBuffer[pageIndex] = vmOffsetInPage / 4;
+ }
+ else {
+ // Patch the previous fixup on this page to point to this one
+ uint64_t delta = (uint64_t)loc - (uint64_t)lastFixup;
+ assert(delta <= maxDelta);
+ lastFixup->raw64 |= (delta << deltaShift);
+ }
+
+ MachOFile::ChainedFixupPointerOnDisk* fixup = (MachOFile::ChainedFixupPointerOnDisk*)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);
+
+ if ( (vmAddr.rawValue() - valueAdd) & deltaMask ) {
+ std::string dylibName = "unknown dylib";
+ std::string segName = "unknown segment";
+ //findDylibAndSegment((void*)pageContent, dylibName, segName);
+ diag.error("rebase pointer (0x%0llX) does not point within cache. vmOffsetInSegment=0x%04llX, seg=%s, dylib=%s\n",
+ vmAddr.rawValue(), vmOffsetInSegment.rawValue(), segName.c_str(), dylibName.c_str());
+ stop = true;
+ return;
+ }
+
+
+ // Make sure we don't have an authenticated value. V2 doesn't support them
+ {
+ uint16_t authDiversity = 0;
+ bool authIsAddr = false;
+ uint8_t authKey = 0;
+ if ( Fixup::Cache64::hasAuthData(loc, authDiversity, authIsAddr, authKey) ) {
+ std::string dylibName = "unknown dylib";
+ std::string segName = "unknown segment";
+ //findDylibAndSegment((void*)pageContent, dylibName, segName);
+ diag.error("rebase pointer (0x%0llX) is authenticated. vmOffsetInSegment=0x%04llX, seg=%s, dylib=%s\n",
+ vmAddr.rawValue(), vmOffsetInSegment.rawValue(), segName.c_str(), dylibName.c_str());
+ return;
+ }
+ }
+
+ uint64_t targetValue = ((vmAddr.rawValue() - valueAdd) & valueMask);
+ if ( uint8_t high8 = Fixup::Cache64::getHigh8(loc) ) {
+ uint64_t tbi = (uint64_t)high8 << 56;
+ targetValue |= tbi;
+ }
+
+ fixup->raw64 = targetValue;
+
+ lastFixup = fixup;
+ lastPageIndex = pageIndex;
+ });
+ }
+
+ if ( diag.hasError() )
+ return Error("could not build slide info because: %s", diag.errorMessageCStr());
+
+ // V2 doesn't deduplicate content like V1, so the used size is the original size too
+ slideChunk->usedFileSize = slideChunk->subCacheFileSize;
+
+ return Error();
+}
+
+Error SubCache::computeSlideInfoV3(const BuilderConfig& config,
+ cache_builder::SlideInfoChunk* slideChunk,
+ Region& region)
+{
+ Diagnostics diag;
+
+ bool canContainAuthPointers = region.canContainAuthPointers();
+
+ assert((region.subCacheVMSize.rawValue() % config.slideInfo.slideInfoPageSize) == 0);
+ dyld_cache_slide_info3* info = (dyld_cache_slide_info3*)slideChunk->subCacheBuffer;
+ info->version = 3;
+ info->page_size = config.slideInfo.slideInfoPageSize;
+ info->page_starts_count = (uint32_t)region.subCacheVMSize.rawValue() / config.slideInfo.slideInfoPageSize;
+ info->auth_value_add = config.layout.cacheBaseAddress.rawValue();
+
+ assert((sizeof(dyld_cache_slide_info3) + (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_V3_PAGE_ATTR_NO_REBASE);
+
+ // Walk each fixup in each segment. Every time we cross a page, add a page start
+ __block dyld_cache_slide_pointer3* lastFixup = nullptr;
+ __block uint64_t lastPageIndex = ~0ULL;
+ for ( Chunk* chunk : region.chunks ) {
+ SlidChunk* slidChunk = chunk->isSlidChunk();
+
+ slidChunk->tracker.forEachFixup(^(void *loc, bool& stop) {
+ // V3 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.offsetToNextPointer = ((uint64_t)loc - (uint64_t)lastFixup) / 8;
+ }
+
+ dyld_cache_slide_pointer3* fixupLocation = (dyld_cache_slide_pointer3*)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);
+
+ 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);
+
+ VMOffset cacheVMOffset = vmAddr - config.layout.cacheBaseAddress;
+ fixupLocation->auth.offsetFromSharedCacheBase = cacheVMOffset.rawValue();
+ fixupLocation->auth.diversityData = authDiversity;
+ fixupLocation->auth.hasAddressDiversity = authIsAddr ? 1 : 0;
+ fixupLocation->auth.key = authKey;
+ fixupLocation->auth.offsetToNextPointer = 0;
+ fixupLocation->auth.unused = 0;
+ fixupLocation->auth.authenticated = 1;
+
+ assert(fixupLocation->auth.offsetFromSharedCacheBase == cacheVMOffset.rawValue());
+ } else {
+ // Unauthenticated value
+ uint64_t pointerValue = vmAddr.rawValue() | ((uint64_t)high8 << 43);
+ fixupLocation->plain.pointerValue = pointerValue;
+ fixupLocation->plain.offsetToNextPointer = 0;
+ fixupLocation->plain.unused = 0;
+ assert(fixupLocation->plain.pointerValue == pointerValue);
+ }
+
+ lastFixup = fixupLocation;
+ lastPageIndex = pageIndex;
+ });
+ }
+
+ if ( diag.hasError() )
+ return Error("could not build slide info because: %s", diag.errorMessageCStr());
+
+ // V3 doesn't deduplicate content like V1, so the used size is the original size too
+ slideChunk->usedFileSize = slideChunk->subCacheFileSize;
+
+ 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)
+{
+ switch ( config.slideInfo.slideInfoFormat.value() ) {
+ case cache_builder::SlideInfo::SlideInfoFormat::v1:
+ return computeSlideInfoV1(config, slideChunk, region);
+ case cache_builder::SlideInfo::SlideInfoFormat::v2:
+ 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();
+}
+
+Error SubCache::convertChainsToVMAddresses(const BuilderConfig& config, Region& region)
+{
+ 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);
+ uint8_t high8 = Fixup::Cache64::getHigh8(loc);
+ *(uint64_t*)loc = vmAddr.rawValue() | ((uint64_t)high8 << 56);
+ } else {
+ CacheVMAddress vmAddr = Fixup::Cache32::getCacheVMAddressFromLocation(config.layout.cacheBaseAddress, loc);
+ *(uint32_t*)loc = (uint32_t)vmAddr.rawValue();
+ }
+ });
+ }
+
+ if ( diag.hasError() )
+ return Error("could not build slide info because: %s", diag.errorMessageCStr());
+
+ return Error();
+}
+
+Error SubCache::computeSlideInfo(const BuilderConfig& config)
+{
+ for ( Region& region : this->regions ) {
+ Error err;
+ switch ( region.kind ) {
+ 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);
+ else
+ err = convertChainsToVMAddresses(config, region);
+ break;
+ case Region::Kind::dataConst:
+ if ( config.slideInfo.slideInfoFormat.has_value() )
+ err = computeSlideInfoForRegion(config, this->dataConstSlideInfo.get(), region);
+ 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);
+ else
+ err = convertChainsToVMAddresses(config, region);
+ break;
+ case Region::Kind::authConst:
+ if ( config.slideInfo.slideInfoFormat.has_value() )
+ err = computeSlideInfoForRegion(config, this->authConstSlideInfo.get(), region);
+ else
+ err = convertChainsToVMAddresses(config, region);
+ break;
+ case Region::Kind::readOnly:
+ case Region::Kind::linkedit:
+ // No slide info for linkedit
+ break;
+ case Region::Kind::unmapped:
+ case Region::Kind::dynamicConfig:
+ case Region::Kind::codeSignature:
+ // No slide info for unmapped/code signature
+ break;
+ case Region::Kind::numKinds:
+ assert(0);
+ break;
+ }
+
+ if ( err.hasError() )
+ return err;
+ }
+
+ return Error();
+}
+
+bool SubCache::shouldKeepCache(bool keepDevelopmentCaches, bool keepCustomerCaches) const
+{
+ switch ( this->kind ) {
+ case Kind::mainDevelopment:
+ case Kind::stubsDevelopment:
+ return keepDevelopmentCaches;
+ case Kind::mainCustomer:
+ case Kind::stubsCustomer:
+ return keepCustomerCaches;
+ case Kind::subUniversal:
+ case Kind::symbols:
+ return true;
+ }
+}