Loading...
--- dyld/dyld-1340/cache_builder/NewSharedCacheBuilder.cpp
+++ dyld/dyld-1042.1/cache_builder/NewSharedCacheBuilder.cpp
@@ -24,11 +24,9 @@
#include "Defines.h"
#include "NewSharedCacheBuilder.h"
-#include "MachOFile.h"
#include "NewAdjustDylibSegments.h"
#include "CacheDylib.h"
#include "ClosureFileSystem.h"
-#include "JSONReader.h"
#include "JSONWriter.h"
#include "StringUtils.h"
#include "Array.h"
@@ -38,28 +36,12 @@
#include "ObjCVisitor.h"
#include "Trie.hpp"
#include "JustInTimeLoader.h"
-#include "OptimizerObjC.h"
#include "OptimizerSwift.h"
-#include "Platform.h"
#include "PrebuiltLoader.h"
#include "DyldProcessConfig.h"
#include "DyldRuntimeState.h"
#include "SwiftVisitor.h"
#include "ParallelUtils.h"
-#include "CString.h"
-#include "Version32.h"
-#include "ExternalGenericMetadataBuilderImport.h"
-#include "SnapshotShared.h"
-#include "AAREncoder.h"
-#include <SharedCacheLinker/SharedCacheLinker.h>
-#include "ThreadLocalVariables.h"
-#include "Algorithm.h"
-
-// mach_o
-#include "ExportsTrie.h"
-#include "Header.h"
-#include "Image.h"
-#include "Misc.h"
// FIXME: Remove this once we don't write to the old objc header struct. See emitObjCOptsHeader()
#include "objc-shared-cache.h"
@@ -68,31 +50,19 @@
#include <list>
#include <mach-o/nlist.h>
#include <sstream>
-#include <sys/fcntl.h>
-#include <sys/stat.h>
#include <unordered_set>
+using dyld3::GradedArchs;
using dyld3::MachOFile;
using dyld4::JustInTimeLoader;
using dyld4::KernelArgs;
using dyld4::Loader;
using dyld4::ProcessConfig;
-using dyld4::RuntimeLocks;
using dyld4::RuntimeState;
using dyld4::SyscallDelegate;
-using dyld4::RuntimeLocks;
-
-using lsl::Allocator;
-
-using mach_o::Header;
-using mach_o::Platform;
-using mach_o::Image;
-using mach_o::ExportsTrie;
-using mach_o::Symbol;
-using mach_o::LinkedDylibAttributes;
-using mach_o::Version32;
-using error::Error;
+
+using lsl::EphemeralAllocator;
using metadata_visitor::SwiftConformance;
using metadata_visitor::SwiftVisitor;
@@ -118,30 +88,14 @@
// Note, don't change the form of this message without checking in with MRM, as they
// parse it. We really need to add structured errors/warnings some time
std::string reason = "Dylib located at '" + inputFile.path + "' not placed in shared cache because: ";
- for ( const error::Error& err : inputFile.getErrors() )
- callback(reason + err.message());
- }
- }
-
- for ( const std::string& warning : this->warnings )
- callback(warning);
-}
-
-void SharedCacheBuilder::forEachError(void (^callback)(const std::string_view& str)) const
-{
- for ( const std::string& str : this->errors ) {
- callback(str);
+ callback(reason + inputFile.getError().message());
+ }
}
}
void SharedCacheBuilder::forEachCacheDylib(void (^callback)(const std::string_view& path)) const
{
for ( const CacheDylib& cacheDylib : this->cacheDylibs ) {
- // skip Swift prespecialized dylib if it's been built
- // it's synthesized by the builder, so mrm doesn't need to remove it
- if ( swiftPrespecializedDylib && &cacheDylib == swiftPrespecializedDylib )
- continue;
-
// Note this has to return the path, not the install name, as MRM uses this to delete
// the path from disk
callback(cacheDylib.inputFile->path);
@@ -155,211 +109,38 @@
}
}
-struct DylibSizes
-{
- uint64_t textSegment = 0;
- uint64_t dataSegment = 0;
- uint64_t dataConstSegment = 0;
- uint64_t dataDirtySegment = 0;
- uint64_t authSegment = 0;
- uint64_t authConstSegment = 0;
- uint64_t linkeditSegment = 0;
-
- // any other segment not in the above
- uint64_t otherSegment = 0;
-
- // Just the __TEXT __text section
- uint64_t textSection = 0;
-
- // parts of the linkedit
- uint64_t exportTrie = 0;
- uint64_t symbolTable = 0;
- uint64_t symbolStrings = 0;
-};
-
-static DylibSizes getDylibSizes(std::span<const SubCache> subCaches,
- std::span<const CacheDylib> cacheDylibs)
-{
- __block DylibSizes dylibSizes;
- __block std::unordered_set<uint64_t> linkeditSizes;
-
- // segment sizes
- for ( const CacheDylib& cacheDylib : cacheDylibs ) {
- cacheDylib.cacheHdr->forEachSegment(^(const Header::SegmentInfo& info, bool& stop) {
- if ( info.segmentName == "__TEXT" )
- dylibSizes.textSegment += info.vmsize;
- else if ( info.segmentName == "__DATA" )
- dylibSizes.dataSegment += info.vmsize;
- else if ( info.segmentName == "__DATA_CONST" )
- dylibSizes.dataConstSegment += info.vmsize;
- else if ( info.segmentName == "__DATA_DIRTY" )
- dylibSizes.dataDirtySegment += info.vmsize;
- else if ( info.segmentName == "__AUTH" )
- dylibSizes.authSegment += info.vmsize;
- else if ( info.segmentName == "__AUTH_CONST" )
- dylibSizes.authConstSegment += info.vmsize;
- else if ( info.segmentName == "__LINKEDIT" )
- linkeditSizes.insert(info.vmsize);
- else
- dylibSizes.otherSegment += info.vmsize;
- });
- }
- for ( uint64_t size : linkeditSizes )
- dylibSizes.linkeditSegment += size;
-
- // section sizes
- for ( const CacheDylib& cacheDylib : cacheDylibs ) {
- cacheDylib.cacheHdr->forEachSection(^(const Header::SectionInfo& info, bool& stop) {
- if ( (info.segmentName == "__TEXT") && (info.sectionName == "__text") )
- dylibSizes.textSection += info.size;
- });
- }
-
- // linkedit sizes
- for ( const CacheDylib& cacheDylib : cacheDylibs ) {
- for ( const LinkeditDataChunk& chunk : cacheDylib.linkeditChunks ) {
- if ( chunk.kind == Chunk::Kind::linkeditExportTrie )
- dylibSizes.exportTrie += chunk.cacheVMSize.rawValue();
- }
-
- if ( !cacheDylib.optimizedSymbols.nlist64.empty() ) {
- dylibSizes.symbolTable += (sizeof(struct nlist_64) * cacheDylib.optimizedSymbols.nlist64.size());
- } else {
- dylibSizes.symbolTable += (sizeof(struct nlist) * cacheDylib.optimizedSymbols.nlist64.size());
- }
- }
-
- for ( const SubCache& subCache : subCaches ) {
- if ( subCache.optimizedSymbolStrings )
- dylibSizes.symbolStrings += subCache.optimizedSymbolStrings->cacheVMSize.rawValue();
- }
-
- return dylibSizes;
-}
-
-std::string SharedCacheBuilder::stats(uint64_t startTimeNanos) const
-{
- uint64_t endTimeNanos = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
- uint64_t totalTime = (endTimeNanos - startTimeNanos) / 1000000000;
-
- DylibSizes dylibSizes = getDylibSizes(this->subCaches, this->cacheDylibs);
-
- std::string result;
- result += "{\n";
-
- // add all the top level fields
- result += std::string(" \"version\": 1,\n");
- result += std::string(" \"minor-version\": 0,\n");
- result += std::string(" \"update\": \"") + this->options.updateName + "\",\n";
- result += std::string(" \"device\": \"") + this->options.deviceName + "\",\n";
- result += std::string(" \"platform\": \"") + this->options.platform.name().c_str() + "\",\n";
- result += std::string(" \"arch\": \"") + this->options.arch.name() + "\",\n";
-
- // main stats
- result += std::string(" \"dylibs-count\": ") + std::to_string(this->cacheDylibs.size()) + ",\n";
- result += std::string(" \"total-vm-size\": ") + std::to_string(this->totalVMSize.rawValue()) + ",\n";
- result += std::string(" \"total-text-segment-size\": ") + std::to_string(dylibSizes.textSegment) + ",\n";
- result += std::string(" \"total-data-segment-size\": ") + std::to_string(dylibSizes.dataSegment) + ",\n";
- result += std::string(" \"total-data-dirty-segment-size\": ") + std::to_string(dylibSizes.dataDirtySegment) + ",\n";
- result += std::string(" \"total-data-const-segment-size\": ") + std::to_string(dylibSizes.dataConstSegment) + ",\n";
- result += std::string(" \"total-auth-segment-size\": ") + std::to_string(dylibSizes.authSegment) + ",\n";
- result += std::string(" \"total-auth-const-segment-size\": ") + std::to_string(dylibSizes.authConstSegment) + ",\n";
- result += std::string(" \"total-other-segment-size\": ") + std::to_string(dylibSizes.otherSegment) + ",\n";
- result += std::string(" \"total-linkedit-segment-size\": ") + std::to_string(dylibSizes.linkeditSegment) + ",\n";
- result += std::string(" \"total-text-section-size\": ") + std::to_string(dylibSizes.textSection) + ",\n";
-
- // optimization stats
- result += std::string(" \"objc-selector-hash-table-size\": ") + std::to_string(this->objcSelectorOptimizer.selectorHashTableTotalByteSize) + ",\n";
- result += std::string(" \"objc-class-hash-table-size\": ") + std::to_string(this->objcClassOptimizer.classHashTableTotalByteSize) + ",\n";
- result += std::string(" \"objc-protocol-hash-table-size\": ") + std::to_string(this->objcProtocolOptimizer.protocolHashTableTotalByteSize) + ",\n";
- result += std::string(" \"objc-selectors-count\": ") + std::to_string(this->objcSelectorOptimizer.selectorsMap.size()) + ",\n";
- result += std::string(" \"objc-classes-count\": ") + std::to_string(this->objcClassOptimizer.namesMap.size()) + ",\n";
- result += std::string(" \"objc-protocols-count\": ") + std::to_string(this->objcProtocolOptimizer.namesMap.size()) + ",\n";
-
- // linkedit breakdown
- result += std::string(" \"linkedit-export-trie-size\": ") + std::to_string(dylibSizes.exportTrie) + ",\n";
- result += std::string(" \"linkedit-symbol-table-size\": ") + std::to_string(dylibSizes.symbolTable) + ",\n";
- result += std::string(" \"linkedit-symbol-strings-size\": ") + std::to_string(dylibSizes.symbolStrings) + ",\n";
-
- // cache metadata
- result += std::string(" \"patch-table-size\": ") + std::to_string(this->patchTableOptimizer.patchTableChunk->cacheVMSize.rawValue()) + ",\n";
- result += std::string(" \"dylib-loaders-size\": ") + std::to_string(this->prebuiltLoaderBuilder.cacheDylibsLoaderSize) + ",\n";
- result += std::string(" \"executable-loaders-size\": ") + std::to_string(this->prebuiltLoaderBuilder.executablesLoaderSize) + ",\n";
-
- // Times
- result += std::string(" \"total-build-time\": ") + std::to_string(totalTime) + "\n";
-
- result += "}\n";
- return result;
-}
-
void SharedCacheBuilder::addFile(const void* buffer, size_t bufferSize, std::string_view path,
- uint64_t inode, uint64_t modTime, bool forceNotCacheEligible)
-{
+ uint64_t inode, uint64_t modTime)
+{
+ Diagnostics diag;
const bool isOSBinary = false;
- uint64_t sliceOffset = 0;
- uint64_t sliceLen = 0;
+ if ( const MachOFile* mf = MachOFile::compatibleSlice(diag, buffer, bufferSize, path.data(),
+ this->options.platform, isOSBinary,
+ this->options.archs) ) {
+ InputFile inputFile;
+ inputFile.mf = mf;
+ inputFile.inode = inode;
+ inputFile.mtime = modTime;
+ inputFile.path = path;
+ allInputFiles.push_back(std::move(inputFile));
+ return;
+ }
// On macOS, also allow iOSMac dylibs
- if ( this->options.platform == Platform::macOS ) {
- Diagnostics diag;
- if ( const MachOFile* mf = MachOFile::compatibleSlice(diag, sliceOffset, sliceLen, buffer, bufferSize, path.data(),
- Platform::macCatalyst, isOSBinary,
- this->options.gradedArchs) ) {
+ if ( this->options.platform == dyld3::Platform::macOS ) {
+ diag.clearError();
+ if ( const MachOFile* mf = MachOFile::compatibleSlice(diag, buffer, bufferSize, path.data(),
+ dyld3::Platform::iOSMac, isOSBinary,
+ this->options.archs) ) {
InputFile inputFile;
- inputFile.mf = mf;
- inputFile.inode = inode;
- inputFile.mtime = modTime;
- inputFile.size = sliceLen;
- inputFile.path = path;
- inputFile.forceNotCacheEligible = forceNotCacheEligible;
+ inputFile.mf = mf;
+ inputFile.inode = inode;
+ inputFile.mtime = modTime;
+ inputFile.path = path;
allInputFiles.push_back(std::move(inputFile));
return;
}
}
-
- Diagnostics diag;
- if ( const MachOFile* mf = MachOFile::compatibleSlice(diag, sliceOffset, sliceLen, buffer, bufferSize, path.data(),
- this->options.platform, isOSBinary,
- this->options.gradedArchs) ) {
- InputFile inputFile;
- inputFile.mf = mf;
- inputFile.inode = inode;
- inputFile.mtime = modTime;
- inputFile.size = sliceLen;
- inputFile.path = path;
- inputFile.forceNotCacheEligible = forceNotCacheEligible;
- allInputFiles.push_back(std::move(inputFile));
- return;
- }
-
- // If we have an error, then add an input file just in case its useful for later
- if ( diag.hasError() ) {
- // Only add dylibs to track for later error emission
- __block bool isDylib = false;
- const std::span<uint8_t> bufferSpan = { (uint8_t*)buffer, bufferSize };
- mach_o::Error parseErr = mach_o::forEachHeader(bufferSpan, path,
- ^(const mach_o::Header* mh, size_t sliceHeader, bool& stop) {
- if ( mh->isDylib() ) {
- isDylib = true;
- stop = true;
- }
- });
-
- if ( !isDylib )
- return;
-
- InputFile inputFile;
- inputFile.mf = nullptr;
- inputFile.inode = 0;
- inputFile.mtime = 0;
- inputFile.size = 0;
- inputFile.path = path;
- inputFile.forceNotCacheEligible = true;
- inputFile.addError(Error("%s", (const char*)diag.errorMessageCStr()));
- allInputFiles.push_back(std::move(inputFile));
- return;
- }
}
void SharedCacheBuilder::setAliases(const std::vector<FileAlias>& aliases,
@@ -368,7 +149,6 @@
this->inputAliases = aliases;
this->inputIntermediateAliases = intermediateAliases;
}
-
//
// MARK: --- SharedCacheBuilder build methods ---
@@ -383,9 +163,6 @@
if ( this->allInputFiles.empty() )
return Error("Cannot build cache with no inputs");
- // Reserve a slot for the Swift prespecialized dylib early, so that it can be ordered
- this->reserveSwiftPrespecializedInputFile();
-
this->categorizeInputs();
this->verifySelfContained();
@@ -393,24 +170,11 @@
return Error("Cannot build cache with no dylibs");
this->sortDylibs();
- this->sortExecutables();
-
- // Note this needs to be after sorting, so the order of objc dylibs is consistent with all dylibs list
- this->findObjCDylibs();
-
- // ObjC dylibs order is now set, so we can create the Swift prespecialized dylib
- // Note this needs to happen after order is known because the Swift dylib needs to
- // known indices of other shared cache dylibs. To create the dylib earlier we would need
- // to add split seg support for dylib indices.
- if ( Error error = this->createSwiftPrespecializedDylib() ) {
- swiftPrespecializedDylibBuildError = error.message();
- return error;
- }
// Note this needs to be after sorting, as aliases point to the cache dylibs
this->calculateDylibAliases();
- if ( Error error = this->calculateDylibDependents() )
+ if ( Error error = this->calculateDylibDependents(); error.hasError() )
return error;
this->categorizeDylibSegments();
@@ -427,23 +191,21 @@
Error SharedCacheBuilder::estimateGlobalOptimizations()
{
this->estimateIMPCaches();
+ this->findObjCDylibs();
this->findCanonicalObjCSelectors();
this->findCanonicalObjCClassNames();
this->findCanonicalObjCProtocolNames();
this->findObjCClasses();
this->findObjCProtocols();
- this->findObjCCategories();
this->estimateObjCHashTableSizes();
this->calculateObjCCanonicalProtocolsSize();
- this->calculateObjCCategoriesSize();
// Note, swift hash tables depends on findObjCClasses()
this->estimateSwiftHashTableSizes();
this->calculateCacheDylibsTrie();
- this->estimateFunctionVariantsSize();
+ this->estimatePatchTableSize();
this->estimateCacheLoadersSize();
- this->estimatePrewarmingSize();
this->setupStubOptimizer();
@@ -456,10 +218,6 @@
// Outputs: subCaches
Error SharedCacheBuilder::createSubCaches()
{
- Timer::Scope timedScope(this->config, "createSubCaches time");
- Timer::AggregateTimer aggregateTimerOwner(this->config);
- auto& aggregateTimer = aggregateTimerOwner;
-
this->computeSubCaches();
// Per-subCache optimizations
@@ -468,54 +226,8 @@
if ( Error error = this->calculateUniqueGOTs(); error.hasError() )
return error;
- // Finalize the subcaches, by removing any unused regions
- for ( SubCache& subCache : this->subCaches ) {
- // Add slide info for each DATA/AUTH segment. Do this after we've added any other DATA*
- // segments
- if ( this->config.slideInfo.slideInfoFormat.has_value() )
- subCache.addSlideInfoChunks();
-
- subCache.removeEmptyRegions();
- }
-
- // Analyze the dylibs to work out binds and patch table info
- {
- this->initializePatchTableOptimizer();
-
- // Because blocks...
- std::vector<const CacheDylib*> builderCacheDylibsOwner;
- auto& builderCacheDylibs = builderCacheDylibsOwner;
- for ( const CacheDylib& cacheDylib : this->cacheDylibs )
- builderCacheDylibs.push_back(&cacheDylib);
-
- Error err = parallel::forEach(this->cacheDylibs, ^(size_t index, CacheDylib& cacheDylib) {
- Diagnostics diag;
- PatchInfo& dylibPatchInfo = this->patchTableOptimizer.patchInfos[cacheDylib.cacheIndex];
-
- std::vector<Error> symbolErrors = cacheDylib.calculateBindTargets(diag, this->config, aggregateTimer, builderCacheDylibs,
- dylibPatchInfo);
- if ( !symbolErrors.empty() ) {
- for ( const Error& symbolErr : symbolErrors )
- this->errors.push_back(symbolErr.message());
- }
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
-
- cacheDylib.calcuatePatchInfo(diag, this->config, aggregateTimer, dylibPatchInfo);
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
-
- return Error();
- });
-
- if ( err )
- return err;
- }
-
this->sortSubCacheSegments();
this->calculateSlideInfoSize();
- if ( Error error = this->calculatePatchTableSize(); error.hasError() )
- return error;
this->calculateCodeSignatureSize();
this->printSubCaches();
if ( Error error = this->computeSubCacheLayout(); error.hasError() )
@@ -526,8 +238,8 @@
return Error();
}
-// This is phase 4 of the build() process. It takes the inputs and Optimizers
-// from the previous phases, and emits them to the cache buffers
+// This is phase 4 of the build() process. It takes the inputs and Optimizers
+// from the previous phases, and creates the SubCache objects
// Inputs: subCaches, various Optimizers
// Outputs: emitted objc strings in the subCache buffers
Error SharedCacheBuilder::preDylibEmitChunks()
@@ -538,7 +250,6 @@
this->setupSplitSegAdjustors();
this->adjustObjCClasses();
this->adjustObjCProtocols();
- this->adjustObjCCategories();
// Note this could be after dylib passes, but having the strings emitted now makes
// it easier to debug the ObjC dylib passes
@@ -550,8 +261,8 @@
return Error();
}
-// This is phase 5 of the build() process.
-// It runs the passes on each of the cache Dylibs
+// This is phase 4 of the build() process.
+// It runs the passes on each of the cacheDylib's
// Inputs: subCaches, various Optimizers
// Outputs: emitted objc strings in the subCache buffers
Error SharedCacheBuilder::runDylibPasses()
@@ -560,15 +271,16 @@
Timer::AggregateTimer aggregateTimerOwner(this->config);
auto& aggregateTimer = aggregateTimerOwner;
+ // Because blocks...
+ std::vector<const CacheDylib*> builderCacheDylibsOwner;
+ auto& builderCacheDylibs = builderCacheDylibsOwner;
+ for ( const CacheDylib& cacheDylib : this->cacheDylibs )
+ builderCacheDylibs.push_back(&cacheDylib);
+
Error err = parallel::forEach(this->cacheDylibs, ^(size_t index, CacheDylib& cacheDylib) {
Diagnostics diag;
cacheDylib.copyRawSegments(this->config, aggregateTimer);
-
- // patch linked dylibs (load commands) as soon as the raw segments were coppied
- // so next steps have accurate view of the dylib
- if ( Error patchErr = this->patchLinkedDylibs(cacheDylib) )
- return patchErr;
PatchInfo& dylibPatchInfo = this->patchTableOptimizer.patchInfos[cacheDylib.cacheIndex];
cacheDylib.applySplitSegInfo(diag, this->options, this->config,
@@ -580,34 +292,35 @@
if ( diag.hasError() )
return Error("%s", diag.errorMessageCStr());
- cacheDylib.bind(diag, this->config, aggregateTimer, dylibPatchInfo, this->functionVariantsOptimizer);
+ cacheDylib.calculateBindTargets(diag, this->config, aggregateTimer, builderCacheDylibs,
+ dylibPatchInfo);
if ( diag.hasError() )
return Error("%s", diag.errorMessageCStr());
- // Disable objc optimizations from EK shared cache
- bool emitObjcOpts = !this->options.platform.isExclaveKit();
- if ( emitObjcOpts ) {
- cacheDylib.updateObjCSelectorReferences(diag, this->config, aggregateTimer, this->objcSelectorOptimizer);
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
-
- cacheDylib.convertObjCMethodListsToOffsets(diag, this->config, aggregateTimer, this->objcSelectorOptimizer.selectorStringsChunk);
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
-
- // Note, must be after updating selector references and converting relative methods to selector offsets
- cacheDylib.sortObjCMethodLists(diag, this->config, aggregateTimer, this->objcSelectorOptimizer.selectorStringsChunk);
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
-
- cacheDylib.optimizeLoadsFromConstants(this->config, aggregateTimer,
- this->objcSelectorOptimizer.selectorStringsChunk);
-
- Error error = cacheDylib.emitObjCIMPCaches(this->config, aggregateTimer, this->objcIMPCachesOptimizer,
- this->objcSelectorOptimizer.selectorStringsChunk);
- if ( error.hasError() )
- return error;
- }
+ cacheDylib.bind(diag, this->config, aggregateTimer, dylibPatchInfo);
+ if ( diag.hasError() )
+ return Error("%s", diag.errorMessageCStr());
+
+ cacheDylib.updateObjCSelectorReferences(diag, this->config, aggregateTimer, this->objcSelectorOptimizer);
+ if ( diag.hasError() )
+ return Error("%s", diag.errorMessageCStr());
+
+ cacheDylib.convertObjCMethodListsToOffsets(diag, this->config, aggregateTimer, this->objcSelectorOptimizer.selectorStringsChunk);
+ if ( diag.hasError() )
+ return Error("%s", diag.errorMessageCStr());
+
+ // Note, must be after updating selector references and converting relative methods to selector offsets
+ cacheDylib.sortObjCMethodLists(diag, this->config, aggregateTimer, this->objcSelectorOptimizer.selectorStringsChunk);
+ if ( diag.hasError() )
+ return Error("%s", diag.errorMessageCStr());
+
+ cacheDylib.optimizeLoadsFromConstants(this->config, aggregateTimer,
+ this->objcSelectorOptimizer.selectorStringsChunk);
+
+ Error error = cacheDylib.emitObjCIMPCaches(this->config, aggregateTimer, this->objcIMPCachesOptimizer,
+ this->objcSelectorOptimizer.selectorStringsChunk);
+ if ( error.hasError() )
+ return error;
cacheDylib.optimizeStubs(this->options, this->config, aggregateTimer, this->stubOptimizer,
dylibPatchInfo);
@@ -621,62 +334,43 @@
return err;
}
-// This is phase 6 of the build() process. It takes the Optimizers
+// This is phase 5 of the build() process. It takes the Optimizers
// from the previous phases, and emits them to the cache buffers
// Inputs: subCaches, various Optimizers
-// Outputs: emitted optimizations in the subCache buffers
+// Outputs: emitted optimiations in the subCache buffers
Error SharedCacheBuilder::postDylibEmitChunks()
{
- // Disable objc optimizations from EK shared cache
- bool emitObjcOpts = !this->options.platform.isExclaveKit();
-
- if ( Error error = this->emitPrewarmingData(); error.hasError() )
- return error;
-
this->optimizeTLVs();
if ( Error error = this->emitUniquedGOTs(); error.hasError() )
return error;
- if ( emitObjcOpts ) {
- // Note this has to be before we emit the protocol hash table
- if ( Error error = this->emitCanonicalObjCProtocols(); error.hasError() )
- return error;
- }
+ // Note this has to be before we emit the protocol hash table
+ if ( Error error = this->emitCanonicalObjCProtocols(); error.hasError() )
+ return error;
+
+ this->emitObjCHashTables();
+ this->emitObjCHeaderInfo();
+ if ( Error error = this->computeObjCClassLayout(); error.hasError() )
+ return error;
+
+ // Note this must be after computeObjCClassLayout() as we need it to set the flags for whether
+ // we have missing weak superclasses or not
+ this->emitObjCOptsHeader();
+
+ // Note, this has to be after we've emitted the objc class hash table, and after emitting
+ // the objc header info
+ if ( Error error = this->emitSwiftHashTables(); error.hasError() )
+ return error;
this->emitCacheDylibsTrie();
- if ( Error error = this->emitPatchTable() )
+ if ( Error error = this->emitPatchTable(); error.hasError() )
return error;
-
- this->emitFunctionVariants();
// Note, this must be after we emit the patch table
if ( Error error = this->emitCacheDylibsPrebuiltLoaders(); error.hasError() )
return error;
- if ( emitObjcOpts ) {
- this->emitObjCHashTables();
-
- // Note this has to be after anyone walking the objc metadata format
- if ( Error error = this->emitPreAttachedObjCCategories(); error.hasError() )
- return error;
-
- // Note, this must be after emitCacheDylibsPrebuiltLoaders() as it needs the offset to the SectionLocations*
- // in the PrebuiltLoader*
- this->emitObjCHeaderInfo();
- if ( Error error = this->computeObjCClassLayout(); error.hasError() )
- return error;
-
- // Note this must be after computeObjCClassLayout() as we need it to set the flags for whether
- // we have missing weak superclasses or not
- this->emitObjCOptsHeader();
-
- // Note, this has to be after we've emitted the objc class hash table, and after emitting
- // the objc header info
- if ( Error error = this->emitSwiftHashTables(); error.hasError() )
- return error;
- }
-
// Note, this has to be after we've emitted the objc hash tables and the objc header infos
if ( Error error = this->emitExecutablePrebuiltLoaders(); error.hasError() )
return error;
@@ -691,18 +385,18 @@
return Error();
}
-// This is phase 7 of the build() process. It does any final work
-// to emit the sub caches
+// This is phase 6 of the build() process. it does any final work to emit
+// the sub caches
// Inputs: everything else
// Outputs: final emitted data in the sub caches
Error SharedCacheBuilder::finalize()
{
+
// Do objc very late, as it adds segments to the mach-o, which aren't in sync with
// the segments on the CacheDylib
this->addObjcSegments();
this->computeCacheHeaders();
this->codeSign();
- this->buildAtlas();
return Error();
}
@@ -779,101 +473,55 @@
Stats stats(this->config);
Timer::Scope timedScope(this->config, "categorizeInputs time");
- struct Chunk
- {
- std::vector<CacheDylib> cacheDylibs;
- std::vector<InputFile*> exeInputFiles;
- std::vector<InputFile*> nonCacheDylibInputFiles;
- };
-
- mapReduce(std::span(allInputFiles), 32, ^(size_t i, Chunk& chunk, std::span<InputFile> files){
- for ( InputFile& inputFile : files ) {
- if ( inputFile.mf == nullptr ) continue;
-
- // only process valid images
- if ( ((Header*)inputFile.mf)->isDyldManaged() ) {
- // don't try to validate kernel or firmware mach-o binaries
- Image image(inputFile.mf, inputFile.size, Image::MappingKind::wholeSliceMapped);
- if ( mach_o::Error err = image.validate() ) {
- inputFile.addError(Error("%s", err.message()));
- continue;
- }
- }
-
- if ( inputFile.mf->isDylib() || inputFile.mf->isDyld() ) {
- auto failureHandler = ^(const char* format, ...) __attribute__((format(printf, 1, 2))) {
- char* output_string;
- va_list list;
- va_start(list, format);
- vasprintf(&output_string, format, list);
- va_end(list);
- inputFile.addError(Error("%s", (const char*)output_string));
- free(output_string);
- };
-
- std::string_view installName = ((const Header*)inputFile.mf)->installName();
- std::string_view dylibPath = inputFile.path;
- if ( (installName != dylibPath) && ((this->options.platform == Platform::macOS) || startsWith(dylibPath, "/System/Cryptexes/OS/")) ) {
- // We now typically require that install names and paths match. However symlinks may allow us to bring in a path which
- // doesn't match its install name.
- // For example:
- // /usr/lib/libstdc++.6.0.9.dylib is a real file with install name /usr/lib/libstdc++.6.dylib
- // /usr/lib/libstdc++.6.dylib is a symlink to /usr/lib/libstdc++.6.0.9.dylib
- // So long as we add both paths (with one as an alias) then this will work, even if dylibs are removed from disk
- // but the symlink remains.
- // Apply the same symlink crawling for dylibs that will install their contents to Cryptex paths but will have
- // install names with the cryptex paths removed.
- char resolvedSymlinkPath[PATH_MAX];
- if ( fileSystem.getRealPath(installName.data(), resolvedSymlinkPath) ) {
- if ( resolvedSymlinkPath == dylibPath ) {
- // Symlink is the install name and points to the on-disk dylib
- //fprintf(stderr, "Symlink works: %s == %s\n", inputFile.path, installName.c_str());
- dylibPath = installName;
- }
+ for ( InputFile& inputFile : this->allInputFiles ) {
+ if ( inputFile.mf->isDylib() || inputFile.mf->isDyld() ) {
+ auto failureHandler = ^(const char* reason) {
+ inputFile.setError(Error("%s", reason));
+ };
+
+ std::string_view installName = inputFile.mf->installName();
+ std::string_view dylibPath = inputFile.path;
+ if ( (installName != dylibPath) && ((this->options.platform == dyld3::Platform::macOS) || startsWith(dylibPath, "/System/Cryptexes/OS/")) ) {
+ // We now typically require that install names and paths match. However symlinks may allow us to bring in a path which
+ // doesn't match its install name.
+ // For example:
+ // /usr/lib/libstdc++.6.0.9.dylib is a real file with install name /usr/lib/libstdc++.6.dylib
+ // /usr/lib/libstdc++.6.dylib is a symlink to /usr/lib/libstdc++.6.0.9.dylib
+ // So long as we add both paths (with one as an alias) then this will work, even if dylibs are removed from disk
+ // but the symlink remains.
+ // Apply the same symlink crawling for dylibs that will install their contents to Cryptex paths but will have
+ // install names with the cryptex paths removed.
+ char resolvedSymlinkPath[PATH_MAX];
+ if ( fileSystem.getRealPath(installName.data(), resolvedSymlinkPath) ) {
+ if ( resolvedSymlinkPath == dylibPath ) {
+ // Symlink is the install name and points to the on-disk dylib
+ //fprintf(stderr, "Symlink works: %s == %s\n", inputFile.path, installName.c_str());
+ dylibPath = installName;
}
}
-
- if ( !inputFile.forceNotCacheEligible && inputFile.mf->canBePlacedInDyldCache(dylibPath.data(), true /* check objc */, failureHandler) ) {
- CacheDylib cacheDylib(inputFile);
- chunk.cacheDylibs.push_back(std::move(cacheDylib));
- }
- else {
- chunk.nonCacheDylibInputFiles.push_back(&inputFile);
- }
- continue;
- }
-
- if ( inputFile.mf->isDynamicExecutable() ) {
- auto failureHandler = ^(const char* reason) {
- inputFile.addError(Error("%s", reason));
- };
- if ( inputFile.mf->canHavePrebuiltExecutableLoader(options.platform, inputFile.path, failureHandler) ) {
- chunk.exeInputFiles.push_back(&inputFile);
- }
-
- continue;
- }
- }
- }, ^(std::span<Chunk> chunks) {
- size_t cacheDylibsSize = 0;
- size_t nonCacheSize = 0;
- size_t exeSize = 0;
- for ( Chunk& chunk : chunks ) {
- cacheDylibsSize += chunk.cacheDylibs.size();
- nonCacheSize += chunk.nonCacheDylibInputFiles.size();
- exeSize += chunk.exeInputFiles.size();
- }
-
- cacheDylibs.reserve(cacheDylibsSize);
- nonCacheDylibInputFiles.reserve(cacheDylibsSize);
- exeInputFiles.reserve(exeSize);
-
- for ( Chunk& chunk : chunks ) {
- std::move(chunk.cacheDylibs.begin(), chunk.cacheDylibs.end(), std::back_inserter(cacheDylibs));
- std::move(chunk.nonCacheDylibInputFiles.begin(), chunk.nonCacheDylibInputFiles.end(), std::back_inserter(nonCacheDylibInputFiles));
- std::move(chunk.exeInputFiles.begin(), chunk.exeInputFiles.end(), std::back_inserter(exeInputFiles));
- }
- });
+ }
+
+ if ( inputFile.mf->canBePlacedInDyldCache(dylibPath.data(), failureHandler) ) {
+ CacheDylib cacheDylib(inputFile);
+ this->cacheDylibs.push_back(std::move(cacheDylib));
+ }
+ else {
+ this->nonCacheDylibInputFiles.push_back(&inputFile);
+ }
+ continue;
+ }
+
+ if ( inputFile.mf->isDynamicExecutable() ) {
+ auto failureHandler = ^(const char* reason) {
+ inputFile.setError(Error("%s", reason));
+ };
+ if ( inputFile.mf->canHavePrebuiltExecutableLoader(options.platform, inputFile.path, failureHandler) ) {
+ this->exeInputFiles.push_back(&inputFile);
+ }
+
+ continue;
+ }
+ }
if ( this->config.log.printStats ) {
stats.add(" inputs: found %lld cache eligible dylibs\n", (uint64_t)this->cacheDylibs.size());
@@ -886,23 +534,11 @@
{
Timer::Scope timedScope(this->config, "verifySelfContained time");
- __block std::unordered_set<std::string_view> badDylibs;
-
- __block std::unordered_map<std::string_view, const InputFile*> allDylibs;
+ __block std::unordered_set<std::string_view> allDylibs;
allDylibs.reserve(this->allInputFiles.size());
for ( const InputFile& inputFile : this->allInputFiles ) {
- if ( inputFile.mf == nullptr ) {
- // Add the file if its path looks like it might be cache eligible. This is
- // better than getting no errors at all later
- if ( inputFile.hasError() && Header::isSharedCacheEligiblePath(inputFile.path.data()) ) {
- allDylibs.insert({ inputFile.path, &inputFile });
- badDylibs.insert(inputFile.path);
- }
- continue;
- }
-
if ( inputFile.mf->isDylib() )
- allDylibs.insert({ ((const Header*)inputFile.mf)->installName(), &inputFile });
+ allDylibs.insert(inputFile.mf->installName());
}
__block std::unordered_set<std::string_view> potentialCacheDylibs;
@@ -910,10 +546,8 @@
for ( const CacheDylib& cacheDylib : this->cacheDylibs )
potentialCacheDylibs.insert(cacheDylib.installName);
-
- // Keep track of all errors in case we need them to diagnose libsystem being missing
- __block std::unordered_map<std::string_view, std::list<std::string>> missingDependencies;
- __block std::unordered_map<std::string_view, std::list<std::string>> ineligibleDependencies;
+ __block std::unordered_set<std::string_view> badDylibs;
+
// check all dependencies to assure every dylib in cache only depends on other dylibs in cache
__block bool doAgain = true;
@@ -921,8 +555,6 @@
doAgain = false;
// scan dylib list making sure all dependents are in dylib list
for ( const CacheDylib& cacheDylib : this->cacheDylibs ) {
- if ( cacheDylib.inputFile == nullptr ) continue;
-
//Timer::Scope timedScope(this->config, cacheDylib.installName);
// Skip dylibs we marked bad from a previous iteration
if ( cacheDylib.inputFile->hasError() )
@@ -940,103 +572,37 @@
}
}
}
-
- // Break weak edges, but only if we haven't seen the dylib.
- if ( isWeak && (allDylibs.count(loadPath) == 0) )
- return;
- if ( isWeak && allowedMissingWeakDylibs.count(loadPath) )
- return;
-
- if ( badDylibs.count(loadPath) ) {
- std::string reason = std::string("Depends on ineligible/bad dylib '") + loadPath + "'";
- cacheDylib.inputFile->addError(Error("%s", reason.c_str()));
+ if ( potentialCacheDylibs.count(loadPath) == 0 ) {
+ // Break weak edges, but only if we haven't seen the dylib.
+ if ( isWeak && (allDylibs.count(loadPath) == 0) )
+ return;
+ if ( isWeak && allowedMissingWeakDylibs.count(loadPath) )
+ return;
+ std::string reason = std::string("Could not find dependency '") + loadPath + "'";
+ cacheDylib.inputFile->setError(Error("%s", reason.c_str()));
badDylibs.insert(cacheDylib.installName);
- ineligibleDependencies[cacheDylib.installName].push_back(loadPath);
doAgain = true;
+ stop = true;
return;
}
- if ( potentialCacheDylibs.count(loadPath) == 0 ) {
- std::string reason = std::string("Could not find dependency '") + loadPath + "'";
- cacheDylib.inputFile->addError(Error("%s", reason.c_str()));
+ if ( badDylibs.count(loadPath) ) {
+ // Break weak edges, but only if we haven't seen the dylib.
+ if ( isWeak && (allDylibs.count(loadPath) == 0) )
+ return;
+ std::string reason = std::string("Depends on ineligible/bad dylib '") + loadPath + "'";
+ cacheDylib.inputFile->setError(Error("%s", reason.c_str()));
badDylibs.insert(cacheDylib.installName);
- missingDependencies[cacheDylib.installName].push_back(loadPath);
doAgain = true;
+ stop = true;
return;
}
});
}
}
- // If libsystem is bad, give up, but try make a better error
- for ( const CacheDylib& cacheDylib : this->cacheDylibs ) {
- if ( cacheDylib.inputFile == nullptr )
- continue;
-
- if ( !cacheDylib.inputFile->hasError() )
- continue;
-
- bool isLibSystem = cacheDylib.installName.ends_with("/usr/lib/libSystem.dylib") || cacheDylib.installName.ends_with("/usr/lib/libSystem.B.dylib");
- if ( !isLibSystem )
- continue;
-
- // Walk the dependency tree to try find the real error which caused us to fail
- std::unordered_set<std::string> seenFiles;
- std::list<std::string> worklist;
- worklist.push_back(std::string(cacheDylib.installName));
- while ( !worklist.empty() ) {
- std::string dylibInstallName = worklist.back();
- worklist.pop_back();
-
- // Check if the dylib depends on something missing
- // If so, we've found an issue
- if ( auto it = missingDependencies.find(dylibInstallName); it != missingDependencies.end() ) {
- for ( auto depInstallName : it->second ) {
- std::string reason = "Dylib located at '";
- reason += dylibInstallName;
- reason += "' not placed in shared cache because: ";
- reason += "Could not find dependency '";
- reason += depInstallName;
- reason += "'";
- this->errors.push_back(reason);
- }
- }
-
- // Add bad dependencies to the worklist, hoping to eventually walk to the root cause
- if ( auto dylibIt = ineligibleDependencies.find(dylibInstallName); dylibIt != ineligibleDependencies.end() ) {
- for ( auto depInstallName : dylibIt->second ) {
- if ( seenFiles.count(depInstallName) )
- continue;
-
- seenFiles.insert(depInstallName);
- worklist.push_back(depInstallName);
-
- if ( auto depIt = allDylibs.find(depInstallName); depIt != allDylibs.end() ) {
- const InputFile* inputFile = depIt->second;
- // Walk the errors on this depenency, skip deps which are just more bad dylibs, and see if we can
- // find the root cause
- for ( const error::Error& err : inputFile->getErrors() ) {
- if ( err.messageContains("Depends on ineligible/bad dylib") )
- continue;
-
- std::string reason = "Dylib located at '";
- reason += depInstallName;
- reason += "' not placed in shared cache because: ";
- reason += err.message();
- this->errors.push_back(reason);
- }
- }
- }
- }
- }
-
- cacheDylibs.clear();
- }
-
// Add bad dylibs to the "other" dylibs for use in prebuilt loaders
for ( const CacheDylib& cacheDylib : this->cacheDylibs ) {
- if ( cacheDylib.inputFile == nullptr ) continue;
-
if ( cacheDylib.inputFile->hasError() ) {
this->nonCacheDylibInputFiles.push_back(cacheDylib.inputFile);
this->dylibHasMissingDependency = true;
@@ -1045,18 +611,9 @@
this->cacheDylibs.erase(std::remove_if(this->cacheDylibs.begin(), this->cacheDylibs.end(), [&](const CacheDylib& dylib) {
// Dylibs with errors must be removed from the cache
- return dylib.inputFile != nullptr && dylib.inputFile->hasError();
+ return dylib.inputFile->hasError();
}),
this->cacheDylibs.end());
-
- // verify that there's at least one dylib that has an input file
- if ( !std::any_of(cacheDylibs.begin(), cacheDylibs.end(), [](const CacheDylib& dylib) {
- return dylib.inputFile != nullptr;
- }) ) {
- // the only remaining dylib is the synthesized Swift prespecialized dylib
- // so remove it too
- cacheDylibs.clear();
- }
}
void SharedCacheBuilder::calculateDylibAliases()
@@ -1078,38 +635,39 @@
{
Timer::Scope timedScope(this->config, "sortDylibs time");
- auto sortOrder = [this](const CacheDylib& dylib) -> uint64_t {
+ std::sort(this->cacheDylibs.begin(), this->cacheDylibs.end(), [&](const CacheDylib& a, const CacheDylib& b) {
// HACK: See addObjCOptimizationsToSubCache() and addObjCTextChunk()
// We put the libobjc __TEXT first in the sub cache so that offsets from it to OBJC_RO are
// positive. But dyld4 and objc HeaderInfo data structures rely on the cache dylibs being
// sorted by mach_header, and moving objc first breaks the order we determine here. So hack
// this too and put libobjc first for now.
- if ( endsWith(dylib.installName, "/usr/lib/libobjc.A.dylib") )
- return (1ULL << 32);
- // rdar://147457741 (dyld shared cache builder should guarnatee that dyld and libdyld end up in the same split cache)
- if ( endsWith(dylib.installName, "/usr/lib/system/libdyld.dylib") )
- return (2ULL << 32);
- if ( endsWith(dylib.installName, "/usr/lib/dyld") )
- return (3ULL << 32);
-
- // fixed order first
- if ( const auto& fixedOrder = options.dylibOrdering.find(std::string(dylib.installName)); fixedOrder != options.dylibOrdering.end() )
- return (4ULL << 32) + fixedOrder->second;
+ bool isObjCA = (a.installName == "/usr/lib/libobjc.A.dylib");
+ bool isObjCB = (b.installName == "/usr/lib/libobjc.A.dylib");
+ if ( isObjCA != isObjCB )
+ return isObjCA;
+
+ const auto& orderA = options.dylibOrdering.find(std::string(a.installName));
+ const auto& orderB = options.dylibOrdering.find(std::string(b.installName));
+ bool foundA = (orderA != options.dylibOrdering.end());
+ bool foundB = (orderB != options.dylibOrdering.end());
+
+ // Order all __DATA_DIRTY segments specified in the order file first, in
+ // the order specified in the file, followed by any other __DATA_DIRTY
+ // segments in lexicographic order.
+ if ( foundA && foundB )
+ return orderA->second < orderB->second;
+ else if ( foundA )
+ return true;
+ else if ( foundB )
+ return false;
// Sort mac before iOSMac
- if ( startsWith(dylib.installName, "/System/iOSSupport/") )
- return (6ULL << 32);
- return (5ULL << 32);
- };
-
- std::sort(this->cacheDylibs.begin(), this->cacheDylibs.end(), [&](const CacheDylib& a, const CacheDylib& b) {
- uint64_t aOrder = sortOrder(a);
- uint64_t bOrder = sortOrder(b);
-
- if ( aOrder != bOrder )
- return aOrder < bOrder;
-
- // Finally sort by install name if there's no explicit ordering
+ bool isIOSMacA = startsWith(a.installName, "/System/iOSSupport/");
+ bool isIOSMacB = startsWith(b.installName, "/System/iOSSupport/");
+ if ( isIOSMacA != isIOSMacB )
+ return !isIOSMacA;
+
+ // Finally sort by install name
return a.installName < b.installName;
});
@@ -1117,46 +675,6 @@
uint32_t cacheIndex = 0;
for ( CacheDylib& cacheDylib : cacheDylibs )
cacheDylib.cacheIndex = cacheIndex++;
-}
-
-void SharedCacheBuilder::sortExecutables()
-{
- Timer::Scope timedScope(this->config, "sortExecutables time");
-
- // make a list of the position on all binaries for anything we don't explicitly sort
- std::unordered_map<const InputFile*, uint32_t> originalPositions;
- for ( const InputFile* file : this->exeInputFiles )
- originalPositions[file] = (uint32_t)originalPositions.size();
-
- // TODO: Use a file from the perf teams if we have one
- auto sortOrder = [&originalPositions](const InputFile* file) -> uint64_t {
- uint32_t originalPos = originalPositions.at(file);
-
- // Sort some paths first
- if ( file->path.starts_with("/usr/libexec/") )
- return (1ULL << 32) + originalPos;
- if ( file->path.starts_with("/Applications/") )
- return (2ULL << 32) + originalPos;
- if ( file->path.starts_with("/private/var/staged_system_apps/") )
- return (3ULL << 32) + originalPos;
- if ( file->path.starts_with("/usr/bin/") )
- return (4ULL << 32) + originalPos;
-
- // Sort some other paths last
- if ( file->path.starts_with("/AppleInternal/") )
- return (62ULL << 32) + originalPos;
- if ( file->path.starts_with("/usr/local/bin/") )
- return (63ULL << 32) + originalPos;
-
- // Any path not in the lists above gets sorted by original position
- return (32ULL << 32) + originalPos;
- };
-
- // Just in case we overflow the buffer in emitExecutablePrebuiltLoaders(), sort the exe files to prioritize
- // daemons and other common apps
- std::sort(this->exeInputFiles.begin(), this->exeInputFiles.end(), [&](const InputFile* a, const InputFile* b) {
- return sortOrder(a) < sortOrder(b);
- });
}
Error SharedCacheBuilder::calculateDylibDependents()
@@ -1210,30 +728,6 @@
stop = true;
}
});
-
- // copy the original list of dependents
- cacheDylib.inputDependents = cacheDylib.dependents;
-
- // note: below changes to dependents need to be kept in sync with load command patching in `patchLinkedDylibs`
- // we might want to generalize that if more libraries require patching
-
- // force swiftCore link the prespecialized dylib
- if ( swiftPrespecializedDylib && cacheDylib.installName.find("libswiftCore.dylib") != std::string_view::npos ) {
- CacheDylib::DependentDylib depDylib;
- depDylib.kind = CacheDylib::DependentDylib::Kind::normal;
- depDylib.dylib = swiftPrespecializedDylib;
- cacheDylib.dependents.push_back(std::move(depDylib));
- }
-
- // clear all dependents of the prespecialized dylib except libSystem
- // otherwise loading the library would pull in lots of other dependencies
- if ( swiftPrespecializedDylib && &cacheDylib == swiftPrespecializedDylib ) {
- if ( cacheDylib.dependents.empty() || cacheDylib.dependents.front().dylib->installName.find("libSystem") == std::string_view::npos ) {
- diag.error("expected libSystem as the first linked dylib of %s", cacheDylib.inputHdr->installName());
- } else {
- cacheDylib.dependents.erase(cacheDylib.dependents.begin()+1, cacheDylib.dependents.end());
- }
- }
if ( diag.hasError() )
return Error("%s", diag.errorMessageCStr());
@@ -1366,39 +860,36 @@
}
static void forEachObjCMethodName(const CacheDylib& cacheDylib,
- void (^callback)(const char* str))
-{
- __block objc_visitor::Visitor objcVisitor = makeInputDylibObjCVisitor(cacheDylib);
-
- auto visitPointerBasedMethod = ^(const objc_visitor::Method& method) {
- const char* selectorString = method.getName(objcVisitor);
- callback(selectorString);
- };
-
- auto visitMethodList = ^(const objc_visitor::MethodList& methodList) {
- if ( methodList.numMethods() == 0 )
+ void (^callback)(std::string_view str))
+{
+ const MachOFile* mf = cacheDylib.inputMF;
+ mf->forEachSection(^(const MachOFile::SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
+ if ( strcmp(sectInfo.segInfo.segName, "__TEXT") != 0 )
return;
- if ( methodList.usesRelativeOffsets() )
+ if ( strcmp(sectInfo.sectName, "__objc_methname") != 0 )
return;
-
- // Check pointer based method lists
- uint32_t numMethods = methodList.numMethods();
- for ( uint32_t i = 0; i != numMethods; ++i ) {
- const objc_visitor::Method& method = methodList.getMethod(objcVisitor, i);
- visitPointerBasedMethod(method);
- }
- };
-
- // selector references
- objcVisitor.forEachSelectorReference(^(VMAddress selRefVMAddr, VMAddress selRefTargetVMAddr,
- const char* selectorString) {
- callback(selectorString);
- });
-
- // classes/categories/protocols/swift generics
- objcVisitor.forEachMethodList(^(objc_visitor::MethodList& objcMethodList,
- std::optional<metadata_visitor::ResolvedValue> extendedMethodTypes) {
- visitMethodList(objcMethodList);
+ if ( sectInfo.segInfo.isProtected || ((sectInfo.sectFlags & SECTION_TYPE) != S_CSTRING_LITERALS) ) {
+ stop = true;
+ return;
+ }
+ if ( malformedSectionRange ) {
+ stop = true;
+ return;
+ }
+
+ // Use the file offset in the section to get the correct content
+ const char* content = (const char*)mf + sectInfo.sectFileOffset;
+ uint64_t sectionSize = sectInfo.sectSize;
+
+ const char* s = (const char*)content;
+ const char* end = s + sectionSize;
+ while ( s < end ) {
+ std::string_view str = s;
+ callback(str);
+ s += str.size() + 1;
+ }
+
+ stop = true;
});
}
@@ -1408,7 +899,7 @@
VMOffset offsetInDylib;
};
-static FoundSymbol findTargetClass(Diagnostics& diag,
+static FoundSymbol findTargetClass(Diagnostics diag,
const std::vector<CacheDylib>& cacheDylibs,
std::string_view symbolName, std::optional<uint32_t> cacheIndex)
{
@@ -1442,17 +933,14 @@
void SharedCacheBuilder::estimateIMPCaches()
{
- // Only LP64 is supported by the runtime
if ( !this->config.layout.is64 )
return;
- // Limited by ImpCacheEntry_v2::impOffset which is 38-bits. For now limit to 16GB
- // as that is the maximum we know slide info v5 can get to
- if ( this->config.layout.cacheSize.rawValue() > 16_GB )
+ if ( this->config.layout.cacheSize.rawValue() > 0x100000000 )
return;
- // Only arm64* are is supported by the runtime
- if ( !this->options.arch.sameCpu(mach_o::Architecture::arm64) )
+ // Only iOS for now
+ if ( this->options.platform != dyld3::Platform::iOS )
return;
// Skip everything if the JSON file is empty
@@ -1474,27 +962,6 @@
// diag.warning("libobjc's magical IMP caches shared cache offsets list section missing (metadata not optimized)");
return;
}
-
- // Also find the _objc_opt_preopt_caches_version symbol, which has the IMP caches version
- __block Diagnostics diag;
- cacheDylib.inputMF->withFileLayout(diag, ^(const mach_o::Layout &layout) {
- mach_o::Layout::FoundSymbol foundInfo;
- if ( !layout.findExportedSymbol(diag, "_objc_opt_preopt_caches_version", false, foundInfo) )
- return;
-
- // We only support header offsets in this dylib, as we are looking for self binds
- // which are likely only to classes
- if ( foundInfo.kind != mach_o::Layout::FoundSymbol::Kind::headerOffset )
- return;
-
- uint64_t vmAddr = layout.textUnslidVMAddr() + foundInfo.value;
-
- __block objc_visitor::Visitor objcVisitor = makeInputDylibObjCVisitor(cacheDylib);
- metadata_visitor::ResolvedValue value = objcVisitor.getValueFor(VMAddress(vmAddr));
- this->objcIMPCachesOptimizer.libobjcImpCachesVersion = *(int*)value.value();
- });
- if ( diag.hasError() )
- return;
}
// Find all the objc dylibs, classes, categories
@@ -1540,9 +1007,6 @@
});
objcVisitor.forEachCategory(^(const objc_visitor::Category& objcCategory, bool& stopCategory) {
- if ( objcCategory.isForSwiftStubClass() )
- return;
-
imp_caches::Category impCacheCategory(objcCategory.getName(objcVisitor));
// instance methods
@@ -1610,7 +1074,6 @@
bool isWeakImport = false;
};
- auto& dylibMethodMap = objcIMPCachesOptimizer.methodMap[cacheDylib.installName];
__block std::vector<BindTarget> bindTargets;
__block Diagnostics diag;
cacheDylib.inputMF->withFileLayout(diag, ^(const mach_o::Layout& layout) {
@@ -1701,6 +1164,7 @@
objc_visitor::Method objcMethod = objcMethodList.getMethod(objcVisitor, i);
imp_caches::BucketMethod methodKey = {
+ .installName = cacheDylib.installName,
.className = impCacheClass.name,
.methodName = objcMethod.getName(objcVisitor),
.isInstanceMethod = !impCacheClass.isMetaClass
@@ -1711,7 +1175,7 @@
&cacheDylib,
InputDylibVMAddress(impVMAddr.rawValue())
};
- dylibMethodMap[methodKey] = inputDylibLocation;
+ objcIMPCachesOptimizer.methodMap[methodKey] = inputDylibLocation;
}
}
@@ -1721,9 +1185,6 @@
// Walk each category and set the class pointer
__block uint32_t categoryIndex = 0;
objcVisitor.forEachCategory(^(const objc_visitor::Category& objcCategory, bool& stopCategory) {
- if ( objcCategory.isForSwiftStubClass() )
- return;
-
imp_caches::Category& impCacheCategory = dylib.categories[categoryIndex];
const DylibClasses& classMap = dylibClassMaps[cacheDylib.cacheIndex];
@@ -1776,6 +1237,7 @@
objc_visitor::Method objcMethod = objcMethodList.getMethod(objcVisitor, i);
imp_caches::BucketMethod methodKey = {
+ .installName = cacheDylib.installName,
.className = impCacheCategory.cls->name,
.methodName = objcMethod.getName(objcVisitor),
.isInstanceMethod = true
@@ -1785,7 +1247,7 @@
&cacheDylib,
InputDylibVMAddress(impVMAddr.rawValue())
};
- dylibMethodMap[methodKey] = inputDylibLocation;
+ objcIMPCachesOptimizer.methodMap[methodKey] = inputDylibLocation;
}
}
@@ -1797,6 +1259,7 @@
objc_visitor::Method objcMethod = objcMethodList.getMethod(objcVisitor, i);
imp_caches::BucketMethod methodKey = {
+ .installName = cacheDylib.installName,
.className = impCacheCategory.cls->name,
.methodName = objcMethod.getName(objcVisitor),
.isInstanceMethod = false
@@ -1806,7 +1269,7 @@
&cacheDylib,
InputDylibVMAddress(impVMAddr.rawValue())
};
- dylibMethodMap[methodKey] = inputDylibLocation;
+ objcIMPCachesOptimizer.methodMap[methodKey] = inputDylibLocation;
}
}
}
@@ -1821,10 +1284,6 @@
// We should probably keep the piece here to walk the classes as that can perhaps give us a good estimate of the size of the IMP caches
// themselves, minus the strings which need their own buffer
this->objcIMPCachesOptimizer.builder->buildImpCaches();
-
- for ( const std::string& warning : this->objcIMPCachesOptimizer.builder->diags.warnings() ) {
- this->warning("IMP caches: %s", warning.data());
- }
// Push all the IMP cache selectors in to the main selectors buffer.
// We could try have an IMP cache selectors buffer and a regular selectors buffer, but that complicates
@@ -1855,7 +1314,6 @@
// Add space for the IMP caches themselves
this->objcIMPCachesOptimizer.dylibIMPCaches.resize(dylibs.size());
- size_t discardedCaches = 0;
for ( uint32_t dylibIndex = 0; dylibIndex != dylibs.size(); ++dylibIndex ) {
imp_caches::Dylib& dylib = dylibs[dylibIndex];
ObjCIMPCachesOptimizer::IMPCacheMap& dylibIMPCaches = objcIMPCachesOptimizer.dylibIMPCaches[dylibIndex];
@@ -1864,24 +1322,6 @@
if ( !impCache.has_value() )
continue;
- // rdar://147219191 (Selector bucket overflows in IMP caches result in malformed tables)
- // validate selector offsets point to matching buckets
- bool discard = false;
- for ( uint32_t slot = 0; slot < impCache->buckets.size(); ++slot ) {
- const imp_caches::Bucket& bucket = impCache->buckets[slot];
- if ( !bucket.isEmptyBucket ) {
- uint32_t expectedSlot = (bucket.selOffset >> impCache->cache_shift) & impCache->cache_mask;
- if ( expectedSlot != slot ) {
- discard = true;
- break;
- }
- }
- }
- if ( discard ) {
- ++discardedCaches;
- continue;
- }
-
VMOffset currentOffset((uint64_t)this->objcIMPCachesOptimizer.impCachesTotalByteSize);
assert((this->objcIMPCachesOptimizer.impCachesTotalByteSize % 8) == 0);
this->objcIMPCachesOptimizer.impCachesTotalByteSize += sizeof(ImpCacheHeader_v2);
@@ -1893,10 +1333,6 @@
}
}
- if ( discardedCaches != 0 ) {
- this->warning("[ERROR] discarded IMP caches: %lu", discardedCaches);
- }
-
if ( this->config.log.printStats ) {
stats.add(" objc: found %lld imp cache selectors\n", (uint64_t)this->objcSelectorOptimizer.selectorsMap.size());
stats.add(" objc: using %lld bytes\n", this->objcSelectorOptimizer.selectorStringsTotalByteSize);
@@ -1907,7 +1343,7 @@
void SharedCacheBuilder::findObjCDylibs()
{
// driverKit has no objc
- if ( this->options.platform == Platform::driverKit )
+ if ( this->options.platform == dyld3::Platform::driverKit )
return;
Stats stats(this->config);
@@ -1915,9 +1351,7 @@
assert(this->objcOptimizer.objcDylibs.empty());
for ( CacheDylib& cacheDylib : this->cacheDylibs ) {
- if ( cacheDylib.inputHdr == nullptr ) continue;
-
- if ( cacheDylib.inputHdr->hasObjC() )
+ if ( cacheDylib.inputMF->hasObjC() )
this->objcOptimizer.objcDylibs.push_back(&cacheDylib);
}
@@ -1938,8 +1372,6 @@
this->objcOptimizer.headerInfoReadWriteByteSize += (uint32_t)this->objcOptimizer.objcDylibs.size() * sizeof(ObjCOptimizer::header_info_rw_32_t);
}
- this->objcOptimizer.imageInfoSize = this->objcOptimizer.objcDylibs.size() * sizeof(objc::objc_image_info);
-
if ( this->config.log.printStats ) {
stats.add(" objc: found %lld objc dylibs\n", (uint64_t)this->objcOptimizer.objcDylibs.size());
}
@@ -1958,19 +1390,11 @@
Error err = parallel::forEach(this->cacheDylibs, ^(size_t index, CacheDylib& cacheDylib) {
std::vector<std::string_view>& strings = dylibSelectors[index];
- __block std::unordered_set<const void*> seenStrings;
- forEachObjCMethodName(cacheDylib, ^(const char* str) {
- auto itAndInserted = seenStrings.insert(str);
- if ( itAndInserted.second )
- strings.push_back(str);
+ forEachObjCMethodName(cacheDylib, ^(std::string_view str) {
+ strings.push_back(std::move(str));
});
- // sort strings as we used to walk the __objc_methname section in order, so by sorting we
- // should get close to possible to the old order
- std::sort(strings.begin(), strings.end(), [](const std::string_view& a, const std::string_view& b) {
- // note pointer comparison as we want the string location in the binary not its contents
- return a.data() < b.data();
- });
+ // FIXME: Walk selector references, classes, categories, protocols, etc
return Error();
});
@@ -2327,175 +1751,12 @@
}
}
-// Walk all the dylibs and build a map of ObjC categories
-void SharedCacheBuilder::findObjCCategories()
-{
- if ( this->objcOptimizer.objcDylibs.empty() )
- return;
-
- Stats stats(this->config);
- Timer::Scope timedScope(this->config, "findObjCCategories time");
-
- // Reserve space for 15k categories, as we have 10k as of writing
- const uint32_t numCategoriesToReserve = 1 << 14;
- this->objcCategoryOptimizer.categories.reserve(numCategoriesToReserve);
- size_t objcIndex = 0;
- for (size_t cacheIndex = 0; cacheIndex < this->cacheDylibs.size(); cacheIndex++) {
- CacheDylib& cacheDylib = this->cacheDylibs[cacheIndex];
- if ( !cacheDylib.inputHdr->hasObjC() )
- continue;
-
- // Skip dylibs with opcode fixups, as the Category visitor operates on chained fixups to find classes
- if ( cacheDylib.inputMF->hasOpcodeFixups() ) {
- this->objcCategoryOptimizer.excludedDylibs.insert(objcIndex);
- objcIndex++;
- continue;
- }
- struct BindTarget {
- std::string_view symbolName;
- std::optional<uint32_t> targetDylibIndex;
- bool isWeakImport = false;
- };
- __block std::vector<BindTarget> bindTargets;
- __block Diagnostics diag;
- cacheDylib.inputMF->withFileLayout(diag, ^(const mach_o::Layout& layout) {
- mach_o::Fixups fixups(layout);
-
- fixups.forEachBindTarget(diag, false, 0, ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
- if ( info.libOrdinal == BIND_SPECIAL_DYLIB_SELF ) {
- bindTargets.push_back({ info.symbolName, cacheDylib.cacheIndex, info.weakImport });
- } else if ( info.libOrdinal < 0 ) {
- // A special ordinal such as weak. Just put in a placeholder for now
- bindTargets.push_back({ info.symbolName, std::nullopt, info.weakImport });
- } else {
- assert(info.libOrdinal <= (int)cacheDylib.dependents.size());
- const CacheDylib *targetDylib = cacheDylib.dependents[info.libOrdinal-1].dylib;
- assert(info.weakImport || (targetDylib != nullptr));
- std::optional<uint32_t> targetDylibIndex;
- if ( targetDylib != nullptr )
- targetDylibIndex = targetDylib->cacheIndex;
- bindTargets.push_back({ info.symbolName, targetDylibIndex, info.weakImport });
- }
-
- if ( diag.hasError() )
- stop = true;
- }, ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
- // This shouldn't happen with chained fixups
- assert(0);
- });
- });
- diag.assertNoError();
-
- __block objc_visitor::Visitor objCVisitor = makeInputDylibObjCVisitor(cacheDylib);
-
- __block bool categoriesHaveClassProperties = false;
- objCVisitor.withImageInfo(^(const uint32_t version, const uint32_t flags) {
- const uint64_t hasCategoryClassPropertiesFlag = (1 << 6);
- categoriesHaveClassProperties = (flags & hasCategoryClassPropertiesFlag);
- });
-
- objCVisitor.forEachCategory(^(const objc_visitor::Category &objcCategory, bool &stopCategory) {
-
- // Skip catlist2 entries. These are only for Swift stub classes
- if ( objcCategory.isForSwiftStubClass() )
- return;
-
- __block ObjCCategoryOptimizer::Category objCCategoryInfo(objcCategory.getName(objCVisitor));
- objCCategoryInfo.dylibObjcIndex = objcIndex;
- objCCategoryInfo.vmAddress = objcCategory.getVMAddress();
-
- objcCategory.withClass(objCVisitor, ^(const dyld3::MachOFile::ChainedFixupPointerOnDisk *fixup, uint16_t pointerFormat) {
- assert(fixup->raw64 != 0);
-
- uint64_t runtimeOffset = 0;
- if ( fixup->isRebase(pointerFormat, objCVisitor.getOnDiskDylibChainedPointerBaseAddress().rawValue(), runtimeOffset) ) {
- // Rebase to a class in this image.
- objCCategoryInfo.classDylibIndex = cacheDylib.cacheIndex;
- objCCategoryInfo.classVMAddress = VMAddress(runtimeOffset);
- } else {
- uint32_t bindOrdinal = 0;
- int64_t bindAddend = 0;
- if ( fixup->isBind(pointerFormat, bindOrdinal, bindAddend) ) {
- BindTarget& bindTarget = bindTargets[bindOrdinal];
- FoundSymbol foundSymbol = findTargetClass(diag, this->cacheDylibs,
- bindTarget.symbolName,
- bindTarget.targetDylibIndex);
- if ( foundSymbol.foundInDylib == nullptr ) {
- // Ignore category if class is missing. Usually due to a weak-link
- if ( !bindTarget.isWeakImport ) {
- this->warning("Class %s could not be found for category %s in %s.", bindTarget.symbolName.data(), objCCategoryInfo.name.data(), cacheDylib.installName.data());
- }
- return;
- }
- objCCategoryInfo.classVMAddress = VMAddress(foundSymbol.offsetInDylib.rawValue());
- objCCategoryInfo.classDylibIndex = foundSymbol.foundInDylib->cacheIndex;
- } else {
- assert(0);
- }
- }
- });
-
- if ( !objCCategoryInfo.classVMAddress.has_value() )
- return;
-
- // instance methods
- {
- objc_visitor::MethodList objcMethodList = objcCategory.getInstanceMethods(objCVisitor);
- uint32_t numMethods = objcMethodList.numMethods();
- if ( numMethods > 0 )
- objCCategoryInfo.iMethodListVMAddress = objcMethodList.getVMAddress().value();
- }
-
- // class methods
- {
- objc_visitor::MethodList objcMethodList = objcCategory.getClassMethods(objCVisitor);
- uint32_t numMethods = objcMethodList.numMethods();
- if ( numMethods > 0 )
- objCCategoryInfo.cMethodListVMAddress = objcMethodList.getVMAddress().value();
- }
-
- // protocols
- {
- objc_visitor::ProtocolList protocols = objcCategory.getProtocols(objCVisitor);
- uint64_t numProtocols = protocols.numProtocols(objCVisitor);
- if ( numProtocols > 0 )
- objCCategoryInfo.protocolListVMAddress = protocols.getVMAddress().value();
- }
-
- // instance properties
- {
- objc_visitor::PropertyList objcPropertyList = objcCategory.getInstanceProperties(objCVisitor);
- uint64_t numProperties = objcPropertyList.numProperties();
- if ( numProperties > 0 )
- objCCategoryInfo.iPropertyListVMAddress = objcPropertyList.getVMAddress().value();
- }
-
- // class properties
- if ( categoriesHaveClassProperties ) {
- objc_visitor::PropertyList objcPropertyList = objcCategory.getClassProperties(objCVisitor);
- uint64_t numProperties = objcPropertyList.numProperties();
- if ( numProperties > 0 )
- objCCategoryInfo.cPropertyListVMAddress = objcPropertyList.getVMAddress().value();
- }
- this->objcCategoryOptimizer.categories.push_back(std::move(objCCategoryInfo));
- });
- objcIndex++;
- }
-
- if ( this->config.log.printStats ) {
- stats.add(" objc: found %lld categories\n", (uint64_t)this->objcCategoryOptimizer.categories.size());
- }
-}
-
static uint32_t hashTableSize(uint32_t maxElements, uint32_t perElementData)
{
uint32_t elementsWithPadding = maxElements * 11 / 10; // if close to power of 2, perfect hash may fail, so don't get within 10% of that
uint32_t powTwoCapacity = 1 << (32 - __builtin_clz(elementsWithPadding - 1));
uint32_t headerSize = 4 * (8 + 256);
- uint32_t totalSize = headerSize + powTwoCapacity / 2 + powTwoCapacity + powTwoCapacity * perElementData;
-
- // Add in a little extra room for very small tables when we are building unit tests
- return std::max(totalSize, 4096U);
+ return headerSize + powTwoCapacity / 2 + powTwoCapacity + powTwoCapacity * perElementData;
}
void SharedCacheBuilder::estimateObjCHashTableSizes()
@@ -2549,71 +1810,6 @@
}
}
-void SharedCacheBuilder::calculateObjCCategoriesSize()
-{
- if ( this->objcOptimizer.objcDylibs.empty() )
- return;
-
- Stats stats(this->config);
- Timer::Scope timedScope(this->config, "calculateObjCCanonicalCategoriesSize time");
-
- uint64_t sizeAndCountSize = sizeof(struct ListOfListsEntry);
- uint64_t listEntrySize = sizeof(struct ListOfListsEntry);
-
- // Add an empty method list that all lists of lists can use if the need it
- // This is used for classes when they don't have method lists, but we want them to
- // FIXME: Get the size from somewhere. Its really the { uint32_t entsize; uint32_t count }
- this->objcCategoryOptimizer.categoriesTotalByteSize += 8;
-
- // This will allocate one listEntrySize for each category list.
- // We might end up using less if the category does not extend a specific list.
- // This will also allocate one listEntrySize and one sizeAndCountSize
- // for every list in the original class.
- // We might end up using less due to multiple categories extending the same class.
- for ( auto& categoryInfo : this->objcCategoryOptimizer.categories ) {
-
- if ( categoryInfo.iMethodListVMAddress.has_value()) {
- // instance methods
- this->objcCategoryOptimizer.categoriesTotalByteSize += sizeAndCountSize;
- this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
- // original instance methods
- this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
- }
- if ( categoryInfo.cMethodListVMAddress.has_value()) {
- // class methods
- this->objcCategoryOptimizer.categoriesTotalByteSize += sizeAndCountSize;
- this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
- // original class methods
- this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
- }
- if ( categoryInfo.protocolListVMAddress.has_value()) {
- // protocols
- this->objcCategoryOptimizer.categoriesTotalByteSize += sizeAndCountSize;
- this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
- // original protocols
- this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
- }
- if ( categoryInfo.iPropertyListVMAddress.has_value()) {
- // instance properties
- this->objcCategoryOptimizer.categoriesTotalByteSize += sizeAndCountSize;
- this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
- // original instance properties
- this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
- }
- if ( categoryInfo.cPropertyListVMAddress.has_value()) {
- // instance properties
- this->objcCategoryOptimizer.categoriesTotalByteSize += sizeAndCountSize;
- this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
- // original class properties
- this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
- }
- }
-
- if ( this->config.log.printStats ) {
- stats.add(" objc: categories size: %lld\n", (uint64_t)this->objcCategoryOptimizer.categoriesTotalByteSize);
- }
-}
-
// Each conformance entry is 3 uint64_t's internally, plus the space for the hash table
static uint32_t swiftHashTableSize(uint32_t maxElements)
{
@@ -2632,11 +1828,6 @@
return hashTableSize + (3 * sizeof(uint64_t) * maxElements);
}
-static uint32_t ptrHashTableSize(uint32_t maxElement, uint32_t numPointerKeys)
-{
- return swiftHashTableSize(maxElement) + numPointerKeys * sizeof(uint64_t);
-}
-
void SharedCacheBuilder::estimateSwiftHashTableSizes()
{
if ( this->objcOptimizer.objcDylibs.empty() )
@@ -2645,7 +1836,7 @@
Stats stats(this->config);
Timer::Scope timedScope(this->config, "estimateSwiftHashTableSizes time");
- this->swiftOptimizer.optsHeaderByteSize = sizeof(SwiftOptimizationHeader);
+ this->swiftProtocolConformanceOptimizer.optsHeaderByteSize = sizeof(SwiftOptimizationHeader);
__block uint32_t numTypeConformances = 0;
__block uint32_t numMetadataConformances = 0;
@@ -2691,29 +1882,7 @@
});
}
- auto& optimizer = this->swiftOptimizer;
-
- if ( swiftPrespecializedDylib ) {
- Diagnostics diagVal;
- Diagnostics& diag = diagVal;
- SwiftVisitor swiftVisitorVal = makeInputDylibSwiftVisitor(*swiftPrespecializedDylib);
- SwiftVisitor& swiftVisitor = swiftVisitorVal;
- swiftVisitor.forEachPointerHashTable(diag, ^(metadata_visitor::ResolvedValue sectionBase, size_t tableIndex, uint8_t *tableStart, size_t numEntries) {
- assert(optimizer.prespecializedMetadataHashTables.size() == tableIndex);
-
- PointerHashTableOptimizerInfo& tableInfo = optimizer.prespecializedMetadataHashTables.emplace_back();
- swiftVisitor.forEachPointerHashTableRelativeEntry(diag, tableStart, VMAddress(0ull), ^(size_t index, std::span<uint64_t> keys, uint64_t value) {
- assert(!keys.empty() && "pointer keys can't be empty");
-
- ++tableInfo.numEntries;
- tableInfo.numPointerKeys += (uint32_t)keys.size();
- });
-
- tableInfo.size = ptrHashTableSize(tableInfo.numEntries, tableInfo.numPointerKeys);
- assert(tableInfo.numEntries == numEntries);
- });
- }
-
+ auto& optimizer = this->swiftProtocolConformanceOptimizer;
optimizer.typeConformancesHashTableSize = swiftHashTableSize(numTypeConformances);
optimizer.metadataConformancesHashTableSize = swiftHashTableSize(numMetadataConformances);
optimizer.foreignTypeConformancesHashTableSize = swiftHashTableSize(numForeignConformances);
@@ -2723,12 +1892,6 @@
(uint64_t)optimizer.typeConformancesHashTableSize, numTypeConformances);
stats.add(" swift: metadata hash table estimated size: %lld (from %d entries)\n", (uint64_t)optimizer.metadataConformancesHashTableSize, numMetadataConformances);
stats.add(" swift: foreign metadata hash table estimated size: %lld (from %d entries)\n", (uint64_t)optimizer.foreignTypeConformancesHashTableSize, numForeignConformances);
-
- stats.add(" swift: prespecialized metadata hash tables %lu\n", optimizer.prespecializedMetadataHashTables.size());
- for ( int i = 0; i < optimizer.prespecializedMetadataHashTables.size(); ++i ) {
- const PointerHashTableOptimizerInfo& tableInfo = optimizer.prespecializedMetadataHashTables[i];
- stats.add(" swift: prespecialized metadata hash table #%d. estimated size: %lld (from %u entries)\n", i, (uint64_t)tableInfo.size, tableInfo.numEntries);
- }
}
}
@@ -2764,7 +1927,7 @@
// For each alias, also see if we have intermediate aliases
// This is the "Current -> A" symlink in say "/S/L/F/CF.fw/Current/CF"
- if ( this->options.platform == Platform::macOS ) {
+ if ( this->options.platform == dyld3::Platform::macOS ) {
for ( const cache_builder::FileAlias& alias : this->inputIntermediateAliases ) {
const auto& pos = dylibPathToDylibIndex.find(alias.realPath);
if ( pos != dylibPathToDylibIndex.end() ) {
@@ -2784,78 +1947,89 @@
}
}
-void SharedCacheBuilder::initializePatchTableOptimizer()
-{
+void SharedCacheBuilder::estimatePatchTableSize()
+{
+ Stats stats(this->config);
+ Timer::Scope timedScope(this->config, "estimatePatchTableSize time");
+
+ // The patch table consists of a series of arrays.
+ // For each dylib, we have a list of all clients of that dylib
+ // For each dylib we also have a list of used exports
+ // For each client we then have a list of symbols used
+ // And for each list of symbols, we have a list of locations to patch
+ // We need to estimate a patch table based on the above lists
+
+ __block uint32_t bindStringsLength = 0;
+ __block uint32_t numBindTargets = 0;
+ __block uint32_t numBinds = 0;
+ uint32_t numClients = 0;
+ for ( const CacheDylib& cacheDylib : this->cacheDylibs ) {
+ __block Diagnostics diag;
+ cacheDylib.inputMF->withFileLayout(diag, ^(const mach_o::Layout& layout) {
+ mach_o::Fixups fixups(layout);
+ fixups.forEachBindTarget(diag, true, 0,
+ ^(const mach_o::Fixups::BindTargetInfo& info, bool &stop) {
+ ++numBindTargets;
+ bindStringsLength += strlen(info.symbolName) + 1;
+ }, ^(const mach_o::Fixups::BindTargetInfo &info, bool &stop) {
+ ++numBindTargets;
+ bindStringsLength += strlen(info.symbolName) + 1;
+ });
+
+ if ( cacheDylib.inputMF->hasChainedFixups() ) {
+ fixups.withChainStarts(diag, ^(const dyld_chained_starts_in_image* starts) {
+ fixups.forEachFixupInAllChains(diag, starts, false,
+ ^(mach_o::ChainedFixupPointerOnDisk* fixupLoc,
+ uint64_t fixupSegmentOffset,
+ const dyld_chained_starts_in_segment* segInfo,
+ bool& stop) {
+ uint32_t bindOrdinal = ~0U;
+ int64_t addend = -1;
+ if ( fixupLoc->isBind(segInfo->pointer_format, bindOrdinal, addend) )
+ ++numBinds;
+ });
+ });
+ } else {
+ fixups.forEachBindLocation_Opcodes(diag,
+ ^(uint64_t runtimeOffset, uint32_t segmentIndex,
+ unsigned int targetIndex, bool &stop) {
+ ++numBinds;
+ }, ^(uint64_t runtimeOffset, uint32_t segmentIndex, unsigned int overrideBindTargetIndex,
+ bool &stop) {
+ ++numBinds;
+ });
+ }
+ });
+
+ numClients += (uint32_t)cacheDylib.dependents.size();
+ }
+
+ // Start with the patch header
+ uint64_t size = sizeof(dyld_cache_patch_info_v3);
+
+ // One of these for each dylib
+ size += sizeof(dyld_cache_image_patches_v2) * this->cacheDylibs.size();
+
+ // Estimate that 2/3 of exports are used
+ size += (sizeof(dyld_cache_image_export_v2) * numBindTargets * 2) / 3;
+ size += (bindStringsLength * 2) / 3;
+
+ // 1 entry per client
+ size += sizeof(dyld_cache_image_clients_v2) * numClients;
+
+ // 1 entry per bind target
+ size += sizeof(dyld_cache_patchable_export_v2) * numBindTargets;
+
+ // 1 entry per location we bind to
+ size += sizeof(dyld_cache_patchable_location_v2) * numBinds;
+
+ this->patchTableOptimizer.patchTableTotalByteSize = size;
+
// Reserve space for the patch infos, one per dylib
- // TODO: Just move these to the dylib. We don't really need them to be owned by the PatchTableOptimizer
this->patchTableOptimizer.patchInfos.resize(this->cacheDylibs.size());
-}
-
-void SharedCacheBuilder::estimateFunctionVariantsSize()
-{
- this->functionVariantsOptimizer.fvInfoTotalByteSize = sizeof(dyld_cache_function_variant_entry) * 256; // FIXME
-}
-
-void SharedCacheBuilder::estimatePrewarmingSize()
-{
- // Skip everything if the JSON file is empty
- if ( this->options.prewarmingOptimizations.empty() )
- return;
-
- using json::Node;
- using json::NodeValueType;
-
- Stats stats(this->config);
- Timer::Scope timedScope(this->config, "estimatePrewarmingSize time");
-
- Diagnostics diag;
- Node rootNode = json::readJSON(diag, this->options.prewarmingOptimizations.data(),
- this->options.prewarmingOptimizations.size(),
- false /* useJSON5 */);
- if ( diag.hasError() )
- return;
-
- // Format is something like:
- // [
- // {
- // "install_name": "..."
- // "locations": [
- // {
- // "name": "symbol name",
- // "offset": 0
- // }
- // ]
- // },
- // ...
- // ]
- if ( rootNode.type != NodeValueType::Array )
- return;
-
- uint64_t numElements = 0;
- for ( const Node& fileNode : rootNode.array ) {
- auto locationsIt = fileNode.map.find("locations");
- if ( locationsIt == fileNode.map.end() ) {
- // FIXME: Should we error out if the JSON isn't what we expect
- // For now just skip bad data
- continue;
- }
-
- if ( locationsIt->second.type != NodeValueType::Array ) {
- // FIXME: Should we error out if the JSON isn't what we expect
- // For now just skip bad data
- continue;
- }
-
- numElements += locationsIt->second.array.size();
- }
-
- this->prewarmingOptimizer.prewarmingByteSize = 0;
- this->prewarmingOptimizer.prewarmingByteSize += sizeof(dyld_prewarming_header);
- this->prewarmingOptimizer.prewarmingByteSize += numElements * sizeof(dyld_prewarming_entry);
- this->prewarmingOptimizer.prewarmingByteSize = (uint32_t)alignTo((uint64_t)this->prewarmingOptimizer.prewarmingByteSize, 8);
if ( this->config.log.printStats ) {
- stats.add(" dyld4 prewarming estimated size: %lld\n", (uint64_t)this->prewarmingOptimizer.prewarmingByteSize);
+ stats.add(" patch table estimated size: %lld\n", (uint64_t)this->patchTableOptimizer.patchTableTotalByteSize);
}
}
@@ -2874,12 +2048,12 @@
size += cacheDylib.inputFile->path.size() + 1;
size = alignTo(size, alignof(dyld4::Loader::LoaderRef));
size += sizeof(dyld4::Loader::LoaderRef) * cacheDylib.dependents.size();
- size += sizeof(Loader::LinkedDylibAttributes) * cacheDylib.dependents.size();
+ size += sizeof(Loader::DependentKind) * cacheDylib.dependents.size();
size += sizeof(Loader::FileValidationInfo);
size += sizeof(Loader::Region) * cacheDylib.segments.size();
// iOSMac dylibs likely contain a patch table
- if ( (this->options.platform == Platform::macOS)
+ if ( (this->options.platform == dyld3::Platform::macOS)
&& startsWith(cacheDylib.installName, "/System/iOSSupport") ) {
__block Diagnostics diag;
cacheDylib.inputMF->withFileLayout(diag, ^(const mach_o::Layout& layout) {
@@ -2898,10 +2072,8 @@
}
// Estimating the size of executable loaders is hard as they may contain ObjC/Swift hash tables,
- // patch tables, etc. For now, 20KB/executable seems about right
- // FIXME: Small programs like daemons need just 500 bytes or so, but large /Applications need up to 1MB
- // Come up with a better estimate
- this->prebuiltLoaderBuilder.executablesLoaderSize = 30_KB * this->exeInputFiles.size();
+ // patch tables, etc. For now, 16KB/executable seems about right
+ this->prebuiltLoaderBuilder.executablesLoaderSize = 16_KB * this->exeInputFiles.size();
// Estimate the trie size
// Assume they are all at a high offset
@@ -2914,7 +2086,7 @@
// Assumes that cdHash strings at runtime use lowercase a-f digits
uint32_t codeSignFileOffset = 0;
uint32_t codeSignFileSize = 0;
- if ( ((const Header*)inputFile->mf)->hasCodeSignature(codeSignFileOffset, codeSignFileSize) ) {
+ if ( inputFile->mf->hasCodeSignature(codeSignFileOffset, codeSignFileSize) ) {
auto handler = ^(const uint8_t cdHash[20]) {
std::string cdHashStr = "/cdhash/";
cdHashStr.reserve(24);
@@ -2989,62 +2161,60 @@
void SharedCacheBuilder::computeSubCaches()
{
Timer::Scope timedScope(this->config, "computeSubCaches time");
- computeLargeSubCache();
+
+ // We have 3 different kinds of caches.
+ // - regular: put everything in a single file
+ // - large: A file is (TEXT, DATA, LINKEDIT), and we might have > 1 file
+ // - split: A file is TEXT/DATA/LINKEDIT, and we've have 1 or more TEXT, and exactly 1 DATA and LINKEDIT
+ if ( config.layout.large.has_value() ) {
+ computeLargeSubCache();
+ } else {
+ computeRegularSubCache();
+ }
}
// ObjC/Swift optimizations produce arrays, hash tables, string sections, etc.
// This adds all of them to the given subCache
void SharedCacheBuilder::addObjCOptimizationsToSubCache(SubCache& subCache)
{
- // Disable objc optimizations for EK
- if ( this->options.platform.isExclaveKit() )
- return;
-
// Add objc header info RW
subCache.addObjCHeaderInfoReadWriteChunk(this->config, this->objcOptimizer);
// Add canonical objc protocols
subCache.addObjCCanonicalProtocolsChunk(this->config, this->objcProtocolOptimizer);
- // Add objc categories
- subCache.addObjCCategoriesChunk(this->config, this->objcCategoryOptimizer);
-
// Add objc opts header
- subCache.addObjCOptsHeaderChunk(this->config, this->objcOptimizer);
+ subCache.addObjCOptsHeaderChunk(this->objcOptimizer);
// Add objc header info RO
- subCache.addObjCHeaderInfoReadOnlyChunk(this->config, this->objcOptimizer);
-
- // Add objc image info
- subCache.addObjCImageInfoChunk(this->config, this->objcOptimizer);
+ subCache.addObjCHeaderInfoReadOnlyChunk(this->objcOptimizer);
// Add selector strings and hash table. These need to be adjacent as the table has offsets in
// to the string section
- subCache.addObjCSelectorStringsChunk(this->config, this->objcSelectorOptimizer);
- subCache.addObjCSelectorHashTableChunk(this->config, this->objcSelectorOptimizer);
+ subCache.addObjCSelectorStringsChunk(this->objcSelectorOptimizer);
+ subCache.addObjCSelectorHashTableChunk(this->objcSelectorOptimizer);
// Add class name strings and hash table
- subCache.addObjCClassNameStringsChunk(this->config, this->objcClassOptimizer);
- subCache.addObjCClassHashTableChunk(this->config, this->objcClassOptimizer);
+ subCache.addObjCClassNameStringsChunk(this->objcClassOptimizer);
+ subCache.addObjCClassHashTableChunk(this->objcClassOptimizer);
// Add protocol name strings and hash table
- subCache.addObjCProtocolNameStringsChunk(this->config, this->objcProtocolOptimizer);
- subCache.addObjCProtocolHashTableChunk(this->config, this->objcProtocolOptimizer);
+ subCache.addObjCProtocolNameStringsChunk(this->objcProtocolOptimizer);
+ subCache.addObjCProtocolHashTableChunk(this->objcProtocolOptimizer);
// Add Swift demangled name strings found in ObjC protocol metadata
- subCache.addObjCProtocolSwiftDemangledNamesChunk(this->config, this->objcProtocolOptimizer);
+ subCache.addObjCProtocolSwiftDemangledNamesChunk(this->objcProtocolOptimizer);
// Add ObjC IMP Caches
- subCache.addObjCIMPCachesChunk(this->config, this->objcIMPCachesOptimizer);
+ subCache.addObjCIMPCachesChunk(this->objcIMPCachesOptimizer);
// Add Swift opts header
- subCache.addSwiftOptsHeaderChunk(this->config, this->swiftOptimizer);
+ subCache.addSwiftOptsHeaderChunk(this->swiftProtocolConformanceOptimizer);
// Add Swift hash tables
- subCache.addSwiftTypeHashTableChunk(this->config, this->swiftOptimizer);
- subCache.addSwiftMetadataHashTableChunk(this->config, this->swiftOptimizer);
- subCache.addSwiftForeignHashTableChunk(this->config, this->swiftOptimizer);
- subCache.addSwiftPrespecializedMetadataPointerTableChunks(this->config, this->swiftOptimizer);
+ subCache.addSwiftTypeHashTableChunk(this->swiftProtocolConformanceOptimizer);
+ subCache.addSwiftMetadataHashTableChunk(this->swiftProtocolConformanceOptimizer);
+ subCache.addSwiftForeignHashTableChunk(this->swiftProtocolConformanceOptimizer);
}
// The shared cache contains many global optimizations such as dyld4 loaders, trie's, etc.
@@ -3058,9 +2228,6 @@
// Add patch table
subCache.addPatchTableChunk(this->patchTableOptimizer);
- // Add function-variants table
- subCache.addFunctionVariantsChunk(this->functionVariantsOptimizer);
-
// Add cache dylib Loader's
subCache.addCacheDylibsLoaderChunk(this->prebuiltLoaderBuilder);
@@ -3069,57 +2236,48 @@
// Add executable trie
subCache.addExecutablesTrieChunk(this->prebuiltLoaderBuilder);
-
- // Add prewarming data
- subCache.addPrewarmingDataChunk(this->config, this->prewarmingOptimizer);
}
// Every subCache needs a code signature, and subCache's with DATA* need slide info. This adds
// anything we need, based on whatever else is already in the SubCache.
void SharedCacheBuilder::addFinalChunksToSubCache(SubCache& subCache)
{
- subCache.addCacheHeaderChunk(this->options, this->config, this->cacheDylibs);
+ subCache.addCacheHeaderChunk(this->cacheDylibs);
+
+ // Add slide info for each DATA/AUTH segment. Do this after we've added any other DATA*
+ // segments
+ if ( this->config.slideInfo.slideInfoFormat.has_value() )
+ subCache.addSlideInfoChunks();
// Add a code signature region
subCache.addCodeSignatureChunk();
-}
-
-static std::optional<Header::SectionInfo> getStubsSection(const DylibSegmentChunk* dylibChunk)
-{
- if ( dylibChunk == nullptr )
- return std::nullopt;
- if ( dylibChunk->kind != Chunk::Kind::dylibText )
- return std::nullopt;
-
- const MachOFile* mf = dylibChunk->inputFile->mf;
- __block std::optional<Header::SectionInfo> stubsSection;
- ((const Header*)mf)->forEachSection(^(const Header::SectionInfo §Info, bool &stop) {
- if ( dylibChunk->segmentName != sectInfo.segmentName )
- return;
-
- unsigned sectionType = (sectInfo.flags & SECTION_TYPE);
- if ( sectionType != S_SYMBOL_STUBS )
- return;
-
- if ( sectInfo.segmentName != "__TEXT" ) {
- // stubs aren't in __TEXT. Give up on this one for now
- return;
- }
-
- if ( mf->isArch("arm64e") ) {
- // For arm64e, we can only optimize __auth_stubs
- if ( sectInfo.sectionName != "__auth_stubs" )
- return;
- } else {
- // For non-arm64e, we can only optimize __stubs
- if ( sectInfo.sectionName != "__stubs" )
- return;
- }
-
- stubsSection = sectInfo;
- });
-
- return stubsSection;
+
+ // Finalize the SubCache, by removing any unused regions
+ subCache.removeEmptyRegions();
+}
+
+void SharedCacheBuilder::computeRegularSubCache()
+{
+ // Put everything into a single file.
+ SubCache subCache = SubCache::makeMainCache(this->options, true);
+
+ // Add all the objc tables. This must be done before we add libobjc's __TEXT
+ this->addObjCOptimizationsToSubCache(subCache);
+
+ for ( CacheDylib& cacheDylib : this->cacheDylibs ) {
+ bool addLinkedit = true;
+ subCache.addDylib(cacheDylib, addLinkedit);
+ }
+
+ // Add all the global optimizations
+ this->addGlobalOptimizationsToSubCache(subCache);
+
+ // Reserve space in the last sub cache for dynamic config data
+ subCache.addDynamicConfigChunk();
+
+ this->addFinalChunksToSubCache(subCache);
+
+ this->subCaches.push_back(std::move(subCache));
}
// Add stubs Chunk's for every stubs section in the given text subCache
@@ -3133,29 +2291,60 @@
const DylibSegmentChunk* textChunk = textRegionChunk->isDylibSegmentChunk();
if ( textChunk == nullptr )
continue;
-
- std::optional<Header::SectionInfo> sectInfo = getStubsSection(textChunk);
- if ( !sectInfo )
+ if ( textChunk->kind != Chunk::Kind::dylibText )
continue;
- // Make a stubs chunk for this stubs section
- CacheDylib* cacheDylib = fileToDylibMap.at(textChunk->inputFile);
- StubsChunk* devStubsChunk = &cacheDylib->developmentStubs;
- StubsChunk* customerStubsChunk = &cacheDylib->customerStubs;
-
- assert(devStubsChunk->segmentName.empty());
- devStubsChunk->segmentName = sectInfo->segmentName;
- devStubsChunk->sectionName = sectInfo->sectionName;
- devStubsChunk->subCacheFileSize = CacheFileSize(sectInfo->size);
- devStubsChunk->cacheVMSize = CacheVMSize(sectInfo->size);
- devStubsSubCache.addStubsChunk(devStubsChunk);
-
- assert(customerStubsChunk->segmentName.empty());
- customerStubsChunk->segmentName = sectInfo->segmentName;
- customerStubsChunk->sectionName = sectInfo->sectionName;
- customerStubsChunk->subCacheFileSize = CacheFileSize(sectInfo->size);
- customerStubsChunk->cacheVMSize = CacheVMSize(sectInfo->size);
- customerStubsSubCache.addStubsChunk(customerStubsChunk);
+ const MachOFile* mf = textChunk->inputFile->mf;
+ mf->forEachSection(^(const dyld3::MachOFile::SectionInfo §Info,
+ bool malformedSectionRange, bool &stop) {
+ if ( textChunk->segmentName != sectInfo.segInfo.segName )
+ return;
+
+ unsigned sectionType = (sectInfo.sectFlags & SECTION_TYPE);
+ if ( sectionType != S_SYMBOL_STUBS )
+ return;
+
+ if ( strcmp(sectInfo.segInfo.segName, "__TEXT") != 0 ) {
+ // stubs aren't in __TEXT. Give up on this one for now
+ return;
+ }
+
+ // Make a stubs chunk for this stubs section
+ CacheDylib* cacheDylib = fileToDylibMap.at(textChunk->inputFile);
+ StubsChunk* devStubsChunk = nullptr;
+ StubsChunk* customerStubsChunk = nullptr;
+
+ if ( mf->isArch("arm64e") ) {
+ // For arm64e, we can only optimize __auth_stubs
+ if ( !strcmp(sectInfo.sectName, "__auth_stubs") ) {
+ devStubsChunk = &cacheDylib->developmentStubs;
+ customerStubsChunk = &cacheDylib->customerStubs;
+ }
+ } else {
+ // For non-arm64e, we can only optimize __stubs
+ if ( !strcmp(sectInfo.sectName, "__stubs") ) {
+ devStubsChunk = &cacheDylib->developmentStubs;
+ customerStubsChunk = &cacheDylib->customerStubs;
+ }
+ }
+
+ if ( devStubsChunk == nullptr )
+ return;
+
+ assert(devStubsChunk->segmentName.empty());
+ devStubsChunk->segmentName = sectInfo.segInfo.segName;
+ devStubsChunk->sectionName = sectInfo.sectName;
+ devStubsChunk->subCacheFileSize = CacheFileSize(sectInfo.sectSize);
+ devStubsChunk->cacheVMSize = CacheVMSize(sectInfo.sectSize);
+ devStubsSubCache.addStubsChunk(devStubsChunk);
+
+ assert(customerStubsChunk->segmentName.empty());
+ customerStubsChunk->segmentName = sectInfo.segInfo.segName;
+ customerStubsChunk->sectionName = sectInfo.sectName;
+ customerStubsChunk->subCacheFileSize = CacheFileSize(sectInfo.sectSize);
+ customerStubsChunk->cacheVMSize = CacheVMSize(sectInfo.sectSize);
+ customerStubsSubCache.addStubsChunk(customerStubsChunk);
+ });
}
}
@@ -3262,49 +2451,9 @@
// Nothing to do here
break;
case cache_builder::Region::Kind::dataConst:
- case cache_builder::Region::Kind::tproConst:
case cache_builder::Region::Kind::data:
case cache_builder::Region::Kind::auth:
- case cache_builder::Region::Kind::authConst:
- case cache_builder::Region::Kind::tproAuthConst:{
- Region& newRegion = newSubCache.regions[(uint32_t)oldRegion.kind];
- newRegion.chunks = std::move(oldRegion.chunks);
- break;
- }
- case cache_builder::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;
- }
- }
- }
-
- // Also split the current file so that read-only are in their own files
- {
- // Create a new subCache
- newSubCaches.push_back(SubCache::makeSubCache(options));
- SubCache& newSubCache = newSubCaches.back();
-
- // Move all data to the new subCache
- for ( Region& oldRegion : subCache.regions ) {
- if ( oldRegion.chunks.empty() )
- continue;
-
- // Move all the data regions, leave the rest
- switch ( oldRegion.kind ) {
- case cache_builder::Region::Kind::text:
- case cache_builder::Region::Kind::dataConst:
- case cache_builder::Region::Kind::tproConst:
- case cache_builder::Region::Kind::data:
- case cache_builder::Region::Kind::auth:
- case cache_builder::Region::Kind::authConst:
- case cache_builder::Region::Kind::tproAuthConst:
- // Nothing to do here
- break;
- case cache_builder::Region::Kind::readOnly:{
+ case cache_builder::Region::Kind::authConst: {
Region& newRegion = newSubCache.regions[(uint32_t)oldRegion.kind];
newRegion.chunks = std::move(oldRegion.chunks);
break;
@@ -3331,104 +2480,53 @@
{
SubCache* currentSubCache = firstSubCache;
- std::string_view libObjcInstallName = "/usr/lib/libobjc.A.dylib";
- if ( this->options.platform.isExclaveKit() )
- libObjcInstallName = "/System/ExclaveKit/usr/lib/libobjc.A.dylib";
-
- // If we have an objc dylib, then gets its optimizations now to work out their size first
- // The subCache with libobjc gets the header info sections, which for now we assume is the first subcache
- // Add all the objc tables. This must be done before we add libobjc's __TEXT
+ // We'll add LINKEDIT at the end. As the shared region is <= 4GB in size, we can fit
+ // all the LINKEDIT in the last subCache and still keep it in range of 32-bit offsets
+ bool allLinkeditInLastSubCache = this->config.layout.allLinkeditInLastSubCache;
+
+ // Walk all the dylibs, and create a new subCache every time we are about to cross
+ // the subCacheTextLimit
+ CacheVMSize subCacheTextSize(0ULL);
for ( CacheDylib& cacheDylib : this->cacheDylibs ) {
- if ( cacheDylib.installName == libObjcInstallName ) {
+ // Peek ahead to find the __TEXT size
+ CacheVMSize textSize(0ULL);
+ for ( DylibSegmentChunk& segmentInfo : cacheDylib.segments ) {
+ if ( segmentInfo.kind == DylibSegmentChunk::Kind::dylibText )
+ textSize += segmentInfo.cacheVMSize;
+ }
+
+ // If we exceed the current limit, then the current subCache is complete and we need
+ // to start a new one
+ if ( (subCacheTextSize + textSize) > this->config.layout.large->subCacheTextLimit ) {
+ // Create a new subCache
+ otherCaches.push_back(SubCache::makeSubCache(this->options));
+ currentSubCache = &otherCaches.back();
+
+ // Reset the limit for the next subCache
+ subCacheTextSize = CacheVMSize(0ULL);
+ }
+
+ subCacheTextSize += textSize;
+
+ // The subCache with libobjc gets the header info sections
+ // Add all the objc tables. This must be done before we add libobjc's __TEXT
+ if ( cacheDylib.installName == "/usr/lib/libobjc.A.dylib" )
this->addObjCOptimizationsToSubCache(*currentSubCache);
- break;
- }
- }
-
- // Walk all the dylibs, and create a new subCache every time we are about to cross
- // the subCache size limit
- if ( this->config.layout.discontiguous.has_value() ) {
- const CacheVMSize subCacheTextLimit = this->config.layout.discontiguous.value().subCacheTextLimit;
- CacheVMSize subCacheTextSize(0ULL);
- for ( CacheDylib& cacheDylib : this->cacheDylibs ) {
- // Peek ahead to find the __TEXT size
- CacheVMSize textSize(0ULL);
- for ( DylibSegmentChunk& segmentInfo : cacheDylib.segments ) {
- if ( segmentInfo.kind == DylibSegmentChunk::Kind::dylibText )
- textSize += segmentInfo.cacheVMSize;
- }
-
- // If we exceed the current limit, then the current subCache is complete and we need
- // to start a new one
- if ( (subCacheTextSize + textSize) > subCacheTextLimit ) {
- // Create a new subCache
- otherCaches.push_back(SubCache::makeSubCache(this->options));
- currentSubCache = &otherCaches.back();
-
- // Reset the limit for the next subCache
- subCacheTextSize = CacheVMSize(0ULL);
- }
-
- subCacheTextSize += textSize;
-
- currentSubCache->addDylib(this->config, cacheDylib);
+
+ // We'll add LINKEDIT at the end. As the shared region is <= 4GB in size, we can fit
+ // all the LINKEDIT in the last subCache and still keep it in range of 32-bit offsets
+ bool addLinkedit = !allLinkeditInLastSubCache;
+ currentSubCache->addDylib(cacheDylib, addLinkedit);
+ }
+
+ // Add all the remaining content in to the final (current) subCache
+
+ // Add linkedit chunks from dylibs, if needed
+ if ( allLinkeditInLastSubCache ) {
+ for ( CacheDylib& cacheDylib : this->cacheDylibs )
currentSubCache->addLinkeditFromDylib(cacheDylib);
- }
- } else {
- const CacheVMSize subCacheTextDataLimit = this->config.layout.contiguous.value().subCacheTextDataLimit;
- const CacheVMSize regionPadding = this->config.layout.contiguous.value().subCachePadding;
- CacheVMSize subCacheTextDataSize(regionPadding);
-
- // If we added any objc optimizations, then take their size in to account now
- for ( const Region& region : currentSubCache->regions ) {
- if ( region.kind == Region::Kind::linkedit )
- continue;
- if ( region.chunks.empty() )
- continue;
- for ( const Chunk* chunk : region.chunks ) {
- // Assume the worst in terms of padding, ie, that every segment is preceded by the max
- // possible number of padding bytes
- subCacheTextDataSize += CacheVMSize(chunk->alignment()) + chunk->cacheVMSize;
- }
- }
-
- for ( CacheDylib& cacheDylib : this->cacheDylibs ) {
- // Peek ahead to find the __TEXT size
- CacheVMSize textDataSize(0ULL);
- for ( DylibSegmentChunk& segmentInfo : cacheDylib.segments ) {
- if ( segmentInfo.kind == DylibSegmentChunk::Kind::dylibLinkedit )
- continue;
-
- // Assume the worst in terms of padding, ie, that every segment is preceded by the max
- // possible number of padding bytes
- textDataSize += CacheVMSize(segmentInfo.alignment()) + segmentInfo.cacheVMSize;
-
- // The One Cache stubs will also be included in this limit so account for them too
- // See addStubsChunks()
- if ( std::optional<Header::SectionInfo> sectInfo = getStubsSection(&segmentInfo) )
- textDataSize += CacheVMSize(sectInfo->size);
- }
-
- // If we exceed the current limit, then the current subCache is complete and we need
- // to start a new one
- if ( (subCacheTextDataSize + textDataSize) > subCacheTextDataLimit ) {
- // Create a new subCache
- otherCaches.push_back(SubCache::makeSubCache(this->options));
- currentSubCache = &otherCaches.back();
-
- // Reset the limit for the next subCache
- subCacheTextDataSize = CacheVMSize(regionPadding);
- }
-
- subCacheTextDataSize += textDataSize;
-
- // We'll add LINKEDIT at the end, not here
- currentSubCache->addDylib(this->config, cacheDylib);
- currentSubCache->addLinkeditFromDylib(cacheDylib);
- }
- }
-
- // Add all the remaining content in to the final (current) subCache
+ }
+
// Add all the global optimizations
this->addGlobalOptimizationsToSubCache(*currentSubCache);
@@ -3786,6 +2884,9 @@
uint32_t& sourceStringSize,
uint32_t& sourceStringCount)
{
+ if ( options.localSymbolsMode == cache_builder::LocalSymbolsMode::strip )
+ return Error();
+
// Map from strings to their offsets in to the new string buffer
auto& stringMap = subCache.symbolStringsOptimizer.stringMap;
@@ -3806,13 +2907,6 @@
std::string_view symbolString(symbolName);
sourceStringSize += symbolString.size() + 1;
++sourceStringCount;
-
- // rdar://129398821 (dyld cache builder add support for binds relative to dylib segments)
- // skip synthetic dyld symbols
- if ( symbolString.find("$dyld$") != std::string_view::npos ) {
- ++oldSymbolIndex;
- return;
- }
auto itAndInserted = stringMap.insert({ symbolString, stringBufferSize });
// If we inserted the string, then account for the space
@@ -4076,54 +3170,17 @@
return Error();
}
-// returns empty string if symbol is in specified boundDylibName,
-// otherwise it return where symbol actually is defined
-static CString getActualSymbolImplDylib(CString symbolName, CString boundDylibName,
- const std::unordered_map<std::string_view, CacheDylib*>& installNameToDylibMap,
- bool& symbolImplIsFunctionVariant, uint16_t& functionVariantIndex)
-{
- // find if bind target is really in another dylib or is a function variant
- __block CString betterDylib;
- const auto& posi = installNameToDylibMap.find(boundDylibName);
- if ( posi != installNameToDylibMap.end() ) {
- CacheDylib* targetDylib = posi->second;
- Symbol symbol;
- if ( targetDylib->inputImage->hasExportsTrie() && targetDylib->inputImage->exportsTrie().hasExportedSymbol(symbolName.c_str(), symbol) ) {
- betterDylib = (const char*)(targetDylib->installName.data()); // FIXME
- uint32_t fvi;
- if (symbol.isFunctionVariant(fvi) ) {
- symbolImplIsFunctionVariant = true;
- functionVariantIndex = fvi;
- }
- }
- else {
- // check if symbol is from re-exported dylib
- const Header* targetHdr = (Header*)targetDylib->inputMF;
- targetHdr->forEachLinkedDylib(^(const char* loadPath, LinkedDylibAttributes kind, Version32 compatVersion, Version32 curVersion, bool synthesizedLink, bool &stop) {
- if ( kind.reExport ) {
- betterDylib = getActualSymbolImplDylib(symbolName, loadPath, installNameToDylibMap, symbolImplIsFunctionVariant, functionVariantIndex);
- if ( !betterDylib.empty() ) {
- stop = true;
- }
- }
- });
- }
- }
- return betterDylib;
-}
-
-
static void parseGOTs(const CacheDylib* dylib, const DylibSegmentChunk* chunk,
std::string_view segmentName, std::string_view sectionName,
- const std::unordered_map<std::string_view, CacheDylib*>& installNameToDylibMap,
- OptimizedGOTSection& dylibOptimizedSection)
-{
+ DylibSectionCoalescer::OptimizedSection& dylibOptimizedSection)
+{
+ const MachOFile* mf = dylib->inputMF;
__block Diagnostics diag;
const bool log = false;
// Skip ineligible dylibs
- if ( !dylib->inputMF->hasChainedFixups() )
+ if ( !mf->hasChainedFixups() )
return;
// Some dylibs have auth gots in segments other than __AUTH_CONST. Skip them for now
@@ -4131,7 +3188,7 @@
return;
__block bool supportsGOTUniquing = false;
- dylib->inputMF->withFileLayout(diag, ^(const mach_o::Layout& layout) {
+ mf->withFileLayout(diag, ^(const mach_o::Layout& layout) {
mach_o::SplitSeg splitSeg(layout);
if ( splitSeg.isV2() )
@@ -4141,11 +3198,13 @@
if ( !supportsGOTUniquing )
return;
- if ( dylib->inputHdr->isArch("x86_64") || dylib->inputHdr->isArch("x86_64h") ) {
+ if ( mf->isArch("x86_64") || mf->isArch("x86_64h") ) {
__block bool oldLinker = false;
- dylib->inputHdr->forEachBuildTool(^(Platform platform, uint32_t tool, uint32_t version) {
+ mf->forEachSupportedBuildTool(^(dyld3::Platform platform, uint32_t tool, uint32_t version) {
uint32_t majorVersion = version >> 16;
-
+ // uint32_t minorVersion = (version >> 8) && 0xFF;
+ // uint32_t veryMinorVersion = version && 0xFF;
+
if ( tool == TOOL_LD ) {
if ( majorVersion < 803 )
oldLinker = true;
@@ -4157,37 +3216,37 @@
}
// rdar://89319146
- if ( dylib->inputHdr->isArch("x86_64") || dylib->inputHdr->isArch("x86_64h") ) {
- if ( !strcmp(dylib->inputHdr->installName(), "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation") )
+ if ( mf->isArch("x86_64") || mf->isArch("x86_64h") ) {
+ if ( !strcmp(mf->installName(), "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation") )
return;
- if ( !strcmp(dylib->inputHdr->installName(), "/usr/lib/system/libdispatch.dylib") )
+ if ( !strcmp(mf->installName(), "/usr/lib/system/libdispatch.dylib") )
return;
}
// rdar://86911139
- if ( dylib->inputHdr->builtForPlatform(Platform::iOS)
- && !strcmp(dylib->inputHdr->installName(), "/System/Library/PrivateFrameworks/CoreUI.framework/CoreUI") )
+ if ( mf->builtForPlatform(dyld3::Platform::iOS)
+ && !strcmp(mf->installName(), "/System/Library/PrivateFrameworks/CoreUI.framework/CoreUI") )
return;
// Dylib segment is eligible. Walk the GOTs
- __block std::optional<std::pair<Header::SegmentInfo, Header::SectionInfo>> gotSectionInfo;
+ __block std::optional<dyld3::MachOAnalyzer::SectionInfo> gotSectionInfo;
__block uint16_t chainedFixupFormat = 0;
- dylib->inputHdr->forEachSection(^(const Header::SegmentInfo& segInfo, const Header::SectionInfo& sectInfo, bool& stop) {
- if ( sectInfo.segmentName != segmentName )
+ mf->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
+ if ( sectInfo.segInfo.segName != segmentName )
return;
- if ( sectInfo.sectionName != sectionName)
+ if ( sectInfo.sectName != sectionName)
return;
- gotSectionInfo = { segInfo, sectInfo };
+ gotSectionInfo = sectInfo;
// As we found the section we want, also get its chained fixup format
- dylib->inputMF->withFileLayout(diag, ^(const mach_o::Layout& layout) {
+ mf->withFileLayout(diag, ^(const mach_o::Layout& layout) {
mach_o::Fixups fixups(layout);
fixups.withChainStarts(diag, ^(const dyld_chained_starts_in_image* starts) {
MachOFile::forEachFixupChainSegment(diag, starts,
^(const dyld_chained_starts_in_segment* segmentInfo,
uint32_t segIndex, bool& stopSegment) {
- if ( segIndex == sectInfo.segIndex ) {
+ if ( segIndex == sectInfo.segInfo.segIndex ) {
chainedFixupFormat = segmentInfo->pointer_format;
stopSegment = true;
}
@@ -4206,7 +3265,7 @@
return;
__block std::vector<mach_o::Fixups::BindTargetInfo> bindTargets;
- dylib->inputMF->withFileLayout(diag, ^(const mach_o::Layout& layout) {
+ mf->withFileLayout(diag, ^(const mach_o::Layout& layout) {
mach_o::Fixups fixups(layout);
fixups.forEachBindTarget(diag, false, 0, ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
@@ -4223,19 +3282,20 @@
return;
__block std::vector<const char*> dependents;
- dylib->inputMF->forEachDependentDylib(^(const char *loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) {
+ mf->forEachDependentDylib(^(const char *loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) {
dependents.push_back(loadPath);
});
- bool hasFunctionVariantFixups = dylib->inputHdr->hasFunctionVariantFixups();
+ auto* cacheGotSection = (CoalescedGOTSection*)dylibOptimizedSection.subCacheSection;
+ DylibSectionCoalescer::DylibSectionOffsetToCacheSectionOffset& offsetMap = dylibOptimizedSection.offsetMap;
// Walk the entries in this section
// File layout so just add the file offset
- const uint8_t* content = (const uint8_t*)dylib->inputHdr + gotSectionInfo->second.fileOffset;
+ const uint8_t* content = (const uint8_t*)mf + gotSectionInfo->sectFileOffset;
const uint8_t* pos = content;
- const uint8_t* end = content + gotSectionInfo->second.size;
- uint32_t pointerSize = dylib->inputHdr->pointerSize();
- assert((gotSectionInfo->second.size % pointerSize == 0));
+ const uint8_t* end = content + gotSectionInfo->sectSize;
+ uint32_t pointerSize = mf->pointerSize();
+ assert((gotSectionInfo->sectSize % pointerSize == 0));
while ( pos != end ) {
const dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixup = (const dyld3::MachOLoaded::ChainedFixupPointerOnDisk*)pos;
pos += pointerSize;
@@ -4246,83 +3306,56 @@
uint32_t sourceSectionOffset = (uint32_t)((uint64_t)fixup - (uint64_t)content);
// Note down rebases, but otherwise skip them
- __block bool targetIsFunctionVariant = false;
- __block uint16_t functionVariantIndex = 0;
if ( !isBind ) {
- if ( hasFunctionVariantFixups ) {
- // see if this rebase is the local of an internal function variant
- uint32_t segIndex = gotSectionInfo->first.segmentIndex;
- dylib->inputImage->functionVariantFixups().forEachFixup(^(mach_o::FunctionVariantFixups::InternalFixup fixupInfo) {
- if ( fixupInfo.segIndex != segIndex )
- return;
- uint64_t segOffset = sourceSectionOffset + gotSectionInfo->second.address - gotSectionInfo->first.vmaddr;
- if ( fixupInfo.segOffset == segOffset ) {
- targetIsFunctionVariant = true;
- functionVariantIndex = fixupInfo.variantIndex;
- }
- });
- }
- if ( !targetIsFunctionVariant ) {
- dylibOptimizedSection.addUnoptimizedOffset(sourceSectionOffset);
- continue;
- }
- }
-
- const CacheDylib* targetDylib = nullptr;
- CoalescedGOTSection::GOTKey key;
- if ( targetIsFunctionVariant ) {
- MachOFile::PointerMetaData pmd(fixup, chainedFixupFormat);
- char* synthName;
- asprintf(&synthName, "internal-function-variant#%d", functionVariantIndex);
- key = { synthName, dylib->installName, pmd, false, true };
- targetDylib = dylib;
- }
- else {
- // We don't support addends right now. But hopefully GOTs don't need them anyway
- if ( addend != 0 )
- continue;
-
- const mach_o::Fixups::BindTargetInfo& bindTarget = bindTargets[bindOrdinal];
-
- // TODO: Weak GOTs. See rdar://86510941
- const char* targetInstallName = nullptr;
- if ( (bindTarget.libOrdinal > 0) && ((unsigned)bindTarget.libOrdinal <= dependents.size()) ) {
- targetInstallName = dependents[bindTarget.libOrdinal - 1];
- } else {
- dylibOptimizedSection.addUnoptimizedOffset(sourceSectionOffset);
- continue;
- }
-
- // find if bind target is really in another dylib or is a function variant
- CString betterDylib = getActualSymbolImplDylib(bindTarget.symbolName, targetInstallName, installNameToDylibMap, targetIsFunctionVariant, functionVariantIndex);
- if ( !betterDylib.empty() && (betterDylib != targetInstallName) ) {
- targetInstallName = betterDylib.c_str();
- // fprintf(stderr, "%s found in %s instead of %s\n", bindTarget.symbolName, betterDylib.c_str(), targetInstallName);
- }
-
- if ( targetIsFunctionVariant )
- targetDylib = installNameToDylibMap.at(targetInstallName);
-
- MachOFile::PointerMetaData pmd(fixup, chainedFixupFormat);
- key = { bindTarget.symbolName, targetInstallName, pmd, bindTarget.weakImport, targetIsFunctionVariant };
- }
-
- if ( dylibOptimizedSection.addOptimizedOffset(sourceSectionOffset, pointerSize, key) ) {
+ dylibOptimizedSection.unoptimizedOffsets.insert(sourceSectionOffset);
+ continue;
+ }
+
+ // We don't support addends right now. But hopefully GOTs don't need them anyway
+ if ( addend != 0 )
+ continue;
+
+ const mach_o::Fixups::BindTargetInfo& bindTarget = bindTargets[bindOrdinal];
+
+ // TODO: Weak GOTs. See rdar://86510941
+ const char* targetInstallName = nullptr;
+ if ( (bindTarget.libOrdinal > 0) && ((unsigned)bindTarget.libOrdinal <= dependents.size()) ) {
+ targetInstallName = dependents[bindTarget.libOrdinal - 1];
+ } else {
+ dylibOptimizedSection.unoptimizedOffsets.insert(sourceSectionOffset);
+ continue;
+ }
+
+ MachOFile::PointerMetaData pmd(fixup, chainedFixupFormat);
+
+ typedef CoalescedGOTSection::GOTKey Key;
+ Key key = { bindTarget.symbolName, targetInstallName, pmd };
+
+ int cacheSectionOffset = (int)(cacheGotSection->gotTargetsToOffsets.size() * pointerSize);
+ auto itAndInserted = cacheGotSection->gotTargetsToOffsets.insert({ key, cacheSectionOffset });
+ if ( itAndInserted.second ) {
+ // We inserted the element, so its offset is already valid. Nothing else to do
+
if (log) {
uint64_t gotOffset = ((uint64_t)pos - (uint64_t)content) - pointerSize;
printf("%s[%lld]: %s -> (%s, %s)\n",
- sectionName.data(), gotOffset, dylib->inputHdr->installName(),
+ sectionName.data(), gotOffset, mf->installName(),
key.targetDylibName.data(), key.targetSymbolName.data());
}
- }
-
- // store function-variant index in other map
- if ( targetIsFunctionVariant )
- dylibOptimizedSection.addFunctionVariantInfo(key, { targetDylib->cacheIndex, functionVariantIndex });
- }
-
- // Record which section we just visited
- dylibOptimizedSection.setSourceSectionInfo(gotSectionInfo->second);
+ } else {
+ // Debugging only. If we didn't include the GOT then we saved that many bytes
+ cacheGotSection->savedSpace += pointerSize;
+ cacheSectionOffset = itAndInserted.first->second;
+ }
+
+ // Now keep track of this offset in our source dylib as pointing to this offset
+ offsetMap[sourceSectionOffset] = cacheSectionOffset;
+ }
+
+ // Record which segment/section we just visited
+ uint32_t segmentIndex = gotSectionInfo->segInfo.segIndex;
+ dylibOptimizedSection.segmentIndex = segmentIndex;
+ dylibOptimizedSection.sectionVMOffsetInSegment = VMOffset(gotSectionInfo->sectAddr - gotSectionInfo->segInfo.vmAddr);
}
// This runs after we've assigned Chunk's to SubCache's, but before we've actually
@@ -4331,7 +3364,7 @@
Error SharedCacheBuilder::calculateUniqueGOTs()
{
// Skip this optimiation on simulator until we've qualified it there
- if ( this->options.isSimulator() )
+ if ( this->options.isSimultor() )
return Error();
Stats stats(this->config);
@@ -4341,12 +3374,9 @@
// DylibSegmentChunk's don't have a pointer to their cache dylib. Make a map for them
std::unordered_map<const InputFile*, CacheDylib*> fileToDylibMap;
- std::unordered_map<std::string_view, CacheDylib*> installNameToDylibMap;
fileToDylibMap.reserve(this->cacheDylibs.size());
- for ( CacheDylib& dylib : this->cacheDylibs ) {
+ for ( CacheDylib& dylib : this->cacheDylibs )
fileToDylibMap[dylib.inputFile] = &dylib;
- installNameToDylibMap[dylib.installName] = &dylib;
- }
for ( SubCache& subCache : this->subCaches ) {
// Find the DATA_CONST/AUTH_CONST in each SubCache, if it has any
@@ -4366,44 +3396,20 @@
if ( (dataConstRegion == nullptr) && (authConstRegion == nullptr) )
continue;
- for ( UniquedGOTKind sectionKind : { UniquedGOTKind::regular, UniquedGOTKind::authGot, UniquedGOTKind::authPtr } ) {
-
- Region* region = nullptr;
- std::string_view segmentName;
- std::string_view sectionName;
- const char* kindName = nullptr;
- CoalescedGOTSection* subCacheUniquedGOTs = nullptr;
-
- // Skip sections if their segment doesn't exist
- switch ( sectionKind ) {
- case UniquedGOTKind::regular:
- region = dataConstRegion;
- segmentName = "__DATA_CONST";
- sectionName = "__got";
- kindName = "regular";
- subCacheUniquedGOTs = &subCache.uniquedGOTsOptimizer.regularGOTs;
- break;
- case UniquedGOTKind::authGot:
- region = authConstRegion;
- segmentName = "__AUTH_CONST";
- sectionName = "__auth_got";
- kindName = "auth-gots";
- subCacheUniquedGOTs = &subCache.uniquedGOTsOptimizer.authGOTs;
- break;
- case UniquedGOTKind::authPtr:
- region = authConstRegion;
- segmentName = "__AUTH_CONST";
- sectionName = "__auth_ptr";
- kindName = "auth-ptrs";
- subCacheUniquedGOTs = &subCache.uniquedGOTsOptimizer.authPtrs;
- break;
- }
- if ( region == nullptr )
+ for ( bool auth : { false, true } ) {
+ if ( auth && (authConstRegion == nullptr) )
continue;
-
- std::vector<OptimizedSection*> dylibOptimizedSections;
- dylibOptimizedSections.reserve(region->chunks.size());
- for ( const Chunk* chunk : region->chunks ) {
+ if ( !auth && (dataConstRegion == nullptr) )
+ continue;
+
+ Region& region = auth ? *authConstRegion : *dataConstRegion;
+ std::string_view segmentName = auth ? "__AUTH_CONST" : "__DATA_CONST";
+ std::string_view sectionName = auth ? "__auth_got" : "__got";
+ CoalescedGOTSection& subCacheUniquedGOTs = auth ? subCache.uniquedGOTsOptimizer.authGOTs : subCache.uniquedGOTsOptimizer.regularGOTs;
+
+ std::vector<DylibSectionCoalescer::OptimizedSection*> dylibOptimizedSections;
+ dylibOptimizedSections.reserve(region.chunks.size());
+ for ( const Chunk* chunk : region.chunks ) {
const DylibSegmentChunk* segmentChunk = chunk->isDylibSegmentChunk();
if ( !segmentChunk )
continue;
@@ -4411,34 +3417,109 @@
if ( chunk->name() != segmentName )
continue;
- CacheDylib* dylib = fileToDylibMap.at(segmentChunk->inputFile);
- OptimizedGOTSection* dylibUniquedGOTs = nullptr;
- switch ( sectionKind ) {
- case UniquedGOTKind::regular:
- dylibUniquedGOTs = &dylib->optimizedSections.gots;
- break;
- case UniquedGOTKind::authGot:
- dylibUniquedGOTs = &dylib->optimizedSections.auth_gots;
- break;
- case UniquedGOTKind::authPtr:
- dylibUniquedGOTs = &dylib->optimizedSections.auth_ptrs;
- break;
+ CacheDylib* dylib = fileToDylibMap.at(segmentChunk->inputFile);
+ auto& dylibUniquedGOTs = auth ? dylib->optimizedSections.auth_gots : dylib->optimizedSections.gots;
+
+ // Set the dylib GOTs to point to the subCache they'll be uniqued to
+ dylibUniquedGOTs.subCacheSection = &subCacheUniquedGOTs;
+ dylibOptimizedSections.push_back(&dylibUniquedGOTs);
+
+ parseGOTs(dylib, segmentChunk, segmentName, sectionName, dylibUniquedGOTs);
+ }
+
+ if ( subCacheUniquedGOTs.gotTargetsToOffsets.empty() )
+ continue;
+
+ // Sort the coalesced GOTs based on the target install name. We find GOTs in the order we parse
+ // the fixups in the dylibs, but we want the final cache to keep all GOTs for the same target near
+ // each other
+ typedef CoalescedGOTSection::GOTKey Key;
+ std::vector<Key> sortedKeys;
+ sortedKeys.reserve(subCacheUniquedGOTs.gotTargetsToOffsets.size());
+ for ( const auto& keyAndValue : subCacheUniquedGOTs.gotTargetsToOffsets )
+ sortedKeys.push_back(keyAndValue.first);
+
+ std::sort(sortedKeys.begin(), sortedKeys.end(),
+ [](const Key& a, const Key& b) {
+ // Put libSystem first, then all the /usr/lib/system dylibs
+ // That way any GOTs for re-exports from libsystem will be close to similar GOTs
+ bool isLibsystemA = a.targetDylibName.find("libSystem.B.dylib") != std::string_view::npos;
+ bool isLibsystemB = b.targetDylibName.find("libSystem.B.dylib") != std::string_view::npos;
+ if ( isLibsystemA != isLibsystemB )
+ return isLibsystemA;
+
+ bool isLibsystemReexportA = a.targetDylibName.find("/usr/lib/system") != std::string_view::npos;
+ bool isLibsystemReexportB = b.targetDylibName.find("/usr/lib/system") != std::string_view::npos;
+ if ( isLibsystemReexportA != isLibsystemReexportB )
+ return isLibsystemReexportA;
+
+ if ( a.targetDylibName != b.targetDylibName )
+ return (a.targetDylibName < b.targetDylibName);
+
+ // Install names are the same. Sort by symbol name
+ return a.targetSymbolName < b.targetSymbolName;
+ });
+
+ // Rewrite entries from their original offset to the new offset
+ std::unordered_map<uint32_t, uint32_t> oldToNewOffsetMap;
+ for ( uint32_t i = 0; i != sortedKeys.size(); ++i ) {
+ const Key& key = sortedKeys[i];
+ auto it = subCacheUniquedGOTs.gotTargetsToOffsets.find(key);
+ assert(it != subCacheUniquedGOTs.gotTargetsToOffsets.end());
+
+ uint32_t newCacheSectionOffset = i * pointerSize;
+
+ // Record the offset mapping for updating the dylibs
+ oldToNewOffsetMap[it->second] = newCacheSectionOffset;
+
+ const bool log = false;
+ if ( log ) {
+ printf("%s[%d]: %s\n", sectionName.data(), newCacheSectionOffset, key.targetSymbolName.data());
}
- // Set the dylib GOTs to point to the subCache they'll be uniqued to
- dylibUniquedGOTs->setSubCacheSection(subCacheUniquedGOTs);
-
- parseGOTs(dylib, segmentChunk, segmentName, sectionName, installNameToDylibMap, *dylibUniquedGOTs);
- }
-
- if ( subCacheUniquedGOTs->empty() )
- continue;
-
- subCacheUniquedGOTs->finalize(pointerSize, sectionName, config, subCache, *region);
+ it->second = newCacheSectionOffset;
+ }
+
+ // Also rewrite entries in each dylib
+ for ( DylibSectionCoalescer::OptimizedSection* dylibOptimizedSection : dylibOptimizedSections ) {
+ for ( auto& keyAndCacheOffset : dylibOptimizedSection->offsetMap ) {
+ auto it = oldToNewOffsetMap.find(keyAndCacheOffset.second);
+ assert(it != oldToNewOffsetMap.end());
+ keyAndCacheOffset.second = it->second;
+ }
+ }
+
+ // Add the new chunks to the subCache
+ if ( auth ) {
+ subCache.uniquedAuthGOTs = std::make_unique<UniquedGOTsChunk>();
+ subCache.uniquedAuthGOTs->cacheVMSize = CacheVMSize((uint64_t)subCacheUniquedGOTs.gotTargetsToOffsets.size() * pointerSize);
+ subCache.uniquedAuthGOTs->subCacheFileSize = CacheFileSize((uint64_t)subCacheUniquedGOTs.gotTargetsToOffsets.size() * pointerSize);
+
+ region.chunks.push_back(subCache.uniquedAuthGOTs.get());
+
+ // FIXME: Do we need this. No-one seems to read it from here, or could get it from the subCache instead
+ subCache.uniquedGOTsOptimizer.authGOTsChunk = subCache.uniquedAuthGOTs.get();
+ subCache.uniquedGOTsOptimizer.authGOTs.cacheChunk = subCache.uniquedGOTsOptimizer.authGOTsChunk;
+ } else {
+ subCache.uniquedGOTs = std::make_unique<UniquedGOTsChunk>();
+ subCache.uniquedGOTs->cacheVMSize = CacheVMSize((uint64_t)subCacheUniquedGOTs.gotTargetsToOffsets.size() * pointerSize);
+ subCache.uniquedGOTs->subCacheFileSize = CacheFileSize((uint64_t)subCacheUniquedGOTs.gotTargetsToOffsets.size() * pointerSize);
+
+ region.chunks.push_back(subCache.uniquedGOTs.get());
+
+ // FIXME: Do we need this. No-one seems to read it from here, or could get it from the subCache instead
+ subCache.uniquedGOTsOptimizer.regularGOTsChunk = subCache.uniquedGOTs.get();
+ subCache.uniquedGOTsOptimizer.regularGOTs.cacheChunk = subCache.uniquedGOTsOptimizer.regularGOTsChunk;
+ }
if ( this->config.log.printStats ) {
+ uint64_t totalSourceGOTs = 0;
+ for ( DylibSectionCoalescer::OptimizedSection* dylibOptimizedSection : dylibOptimizedSections ) {
+ totalSourceGOTs += dylibOptimizedSection->offsetMap.size();
+ }
+ const char* kind = auth ? "auth" : "regular";
stats.add(" got uniquing: uniqued %lld %s GOTs to %lld GOTs\n",
- subCacheUniquedGOTs->numSourceGOTs(), kindName, subCacheUniquedGOTs->numCacheGOTs());
+ totalSourceGOTs, kind, (uint64_t)subCacheUniquedGOTs.gotTargetsToOffsets.size());
}
}
}
@@ -4471,8 +3552,7 @@
const DylibSegmentChunk* segmentA = a->isDylibSegmentChunk();
const DylibSegmentChunk* segmentB = b->isDylibSegmentChunk();
- // There can be data chunks that aren't dylib segments, e.g. ObjCHeaderInfoReadWriteChunk.
- if ( segmentA && segmentB && segmentA->kind == DylibSegmentChunk::Kind::dylibDataDirty ) {
+ if ( segmentA->kind == DylibSegmentChunk::Kind::dylibDataDirty ) {
const auto& orderA = dirtyDataSegmentOrdering.find(segmentA->inputFile->path);
const auto& orderB = dirtyDataSegmentOrdering.find(segmentB->inputFile->path);
bool foundA = (orderA != dirtyDataSegmentOrdering.end());
@@ -4488,25 +3568,13 @@
return false;
}
- const DylibSegmentChunk* dylibA = a->isTPROChunk();
- const DylibSegmentChunk* dylibB = b->isTPROChunk();
- // Note this shouldn't be possible, but best to be safe and avoid asserting
- if ( dylibA && dylibB ) {
- // Sort dyld last so that its allocator gets packed with TPRO from other dylibs
- bool isDyldA = dylibA->inputFile->path == "/usr/lib/dyld";
- bool isDyldB = dylibB->inputFile->path == "/usr/lib/dyld";
- if ( isDyldA != isDyldB )
- return !isDyldA;
- }
-
// Note we are using a stable sort, so if the kind's aren't different, return false
// and we'll keep Section's in the order they were added to the vector
return false;
};
- auto dataConstSortOrder = [](const Chunk* a, const Chunk* b) -> bool {
- // Sort TPRO_CONST before DATA_CONST. This only happens on x86_64
- // where we put TPRO_CONST and DATA_CONST in the same Region
+ auto linkeditSortOrder = [](const Chunk* a, const Chunk* b) -> bool {
+ // Sort read-only segments before LINKEDIT
if ( a->sortOrder() != b->sortOrder() )
return a->sortOrder() < b->sortOrder();
@@ -4515,93 +3583,19 @@
return false;
};
- auto tproConstSortOrder = [](const Chunk* a, const Chunk* b) -> bool {
- // Sort uniqued GOTs before dylib TPRO_CONST. This puts the uniqued
- // GOTs before the libdyld TPRO_CONST, but should still be on the same page
- if ( a->sortOrder() != b->sortOrder() )
- return a->sortOrder() < b->sortOrder();
-
- const DylibSegmentChunk* dylibA = a->isTPROChunk();
- const DylibSegmentChunk* dylibB = b->isTPROChunk();
- // Note this shouldn't be possible, but best to be safe and avoid asserting
- if ( !dylibA || !dylibB )
- return false;
-
- // Sort dyld last so that its allocator gets packed with TPRO from other dylibs
- bool isDyldA = dylibA->inputFile->path == "/usr/lib/dyld";
- bool isDyldB = dylibB->inputFile->path == "/usr/lib/dyld";
- if ( isDyldA != isDyldB )
- return !isDyldA;
-
- // Note we are using a stable sort, so if the kind's aren't different, return false
- // and we'll keep Section's in the order they were added to the vector
- return false;
- };
-
- auto readOnlySortOrder = [](const Chunk* a, const Chunk* b) -> bool {
- // Sort read-only segments before LINKEDIT
- if ( a->sortOrder() != b->sortOrder() )
- return a->sortOrder() < b->sortOrder();
-
- // Note we are using a stable sort, so if the kind's aren't different, return false
- // and we'll keep Section's in the order they were added to the vector
- return false;
- };
-
+ // Only sort data/auth. Everything else is already in order
for ( SubCache& subCache : this->subCaches ) {
for ( Region& region : subCache.regions ) {
- switch ( region.kind ) {
- case Region::Kind::text:
- std::stable_sort(region.chunks.begin(), region.chunks.end(), textSortOrder);
- break;
- case Region::Kind::dataConst:
- case Region::Kind::authConst:
- std::stable_sort(region.chunks.begin(), region.chunks.end(), dataConstSortOrder);
- break;
- case Region::Kind::tproConst:
- case Region::Kind::tproAuthConst:
- std::stable_sort(region.chunks.begin(), region.chunks.end(), tproConstSortOrder);
- break;
- case Region::Kind::data:
- case Region::Kind::auth:
- std::stable_sort(region.chunks.begin(), region.chunks.end(), dataSortOrder);
- break;
- case Region::Kind::readOnly:
- case Region::Kind::linkedit:
- std::stable_sort(region.chunks.begin(), region.chunks.end(), readOnlySortOrder);
- break;
- case Region::Kind::unmapped:
- case Region::Kind::dynamicConfig:
- case Region::Kind::codeSignature:
- case Region::Kind::numKinds:
- break;
- }
- }
- }
-
- // After sorting, we have to add alignment chunks before/after x86_64 TPRO
- if ( this->config.layout.tproIsInData )
- addAlignmentChunks();
-}
-
-void SharedCacheBuilder::addAlignmentChunks()
-{
- for ( SubCache& subCache : this->subCaches ) {
- SubCache::forEachTPRORegionInData(&subCache, {}, ^(Region& region, const Chunk *firstChunk, const Chunk *lastChunk) {
- // Add alignment before the first chunk
- {
- auto firstPos = std::find(region.chunks.begin(), region.chunks.end(), firstChunk);
- assert(firstPos != region.chunks.end());
- region.chunks.insert(firstPos, ®ion.alignmentChunks.emplace_back());
- }
-
- // Add alignment after the last chunk
- {
- auto lastPos = std::find(region.chunks.begin(), region.chunks.end(), lastChunk);
- assert(lastPos != region.chunks.end());
- region.chunks.insert(lastPos + 1, ®ion.alignmentChunks.emplace_back());
- }
- });
+ if ( region.kind == Region::Kind::text ) {
+ std::stable_sort(region.chunks.begin(), region.chunks.end(), textSortOrder);
+ }
+ else if ( (region.kind == Region::Kind::data) || (region.kind == Region::Kind::auth) ) {
+ std::stable_sort(region.chunks.begin(), region.chunks.end(), dataSortOrder);
+ }
+ else if ( region.kind == Region::Kind::linkedit ) {
+ std::stable_sort(region.chunks.begin(), region.chunks.end(), linkeditSortOrder);
+ }
+ }
}
}
@@ -4632,17 +3626,11 @@
}
totalRegionVMSize = alignPage(totalRegionVMSize);
- uint64_t pagesToSlide = (totalRegionVMSize.rawValue() / builderConfig.slideInfo.slideInfoPageSize);
-
// Slide info needs a certain number of bytes per page
uint64_t slideInfoSize = 0;
switch ( builderConfig.slideInfo.slideInfoFormat.value() ) {
case cache_builder::SlideInfo::SlideInfoFormat::v1:
slideInfoSize += sizeof(dyld_cache_slide_info);
-
- // HACK: v1 info wants to round up the toc_count to 128 so that all entries start on a 128 boundary
- // We can do this by just adding one more page
- ++pagesToSlide;
break;
case cache_builder::SlideInfo::SlideInfoFormat::v2:
slideInfoSize += sizeof(dyld_cache_slide_info2);
@@ -4650,21 +3638,16 @@
case cache_builder::SlideInfo::SlideInfoFormat::v3:
slideInfoSize += sizeof(dyld_cache_slide_info3);
break;
- case cache_builder::SlideInfo::SlideInfoFormat::v5:
- slideInfoSize += sizeof(dyld_cache_slide_info5);
- break;
- }
- slideInfoSize += pagesToSlide * builderConfig.slideInfo.slideInfoBytesPerDataPage;
+ }
+ slideInfoSize += (totalRegionVMSize.rawValue() / builderConfig.slideInfo.slideInfoPageSize) * builderConfig.slideInfo.slideInfoBytesPerDataPage;
slideInfo->cacheVMSize = CacheVMSize(slideInfoSize);
slideInfo->subCacheFileSize = CacheFileSize(slideInfoSize);
};
for ( const SubCache& subCache : this->subCaches ) {
- calculateRegionSlideInfoSize(this->config, Region::Kind::tproConst, subCache.regions, subCache.tproConstSlideInfo);
calculateRegionSlideInfoSize(this->config, Region::Kind::data, subCache.regions, subCache.dataSlideInfo);
calculateRegionSlideInfoSize(this->config, Region::Kind::dataConst, subCache.regions, subCache.dataConstSlideInfo);
- calculateRegionSlideInfoSize(this->config, Region::Kind::tproAuthConst, subCache.regions, subCache.tproAuthConstSlideInfo);
calculateRegionSlideInfoSize(this->config, Region::Kind::auth, subCache.regions, subCache.authSlideInfo);
calculateRegionSlideInfoSize(this->config, Region::Kind::authConst, subCache.regions, subCache.authConstSlideInfo);
}
@@ -4696,7 +3679,7 @@
void SharedCacheBuilder::printSubCaches() const
{
- const bool printSegments = this->config.log.printDebugCacheLayout;
+ const bool printSegments = false;
if ( !this->config.log.printStats )
return;
@@ -4715,20 +3698,11 @@
case Region::Kind::dataConst:
regionName = "dataConst";
break;
- case Region::Kind::tproConst:
- regionName = "tproConst";
- break;
case Region::Kind::auth:
regionName = "auth";
break;
case Region::Kind::authConst:
regionName = "authConst";
- break;
- case Region::Kind::tproAuthConst:
- regionName = "tproAuthConst";
- break;
- case Region::Kind::readOnly:
- regionName = "readOnly";
break;
case Region::Kind::linkedit:
regionName = "linkedit";
@@ -4883,6 +3857,106 @@
assert(this->totalVMSize == totalCustomerCacheSize);
}
+ if ( this->totalVMSize > this->config.layout.cacheSize ) {
+ return Error("Cache overflow (0x%llx > 0x%llx)",
+ this->totalVMSize.rawValue(),
+ this->config.layout.cacheSize.rawValue());
+ }
+
+ return Error();
+}
+
+// This is the x86_64 sim layout, where each of TEXT/DATA/LINKEDIT has its own fixed address
+Error SharedCacheBuilder::computeSubCacheDiscontiguousSimVMLayout()
+{
+ // Add padding between each region, and set the Region VMAddr's
+ CacheVMAddress maxVMAddress = this->config.layout.cacheBaseAddress;
+ assert(this->subCaches.size() == 1);
+ SubCache& subCache = this->subCaches.front();
+ subCache.subCacheVMAddress = this->config.layout.cacheBaseAddress;
+
+ bool seenText = false;
+ bool seenData = false;
+ bool seenLinkedit = false;
+ bool seenDynamicConfig = false;
+ CacheVMAddress lastDataEnd;
+ CacheVMAddress linkEditEnd;
+ for ( Region& region : subCache.regions ) {
+ switch ( region.kind ) {
+ case Region::Kind::text:
+ assert(!seenText);
+ seenText = true;
+ region.subCacheVMAddress = this->config.layout.discontiguous->simTextBaseAddress;
+
+ // Check for overflow
+ if ( region.subCacheVMSize > this->config.layout.discontiguous->simTextSize ) {
+ return Error("Overflow in text (0x%llx > 0x%llx)",
+ region.subCacheVMSize.rawValue(),
+ this->config.layout.discontiguous->simTextSize.rawValue());
+ }
+ break;
+ case Region::Kind::dataConst:
+ case Region::Kind::data:
+ case Region::Kind::auth:
+ case Region::Kind::authConst:
+ if ( seenData ) {
+ // This data follows from the previous one
+ region.subCacheVMAddress = lastDataEnd;
+ } else {
+ seenData = true;
+ region.subCacheVMAddress = this->config.layout.discontiguous->simDataBaseAddress;
+ }
+ lastDataEnd = region.subCacheVMAddress + region.subCacheVMSize;
+ break;
+ case Region::Kind::linkedit:
+ assert(!seenLinkedit);
+ seenLinkedit = true;
+ region.subCacheVMAddress = this->config.layout.discontiguous->simLinkeditBaseAddress;
+
+ // Check for overflow
+ if ( region.subCacheVMSize > this->config.layout.discontiguous->simLinkeditSize ) {
+ return Error("Overflow in linkedit (0x%llx > 0x%llx)",
+ region.subCacheVMSize.rawValue(),
+ this->config.layout.discontiguous->simLinkeditSize.rawValue());
+ }
+ linkEditEnd = region.subCacheVMAddress + region.subCacheVMSize;
+ break;
+ case Region::Kind::dynamicConfig:
+ assert(!seenDynamicConfig);
+ seenDynamicConfig = true;
+ // Grab space right after the linkedit
+ region.subCacheVMAddress = linkEditEnd;
+ // Check for overflow
+ if ( region.subCacheVMSize > this->config.layout.discontiguous->simLinkeditSize ) {
+ return Error("Overflow in dynamicConfig (0x%llx > 0x%llx)",
+ region.subCacheVMSize.rawValue(),
+ this->config.layout.discontiguous->simLinkeditSize.rawValue());
+ }
+ break;
+ case Region::Kind::unmapped:
+ case Region::Kind::codeSignature:
+ break;
+ case Region::Kind::numKinds:
+ assert(0);
+ break;
+ }
+
+ if ( seenData ) {
+ // Check for overflow
+ CacheVMSize dataSize(lastDataEnd.rawValue() - this->config.layout.discontiguous->simDataBaseAddress.rawValue());
+ if ( dataSize > this->config.layout.discontiguous->simDataSize ) {
+ return Error("Overflow in data (0x%llx > 0x%llx)",
+ dataSize.rawValue(),
+ this->config.layout.discontiguous->simDataSize.rawValue());
+ }
+ }
+
+ if ( region.needsSharedCacheReserveAddressSpace() )
+ maxVMAddress = region.subCacheVMAddress + region.subCacheVMSize;
+ }
+
+ this->totalVMSize = CacheVMSize((maxVMAddress - this->config.layout.cacheBaseAddress).rawValue());
+
return Error();
}
@@ -4930,15 +4004,12 @@
case Region::Kind::codeSignature:
case Region::Kind::numKinds:
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:
lastReadWriteRegion = ®ion;
break;
- case Region::Kind::readOnly:
case Region::Kind::dynamicConfig:
case Region::Kind::linkedit:
lastReadOnlyRegion = ®ion;
@@ -4975,105 +4046,14 @@
}
this->totalVMSize = CacheVMSize((vmAddress - this->config.layout.cacheBaseAddress).rawValue());
+
+ if ( this->totalVMSize > this->config.layout.cacheSize ) {
+ return Error("Cache overflow (0x%llx > 0x%llx)",
+ this->totalVMSize.rawValue(),
+ this->config.layout.cacheSize.rawValue());
+ }
return Error();
-}
-
-void SharedCacheBuilder::evictLeafDylibs(CacheVMSize reductionTarget)
-{
- // build a reverse map of all dylib dependencies
- std::unordered_map<std::string_view, std::unordered_set<std::string_view>> references;
- // Ensure we have an entry (even if it is empty)
- for ( const CacheDylib& cacheDylib : cacheDylibs )
- references[cacheDylib.installName] = { };
-
- for ( const CacheDylib& cacheDylib : cacheDylibs ) {
- for ( const CacheDylib::DependentDylib& depDylib : cacheDylib.dependents ) {
- // Skip missing weak links
- if ( depDylib.dylib == nullptr )
- continue;
- references[depDylib.dylib->installName].insert(cacheDylib.installName);
- }
- }
-
- struct DylibAndSize
- {
- CacheDylib* dylib;
- CacheVMSize size;
- };
-
- // Find the sizes of all the dylibs
- std::vector<DylibAndSize> dylibsToSort;
- for ( CacheDylib& cacheDylib : cacheDylibs ) {
- CacheVMSize segsSize = CacheVMSize(0ULL);
- for ( const DylibSegmentChunk& segment : cacheDylib.segments ) {
- if ( segment.segmentName == "__LINKEDIT" )
- continue;
-
- segsSize += segment.cacheVMSize;
- }
- dylibsToSort.push_back({ &cacheDylib, segsSize });
- }
-
- // Build an ordered list of what to remove. At each step we do following
- // 1) Find all dylibs that nothing else depends on
- // 2a) If any of those dylibs are not in the order select the largest one of them
- // 2b) If all the leaf dylibs are in the order file select the last dylib that appears last in the order file
- // 3) Remove all entries to the removed file from the reverse dependency map
- // 4) Go back to one and repeat until there are no more evictable dylibs
- // This results in us always choosing the locally optimal selection, and then taking into account how that impacts
- // the dependency graph for subsequent selections
-
- std::vector<DylibAndSize> sortedDylibs;
- bool candidateFound = true;
- while ( candidateFound ) {
- candidateFound = false;
- DylibAndSize candidate;
- uint64_t candidateOrder = 0;
- for( const auto& dylib : dylibsToSort ) {
- const auto& dylibRefs = references.at(dylib.dylib->installName);
- if ( !dylibRefs.empty())
- continue;
-
- const auto& j = options.dylibOrdering.find(std::string(dylib.dylib->installName));
- uint64_t order = 0;
- if ( j != options.dylibOrdering.end() ) {
- order = j->second;
- } else {
- // Not in the order file, set order sot it goes to the front of the list
- order = UINT64_MAX;
- }
- if ( order > candidateOrder || (order == UINT64_MAX && candidate.size < dylib.size) ) {
- // The new file is either a lower priority in the order file
- // or the same priority as the candidate but larger
- candidate = dylib;
- candidateOrder = order;
- candidateFound = true;
- }
- }
- if (candidateFound) {
- sortedDylibs.push_back(candidate);
- references.erase(candidate.dylib->installName);
- for (auto& dependent : references) {
- (void)dependent.second.erase(candidate.dylib->installName);
- }
- auto j = std::find_if(dylibsToSort.begin(), dylibsToSort.end(),
- [&candidate](const DylibAndSize& dylib) {
- return candidate.dylib->installName == dylib.dylib->installName;
- });
- if ( j != dylibsToSort.end() ) {
- dylibsToSort.erase(j);
- }
- }
- }
-
- // build set of dylibs that if removed will allow cache to build
- for ( DylibAndSize& dylib : sortedDylibs ) {
- this->evictedDylibs.push_back(dylib.dylib->inputFile->path);
- if ( dylib.size > reductionTarget )
- break;
- reductionTarget -= dylib.size;
- }
}
// In file layout, we need each Region to start page-aligned. Within a Region, we can pack pages
@@ -5152,15 +4132,13 @@
if ( Error error = computeSubCacheContiguousVMLayout(); error.hasError() )
return error;
} else {
- if ( Error error = computeSubCacheDiscontiguousVMLayout(); error.hasError() )
- return error;
- }
-
- if ( this->totalVMSize > this->config.layout.cacheSize ) {
- evictLeafDylibs(this->totalVMSize - this->config.layout.cacheSize);
- return Error("Cache overflow (0x%llx > 0x%llx)",
- this->totalVMSize.rawValue(),
- this->config.layout.cacheSize.rawValue());
+ if ( this->options.isSimultor() ) {
+ if ( Error error = computeSubCacheDiscontiguousSimVMLayout(); error.hasError() )
+ return error;
+ } else {
+ if ( Error error = computeSubCacheDiscontiguousVMLayout(); error.hasError() )
+ return error;
+ }
}
// Update Section VMAddr's now that we know where all the Region's are in memory
@@ -5177,12 +4155,18 @@
}
}
+ if ( this->totalVMSize > this->config.layout.cacheSize ) {
+ return Error("Cache overflow (0x%llx > 0x%llx)",
+ this->totalVMSize.rawValue(),
+ this->config.layout.cacheSize.rawValue());
+ }
+
return Error();
}
Error SharedCacheBuilder::allocateSubCacheBuffers()
{
- const bool log = this->options.debug;
+ const bool log = false;
Timer::Scope timedScope(this->config, "allocateSubCacheBuffers time");
@@ -5193,6 +4177,7 @@
const Region& lastRegion = subCache.regions.back();
uint64_t bufferSize = (lastRegion.subCacheFileOffset + lastRegion.subCacheFileSize).rawValue();
+#if SUPPORT_CACHE_BUILDER_MEMORY_BUFFERS
// The MRM builder has no file system, so use an in-memory buffer
vm_address_t fullAllocatedBuffer;
if ( kern_return_t kr = vm_allocate(mach_task_self(), &fullAllocatedBuffer, bufferSize, VM_FLAGS_ANYWHERE); kr != 0 ) {
@@ -5202,6 +4187,47 @@
uint8_t *buffer = (uint8_t*)fullAllocatedBuffer;
subCache.buffer = buffer;
subCache.bufferSize = bufferSize;
+#else
+ char pathTemplate[] = "/tmp/temp.XXXXXX";
+ int fd = mkstemp(pathTemplate);
+ if ( fd == -1 ) {
+ // Failed to create the file
+ return Error("could not create shared cache file because: %s", strerror(errno));
+ }
+
+ // Resize the file
+ if ( int result = ftruncate(fd, bufferSize); result == -1 ) {
+ // Failed to resize to the space we need
+ return Error("could not truncate shared cache file because: %s", strerror(errno));
+ }
+
+ void* buffer = mmap(nullptr, (vm_size_t)bufferSize, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
+ if ( buffer == MAP_FAILED ) {
+ // Failed to mmap the file
+ return Error("could not mmap shared cache file because: %s", strerror(errno));
+ }
+
+ // TODO: It would be great to unlink the file, so that it won't be there on disk if the builder crashes
+#if 0
+ // Unlink the file. This way, it'll be removed if we crash
+ if ( int result = unlinkat(fd, pathTemplate, 0) ) {
+ // Failed to unlink the file
+ return Error("could not unlink shared cache file because: %s", strerror(errno));
+ }
+
+ // Close the file as we don't need it now that we have the mmapped buffer
+ if ( int result = close(fd) ) {
+ // Failed to close the file
+ return Error("could not close shared cache file because: %s", strerror(errno));
+ }
+#endif
+
+ subCache.buffer = (uint8_t*)buffer;
+ subCache.bufferSize = bufferSize;
+ subCache.fd = fd;
+ subCache.tempPath = pathTemplate;
+
+#endif // SUPPORT_CACHE_BUILDER_MEMORY_BUFFERS
if ( log ) {
this->config.log.log("SubCache[%d] allocated (%p..%p)\n",
@@ -5227,7 +4253,6 @@
assert(!cacheDylib.segments.empty());
assert(cacheDylib.segments[0].kind == cache_builder::DylibSegmentChunk::Kind::dylibText);
cacheDylib.cacheMF = (MachOFile*)cacheDylib.segments[0].subCacheBuffer;
- cacheDylib.cacheHdr = (const Header*)cacheDylib.segments[0].subCacheBuffer;
cacheDylib.cacheLoadAddress = cacheDylib.segments[0].cacheVMAddress;
}
@@ -5243,7 +4268,7 @@
}
// Add a watchpoint for anything we need to debug
-#if 0
+#if DEBUG
{
CacheVMAddress vmAddrToWatch(0x00007FFB40FB4D58ULL);
for ( const SubCache& subCache : this->subCaches ) {
@@ -5265,31 +4290,6 @@
}
#endif
-
- // Add a watchpoint for anything we need to debug
-#if 0
- {
- CacheFileOffset fileOffsetToWatch(0x0012bc0ULL);
- std::string_view suffixToWatch = ".01";
- for ( const SubCache& subCache : this->subCaches ) {
- if ( subCache.fileSuffix != suffixToWatch )
- continue;
- for ( const Region& region : subCache.regions ) {
- for ( const Chunk* chunk : region.chunks ) {
- if ( fileOffsetToWatch < chunk->subCacheFileOffset )
- continue;
- if ( fileOffsetToWatch >= (chunk->subCacheFileOffset + chunk->subCacheFileSize) )
- continue;
- uint64_t offsetInChunk = fileOffsetToWatch.rawValue() - chunk->subCacheFileOffset.rawValue();
- uint8_t* addrToWatch = chunk->subCacheBuffer + offsetInChunk;
- printf("watchpoint set expression -w w -s 8 -- %p\n", addrToWatch);
- printf("");
- }
- }
- }
- }
-#endif
-
return Error();
}
@@ -5340,6 +4340,7 @@
assert(segment.subCacheFileSize.rawValue() == 0);
segment.subCacheFileSize = region->subCacheFileSize;
+
}
}
@@ -5396,10 +4397,7 @@
case Chunk::Kind::linkeditExportTrie:
movedLinkedit.kind = MovedLinkedit::Kind::exportTrie;
break;
- case Chunk::Kind::linkeditFunctionVariants:
- movedLinkedit.kind = MovedLinkedit::Kind::functionVariants;
- break;
- default:
+ default:
assert(0);
break;
}
@@ -5446,40 +4444,6 @@
});
assert(!err.hasError());
-
- // Now that we have an adjustor, use it to adjust the bindTargets
- err = parallel::forEach(this->cacheDylibs, ^(size_t index, CacheDylib& cacheDylib) {
- for ( CacheDylib::BindTarget& bindTarget : cacheDylib.bindTargets ) {
- // Adjust the bind target. We have a runtime offset for the target input dylib, but we need to know where that runtime Offset will
- // map to in the target cache dylib
- switch ( bindTarget.kind ) {
- case CacheDylib::BindTarget::Kind::absolute:
- // Skip these. They won't change due to shifting the input dylib in to the cache
- break;
- case CacheDylib::BindTarget::Kind::inputImage: {
- // Convert from an input dylib offset to the cache dylib offset
- CacheDylib::BindTarget::InputImage inputImage = bindTarget.inputImage;
- InputDylibVMAddress targetInputVMAddr = inputImage.targetDylib->inputLoadAddress + inputImage.targetRuntimeOffset;
- CacheVMAddress targetCacheVMAddr = inputImage.targetDylib->adjustor->adjustVMAddr(targetInputVMAddr);
-
- // Actually change the bindTarget to reflect the new type
- bindTarget.kind = CacheDylib::BindTarget::Kind::cacheImage;
- bindTarget.inputImage.~InputImage();
- bindTarget.cacheImage = (CacheDylib::BindTarget::CacheImage) {
- VMOffset(targetCacheVMAddr - inputImage.targetDylib->cacheLoadAddress),
- inputImage.targetDylib, inputImage.isWeakDef,
- inputImage.isFunctionVariant, inputImage.functionVariantTableIndex
- };
- break;
- }
- case CacheDylib::BindTarget::Kind::cacheImage:
- return Error("Shouldn't see cacheImage fixups at this point");
- }
- }
- return Error();
- });
-
- assert(!err.hasError());
}
void SharedCacheBuilder::adjustObjCClasses()
@@ -5518,108 +4482,15 @@
}
}
-void SharedCacheBuilder::adjustObjCCategories()
-{
- if ( this->objcOptimizer.objcDylibs.empty() )
- return;
-
- Timer::Scope timedScope(this->config, "adjustObjCCategories time");
-
- // Categories were stored as input dylib VMAddr's. Convert to cache dylib VMAddr's
- for ( auto& categoryInfo : this->objcCategoryOptimizer.categories ) {
- CacheDylib* cacheDylib = this->objcOptimizer.objcDylibs[categoryInfo.dylibObjcIndex.value()];
-
- // category address
- if ( categoryInfo.vmAddress.has_value() ) {
- uint64_t inputAddr = categoryInfo.vmAddress.value().rawValue();
- InputDylibVMAddress inputVMAddr(inputAddr);
- CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
- categoryInfo.vmAddress = VMAddress(cacheVMAddr.rawValue());
- }
-
- // class address
- if ( categoryInfo.classVMAddress.has_value() ) {
- CacheDylib& classDylib = this->cacheDylibs[categoryInfo.classDylibIndex.value()];
- uint64_t inputAddr = categoryInfo.classVMAddress.value().rawValue();
- InputDylibVMAddress inputVMAddr(inputAddr);
- CacheVMAddress cacheVMAddr = classDylib.adjustor->adjustVMAddr(inputVMAddr);
- categoryInfo.classVMAddress = VMAddress(cacheVMAddr.rawValue());
- }
-
- // instance methods
- if ( categoryInfo.iMethodListVMAddress.has_value() ) {
- uint64_t inputAddr = categoryInfo.iMethodListVMAddress.value().rawValue();
- InputDylibVMAddress inputVMAddr(inputAddr);
- CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
- categoryInfo.iMethodListVMAddress = VMAddress(cacheVMAddr.rawValue());
- }
-
- // class methods
- if ( categoryInfo.cMethodListVMAddress.has_value() ) {
- uint64_t inputAddr = categoryInfo.cMethodListVMAddress.value().rawValue();
- InputDylibVMAddress inputVMAddr(inputAddr);
- CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
- categoryInfo.cMethodListVMAddress = VMAddress(cacheVMAddr.rawValue());
- }
-
- // protocols
- if ( categoryInfo.protocolListVMAddress.has_value() ) {
- uint64_t inputAddr = categoryInfo.protocolListVMAddress.value().rawValue();
- InputDylibVMAddress inputVMAddr(inputAddr);
- CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
- categoryInfo.protocolListVMAddress = VMAddress(cacheVMAddr.rawValue());
- }
-
- // instance properties
- if ( categoryInfo.iPropertyListVMAddress.has_value() ) {
- uint64_t inputAddr = categoryInfo.iPropertyListVMAddress.value().rawValue();
- InputDylibVMAddress inputVMAddr(inputAddr);
- CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
- categoryInfo.iPropertyListVMAddress = VMAddress(cacheVMAddr.rawValue());
- }
-
- // class properties
- if ( categoryInfo.cPropertyListVMAddress.has_value() ) {
- uint64_t inputAddr = categoryInfo.cPropertyListVMAddress.value().rawValue();
- InputDylibVMAddress inputVMAddr(inputAddr);
- CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
- categoryInfo.cPropertyListVMAddress = VMAddress(cacheVMAddr.rawValue());
- }
- }
-}
-
-Error SharedCacheBuilder::calculatePatchTableSize()
-{
- Stats stats(this->config);
- Timer::Scope timedScope(this->config, "calculatePatchTableSize time");
-
- PatchTableBuilder& builder = this->patchTableOptimizer.builder;
- Error err = builder.prepare(this->cacheDylibs, this->patchTableOptimizer.patchInfos);
- if ( err.hasError() )
- return err;
-
- uint64_t patchTableSize = builder.getPatchTableSize();
-
- auto* patchTableChunk = this->patchTableOptimizer.patchTableChunk;
- patchTableChunk->cacheVMSize = CacheVMSize(patchTableSize);
- patchTableChunk->subCacheFileSize = CacheFileSize(patchTableSize);
-
- if ( this->config.log.printStats ) {
- stats.add(" patch table: used %lld bytes\n", patchTableSize);
- }
-
- return Error();
-}
-
Error SharedCacheBuilder::emitPatchTable()
{
Stats stats(this->config);
Timer::Scope timedScope(this->config, "emitPatchTable time");
-
+
// Skip this optimization on simulator until we've qualified it there
__block PatchTableBuilder::PatchableClassesSet patchableObjCClasses;
__block PatchTableBuilder::PatchableSingletonsSet patchableCFObj2;
- if ( !this->options.isSimulator() ) {
+ if ( !this->options.isSimultor() ) {
for ( const CacheDylib& cacheDylib : this->cacheDylibs ) {
__block objc_visitor::Visitor objcVisitor = makeInputDylibObjCVisitor(cacheDylib);
objcVisitor.forEachClassAndMetaClass(^(const objc_visitor::Class& objcClass, bool& stopClass) {
@@ -5627,216 +4498,50 @@
patchableObjCClasses.insert(cacheDylib.adjustor->adjustVMAddr(inputVMAddr));
});
- cacheDylib.cacheHdr->forEachSingletonPatch(^(uint64_t runtimeOffset) {
+ // Note we have a diagnostic object here, but we don't care if it fails. Then we'll
+ // just skip singleton patching on this dylib
+ Diagnostics diag;
+ cacheDylib.cacheMF->forEachSingletonPatch(diag, ^(MachOFile::SingletonPatchKind kind, uint64_t runtimeOffset) {
patchableCFObj2.insert(cacheDylib.cacheLoadAddress + VMOffset(runtimeOffset));
});
}
}
-
- const auto* patchTableChunk = this->patchTableOptimizer.patchTableChunk;
+
+ PatchTableBuilder builder;
+ Error err = builder.build(this->cacheDylibs, this->patchTableOptimizer.patchInfos,
+ patchableObjCClasses,
+ patchableCFObj2,
+ this->config.layout.cacheBaseAddress);
+ if ( err.hasError() )
+ return err;
+
+ auto* patchTableChunk = this->patchTableOptimizer.patchTableChunk;
uint8_t* buffer = patchTableChunk->subCacheBuffer;
uint64_t bufferSize = patchTableChunk->subCacheFileSize.rawValue();
uint64_t patchInfoAddr = patchTableChunk->cacheVMAddress.rawValue();
-
- PatchTableBuilder& builder = this->patchTableOptimizer.builder;
- if ( Error error = builder.build(this->cacheDylibs, this->patchTableOptimizer.patchInfos, patchableObjCClasses, patchableCFObj2, this->config.layout.cacheBaseAddress) )
- return error;
if ( Error error = builder.write(buffer, bufferSize, patchInfoAddr); error.hasError() )
return error;
-
+
// We don't need the patchInfos, so clear it to save memory
this->patchTableOptimizer.patchInfos.clear();
- return Error();
-}
-
-Error SharedCacheBuilder::emitPrewarmingData()
-{
- // Skip everything if the JSON file is empty
- if ( this->options.prewarmingOptimizations.empty() )
- return Error::none();
-
- using json::Node;
- using json::NodeValueType;
-
- Stats stats(this->config);
- Timer::Scope timedScope(this->config, "emitPrewarmingData time");
-
- __block Diagnostics diag;
- Node rootNode = json::readJSON(diag, this->options.prewarmingOptimizations.data(),
- this->options.prewarmingOptimizations.size(),
- false /* useJSON5 */);
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
-
- std::unordered_map<std::string_view, const CacheDylib*> dylibMap;
- for ( const CacheDylib& cacheDylib : cacheDylibs )
- dylibMap[cacheDylib.installName] = &cacheDylib;
-
- // Add install names too, just in case dylibs are moving
- dylibMap.insert(this->dylibAliases.begin(), this->dylibAliases.end());
-
- // Format is something like:
- // [
- // {
- // "install_name": "..."
- // "locations": [
- // {
- // "name": "symbol name",
- // "offset": 0
- // }
- // ]
- // },
- // ...
- // ]
- if ( rootNode.type != NodeValueType::Array )
- return Error::none();
-
- std::vector<dyld_prewarming_entry> prewarmingEntries;
-
- const uint64_t PrewarmingPageSize = DYLD_CACHE_PREWARMING_DATA_PAGE_SIZE;
- const uint64_t PrewarmingPageMask = ~(PrewarmingPageSize - 1);
-
- // FIXME: Do this in parallel if needed, or even in CacheDylib when it goes parallel
- for ( const Node& fileNode : rootNode.array ) {
- // Find the install name and locations array
- auto installNameIt = fileNode.map.find("install_name");
- auto locationsIt = fileNode.map.find("locations");
- if ( (installNameIt == fileNode.map.end()) || (locationsIt == fileNode.map.end()) )
- continue;
-
- const Node& installNameNode = installNameIt->second;
- const Node& locationsNode = locationsIt->second;
-
- // Find the cache dylib for this install name
- const CacheDylib* cacheDylib = nullptr;
- if ( auto it = dylibMap.find(installNameNode.value); it != dylibMap.end() )
- cacheDylib = it->second;
- else
- continue;
-
- // Get the exports trie
- __block const uint8_t* trieStart = nullptr;
- __block size_t trieSize = 0;
- cacheDylib->inputMF->withFileLayout(diag, ^(const mach_o::Layout &layout) {
- if ( layout.linkedit.exportsTrie.hasValue() ) {
- trieStart = layout.linkedit.exportsTrie.buffer;
- trieSize = layout.linkedit.exportsTrie.bufferSize;
- }
- });
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
-
- if ( trieStart == nullptr )
- continue;
-
- ExportsTrie exportsTrie(trieStart, trieSize);
-
- // Lazily make a map of local symbols if we need it
- __block std::unordered_map<std::string_view, uint64_t> localsMap;
- auto populateLocals = ^() {
- cacheDylib->inputMF->withFileLayout(diag, ^(const mach_o::Layout& layout) {
- mach_o::SymbolTable symbolTable(layout);
-
- symbolTable.forEachLocalSymbol(diag, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
- uint8_t n_sect, uint16_t n_desc, bool& stop) {
- if ( ((n_type & N_TYPE) == N_SECT) && ((n_type & N_STAB) == 0) ) {
- localsMap[symbolName] = (n_value - cacheDylib->inputLoadAddress.rawValue());
- }
- });
- });
- };
-
- // Walk all locations, looking them up in exports/locals
- for ( const Node& locationNode : locationsNode.array ) {
- auto nameIt = locationNode.map.find("name");
- auto offsetIt = locationNode.map.find("offset");
- if ( (nameIt == locationNode.map.end()) || (offsetIt == locationNode.map.end()) )
- continue;
-
- uint64_t offset = parseRequiredInt(diag, offsetIt->second);
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
-
- // Check for an export first, then a local
- uint64_t implOffset = 0;
- mach_o::Symbol symbol;
- if ( exportsTrie.hasExportedSymbol(nameIt->second.value.c_str(), symbol) ) {
- if ( !symbol.isRegular(implOffset) )
- continue;
- } else {
- if ( localsMap.empty() ) {
- populateLocals();
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
- }
-
- if ( auto localsIt = localsMap.find(nameIt->second.value); localsIt != localsMap.end() )
- implOffset = localsIt->second;
- }
-
- InputDylibVMAddress inputVMAddr(cacheDylib->inputLoadAddress + VMOffset(implOffset));
- CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
- cacheVMAddr += VMOffset(offset);
-
- VMOffset cacheVMOffset = cacheVMAddr - this->config.layout.cacheBaseAddress;
-
- prewarmingEntries.push_back({ cacheVMOffset.rawValue() & PrewarmingPageMask, 1 });
- }
- }
-
- PrewarmingOptimizer& opt = this->prewarmingOptimizer;
- dyld_prewarming_header* header = (dyld_prewarming_header*)opt.prewarmingChunk->subCacheBuffer;
- const void* bufferEnd = opt.prewarmingChunk->subCacheBuffer + opt.prewarmingChunk->subCacheFileSize.rawValue();
-
- if ( prewarmingEntries.empty() ) {
- header->version = 1;
- header->count = 0;
- return Error::none();
- }
-
- // Sort by ascending VM addresses
- std::sort(prewarmingEntries.begin(), prewarmingEntries.end(),
- [](const dyld_prewarming_entry& a, const dyld_prewarming_entry& b) {
- return a.cacheVMOffset < b.cacheVMOffset;
- });
-
- // Combine adjacent entries
- std::vector<dyld_prewarming_entry> combinedEntries;
- combinedEntries.push_back(prewarmingEntries.front());
- for ( const dyld_prewarming_entry& entry : std::span(prewarmingEntries).subspan(1) ) {
- dyld_prewarming_entry& last = combinedEntries.back();
- if ( entry.cacheVMOffset == last.cacheVMOffset )
- continue;
- if ( entry.cacheVMOffset == (last.cacheVMOffset + PrewarmingPageSize) ) {
- ++last.numPages;
- } else {
- combinedEntries.push_back(entry);
- }
- }
-
- // write the results
- dyld_prewarming_entry* firstOutputEntry = &header->entries[0];
- dyld_prewarming_entry* lastOutputEntry = &header->entries[combinedEntries.size()];
- if ( lastOutputEntry > bufferEnd )
- return Error("overflow in prewarming optimizer (%p vs %p)", lastOutputEntry, bufferEnd);
-
- header->version = 1;
- header->count = (uint32_t)combinedEntries.size();
- memcpy(firstOutputEntry, combinedEntries.data(), sizeof(dyld_prewarming_entry) * header->count);
-
+ if ( this->config.log.printStats ) {
+ uint64_t patchInfoSize = builder.getPatchTableSize();
+ stats.add(" patch table: used %lld out of %lld bytes of buffer\n", patchInfoSize, bufferSize);
+ }
+
return Error();
}
// dyld4 needs a fake "main.exe" to set up the state.
// On macOS this *has* to come from an actual executable, as choosing a zippered
// dylib may incorrectly lead to setting up the ProcessConfig as iOSMac.
-// Simulators and ExclaveKit don't have executables yet so choose a dylib there
+// Simulators don't have executables yet so choose a dylib there
static const MachOFile* getFakeMainExecutable(const BuilderOptions& options,
std::span<CacheDylib> cacheDylibs,
std::span<InputFile*> executableFiles)
{
- if ( options.isSimulator() ) {
+ if ( options.isSimultor() ) {
std::string_view installName = "/usr/lib/libSystem.B.dylib";
for ( const CacheDylib& cacheDylib : cacheDylibs ) {
if ( cacheDylib.installName == installName ) {
@@ -5844,17 +4549,9 @@
return cacheDylib.cacheMF;
}
}
- } else if (options.isExclaveKit() ) {
- std::string_view installName = "/System/ExclaveKit/usr/lib/libSystem.dylib";
- for ( const CacheDylib& cacheDylib : cacheDylibs ) {
- if ( cacheDylib.installName == installName ) {
- assert(cacheDylib.cacheMF != nullptr);
- return cacheDylib.cacheMF;
- }
- }
} else {
const char* binPath = "/usr/bin/";
- if ( options.platform == Platform::driverKit )
+ if ( options.platform == dyld3::Platform::driverKit )
binPath = "/System/Library/DriverExtensions/";
for ( const InputFile* exeFile : executableFiles ) {
if ( startsWith(exeFile->path, binPath) )
@@ -5912,8 +4609,8 @@
}
// The cache segments don't have the permissions. Get that from the load commands
- cacheDylib.cacheHdr->forEachSegment(^(const Header::SegmentInfo& info, bool& stop) {
- segments[info.segmentIndex].protections = info.initProt;
+ cacheDylib.cacheMF->forEachSegment(^(const MachOFile::SegmentInfo& info, bool& stop) {
+ segments[info.segIndex].protections = info.protections;
});
this->dylibSegmentLayout.push_back(std::move(segments));
}
@@ -5940,11 +4637,6 @@
linkeditLayout.exportsTrie.entryCount = 0; // Not needed here
linkeditLayout.exportsTrie.hasLinkedit = true;
break;
- case MovedLinkedit::Kind::functionVariants:
- linkeditLayout.functionVariants.buffer = kindAndLinkdit.second.cacheLocation;
- linkeditLayout.functionVariants.bufferSize = (uint32_t)kindAndLinkdit.second.dataSize.rawValue();
- linkeditLayout.functionVariants.hasLinkedit = true;
- break;
case MovedLinkedit::Kind::numKinds:
// This should never happen
assert(false);
@@ -5970,21 +4662,21 @@
this->executableSegmentLayout.reserve(executableFiles.size());
for ( const InputFile* executableFile : executableFiles ) {
__block std::vector<mach_o::SegmentLayout> segments;
- ((const Header*)executableFile->mf)->forEachSegment(^(const Header::SegmentInfo& info, bool& stop) {
+ executableFile->mf->forEachSegment(^(const MachOFile::SegmentInfo& info, bool& stop) {
// Note file layout here, not VM layout
uint8_t* segmentBuffer = (uint8_t*)executableFile->mf + info.fileOffset;
mach_o::SegmentLayout segment;
- segment.vmAddr = info.vmaddr;
- segment.vmSize = info.vmsize;
+ segment.vmAddr = info.vmAddr;
+ segment.vmSize = info.vmSize;
segment.fileOffset = info.fileOffset;
segment.fileSize = info.fileSize;
segment.buffer = segmentBuffer;
- segment.protections = info.initProt;
+ segment.protections = info.protections;
segment.kind = mach_o::SegmentLayout::Kind::unknown;
- if ( info.segmentName == "__TEXT" ) {
+ if ( !strcmp(info.segName, "__TEXT") ) {
segment.kind = mach_o::SegmentLayout::Kind::text;
- } else if ( info.segmentName == "__LINKEDIT" ) {
+ } else if ( !strcmp(info.segName, "__LINKEDIT") ) {
segment.kind = mach_o::SegmentLayout::Kind::linkedit;
}
@@ -6076,7 +4768,7 @@
}
}
- Loader::LoadOptions::Finder loaderFinder = ^(Diagnostics& loadDiag, Platform, const char* loadPath, const dyld4::Loader::LoadOptions& options) {
+ Loader::LoadOptions::Finder loaderFinder = ^(Diagnostics& loadDiag, dyld3::Platform, const char* loadPath, const dyld4::Loader::LoadOptions& options) {
auto pos = loadersMap.find(loadPath);
if ( pos != loadersMap.end() ) {
return (const Loader*)pos->second;
@@ -6105,7 +4797,7 @@
Diagnostics loadDiag;
((Loader*)ldr)->loadDependents(loadDiag, state, options);
if ( loadDiag.hasError() ) {
- return Error("%s, loading dependents of %s", loadDiag.errorMessageCStr(), ldr->path(state));
+ return Error("%s, loading dependents of %s", loadDiag.errorMessageCStr(), ldr->path());
}
}
@@ -6137,7 +4829,7 @@
return Error("Could not find a main executable for building cache loaders");
const LayoutBuilder layoutBuilder(cacheDylibs, { });
- STACK_ALLOCATOR(processConfigAlloc, 0);
+ EphemeralAllocator processConfigAlloc;
__block dyld4::Vector<ProcessConfig::DyldCache::CacheDylib> processConfigDylibs(processConfigAlloc);
for ( uint32_t dylibIndex = 0; dylibIndex != this->cacheDylibs.size(); ++dylibIndex ) {
@@ -6160,12 +4852,11 @@
}
// build PrebuiltLoaderSet of all dylibs in cache
- STACK_ALLOCATOR(alloc, 0);
KernelArgs kernArgs(mainExecutable, { "test.exe" }, {}, {});
SyscallDelegate osDelegate;
+ EphemeralAllocator alloc;
ProcessConfig processConfig(&kernArgs, osDelegate, alloc);
- RuntimeLocks locks;
- RuntimeState state(processConfig, locks, alloc);
+ RuntimeState state(processConfig, alloc);
// FIXME: This is terrible and needs to be a real reset method
processConfig.dyldCache.cacheBuilderDylibs = &processConfigDylibs;
@@ -6221,18 +4912,18 @@
VMAddress& protocolClassVMAddr, MachOFile::PointerMetaData& protocolClassPMD)
{
for ( CacheDylib* cacheDylib : objcDylibs ) {
- if ( cacheDylib->installName.ends_with("/usr/lib/libobjc.A.dylib" )) {
+ if ( cacheDylib->installName == "/usr/lib/libobjc.A.dylib" ) {
__block InputDylibVMAddress inputOptPtrsVMAddress;
__block uint64_t sectionSize = 0;
__block bool found = false;
- cacheDylib->inputHdr->forEachSection(^(const Header::SectionInfo& sectInfo, bool& stop) {
- if ( !sectInfo.segmentName.starts_with("__DATA") && !sectInfo.segmentName.starts_with("__AUTH") )
+ cacheDylib->inputMF->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
+ if ( (strncmp(sectInfo.segInfo.segName, "__DATA", 6) != 0) && (strncmp(sectInfo.segInfo.segName, "__AUTH", 6) != 0) )
return;
- if ( sectInfo.sectionName != "__objc_opt_ptrs" )
+ if ( strcmp(sectInfo.sectName, "__objc_opt_ptrs") != 0 )
return;
- inputOptPtrsVMAddress = InputDylibVMAddress(sectInfo.address);
- sectionSize = sectInfo.size;
+ inputOptPtrsVMAddress = InputDylibVMAddress(sectInfo.sectAddr);
+ sectionSize = sectInfo.sectSize;
found = true;
stop = true;
@@ -6249,13 +4940,13 @@
// T protocolClass;
// };
// typedef struct objc_opt_pointerlist_tt<uintptr_t> objc_opt_pointerlist_t;
- if ( sectionSize < cacheDylib->inputHdr->pointerSize() ) {
+ if ( sectionSize < cacheDylib->inputMF->pointerSize() ) {
return Error("libobjc's pointer list section is too small (metadata not optimized)");
}
CacheVMAddress cacheOptPtrsVMAddr = cacheDylib->adjustor->adjustVMAddr(inputOptPtrsVMAddress);
- objc_visitor::Visitor objcVisitor = cacheDylib->makeCacheObjCVisitor(config, nullptr, nullptr, nullptr);
+ objc_visitor::Visitor objcVisitor = cacheDylib->makeCacheObjCVisitor(config, nullptr, nullptr);
metadata_visitor::ResolvedValue protocolClassValue = objcVisitor.getValueFor(VMAddress(cacheOptPtrsVMAddr.rawValue()));
protocolClassVMAddr = objcVisitor.resolveRebase(protocolClassValue).vmAddress();
@@ -6311,7 +5002,7 @@
const LayoutBuilder layoutBuilder(cacheDylibs, this->exeInputFiles);
const LayoutBuilder* layoutBuilderPtr = &layoutBuilder;
- STACK_ALLOCATOR(processConfigAlloc, 0);
+ EphemeralAllocator processConfigAlloc;
dyld4::Vector<ProcessConfig::DyldCache::CacheDylib> processConfigDylibsOwner(processConfigAlloc);
auto& processConfigDylibs = processConfigDylibsOwner;
@@ -6343,8 +5034,8 @@
}
// Assume last segment file size is the overall file size
- __block uint32_t fileSize = 0;
- ((const Header*)inputFile->mf)->forEachSegment(^(const Header::SegmentInfo& info, bool& stop) {
+ __block uint64_t fileSize = 0;
+ inputFile->mf->forEachSegment(^(const MachOFile::SegmentInfo& info, bool& stop) {
fileSize = std::max(fileSize, info.fileOffset + info.fileSize);
});
otherMapping[inputFile->path] = { inputFile->mf, fileSize };
@@ -6379,16 +5070,15 @@
KernelArgs kernArgs(mainMF, { "test.exe" }, {}, {});
SyscallDelegate osDelegate;
osDelegate._mappedOtherDylibs = otherMapping;
- osDelegate._gradedArchs = &this->options.gradedArchs;
+ osDelegate._gradedArchs = &this->options.archs;
//osDelegate._dyldCache = dyldCache;
- STACK_ALLOCATOR(alloc, 0);
+ EphemeralAllocator alloc;
ProcessConfig processConfig(&kernArgs, osDelegate, alloc);
- RuntimeLocks locks;
- RuntimeState state(processConfig, locks, alloc);
+ RuntimeState state(processConfig, alloc);
RuntimeState* statePtr = &state;
Diagnostics launchDiag;
- processConfig.reset(mainMF, exeFile->path.c_str(), exeFile->size, nullptr);
+ processConfig.reset(mainMF, exeFile->path.c_str(), nullptr);
state.resetCachedDylibsArrays(cachedDylibsLoaderSet);
// FIXME: This is terrible and needs to be a real reset method
@@ -6396,9 +5086,7 @@
processConfig.dyldCache.dylibsExpectedOnDisk = !this->options.dylibsRemovedFromDisk;
processConfig.dyldCache.development = isDevelopmentSharedCache(this->options);
- // Disable objc optimizations from EK shared cache
- bool emitObjcOpts = !this->options.platform.isExclaveKit();
- if ( !this->objcOptimizer.objcDylibs.empty() && emitObjcOpts ) {
+ if ( !this->objcOptimizer.objcDylibs.empty() ) {
processConfig.dyldCache.objcClassHashTable = (const objc::ClassHashTable*)this->objcClassOptimizer.classHashTableChunk->subCacheBuffer;
processConfig.dyldCache.objcSelectorHashTable = (const objc::SelectorHashTable*)this->objcSelectorOptimizer.selectorHashTableChunk->subCacheBuffer;
processConfig.dyldCache.objcProtocolHashTable = (const objc::ProtocolHashTable*)this->objcProtocolOptimizer.protocolHashTableChunk->subCacheBuffer;
@@ -6409,24 +5097,18 @@
processConfig.dyldCache.unslidLoadAddress = config.layout.cacheBaseAddress.rawValue();
}
- // check if main executable is valid mach-o
- if ( mach_o::Error lerr = state.config.process.mainExecutableHdr->valid(state.config.process.mainExecutableSliceSize) ) {
- fprintf(stderr, "warning: can't build PrebuiltLoaderSet for '%s': %s\n", exeFile->path.c_str(), lerr.message());
- return Error();
- }
-
- Loader::LoadOptions::Finder loaderFinder = ^(Diagnostics& diag, Platform plat, const char* loadPath, const dyld4::Loader::LoadOptions& loadOptions) {
+ Loader::LoadOptions::Finder loaderFinder = ^(Diagnostics& diag, dyld3::Platform plat, const char* loadPath, const dyld4::Loader::LoadOptions& loadOptions) {
// when building macOS cache, there may be some incorrect catalyst paths
- if ( (plat == Platform::macCatalyst) && (strncmp(loadPath, "/System/iOSSupport/", 19) != 0) ) {
+ if ( (plat == dyld3::Platform::iOSMac) && (strncmp(loadPath, "/System/iOSSupport/", 19) != 0) ) {
char altPath[PATH_MAX];
strlcpy(altPath, "/System/iOSSupport", PATH_MAX);
strlcat(altPath, loadPath, PATH_MAX);
- if ( const dyld4::PrebuiltLoader* ldr = cachedDylibsLoaderSet->findLoader(*statePtr, altPath) )
+ if ( const dyld4::PrebuiltLoader* ldr = cachedDylibsLoaderSet->findLoader(altPath) )
return (const Loader*)ldr;
}
// check if path is a dylib in the dyld cache, then use its PrebuiltLoader
- if ( const dyld4::PrebuiltLoader* ldr = cachedDylibsLoaderSet->findLoader(*statePtr, loadPath) )
+ if ( const dyld4::PrebuiltLoader* ldr = cachedDylibsLoaderSet->findLoader(loadPath) )
return (const Loader*)ldr;
// call through to getLoader() which will expand @paths
@@ -6455,23 +5137,6 @@
// FIXME: Propagate errors
return Error();
}
-
- // Set dylibs to be fixedUp before we partition delay init, as it uses this state
- for ( const Loader* ldr : state.loaded ) {
- if ( const PrebuiltLoader* prebuiltLdr = ldr->isPrebuiltLoader() )
- prebuiltLdr->setFixedUp(state);
- }
-
- // split off delay loaded dylibs into delayLoaded vector
- // We have to do this before making the PrebuiltLoaderSet as objc in the closure needs
- // to know which shared cache dylibs are delay or not.
- STACK_ALLOC_ARRAY(const Loader*, loadersTemp, state.loaded.size());
- for (const Loader* ldr : state.loaded)
- loadersTemp.push_back(ldr);
- std::span<const Loader*> allLoaders(&loadersTemp[0], (size_t)loadersTemp.count());
- std::span<const Loader*> topLoaders = allLoaders.subspan(0, 1);
- state.partitionDelayLoads(allLoaders, topLoaders);
-
state.setMainLoader(mainLoader);
const dyld4::PrebuiltLoaderSet* prebuiltAppSet = dyld4::PrebuiltLoaderSet::makeLaunchSet(launchDiag, state, missingPaths);
if ( launchDiag.hasError() ) {
@@ -6497,39 +5162,40 @@
return Error();
});
- const PrebuiltLoaderChunk* loaderChunk = this->prebuiltLoaderBuilder.executablesLoaderChunk;
- const uint64_t loaderBufferSize = loaderChunk->subCacheFileSize.rawValue();
-
assert(!err.hasError());
+
std::map<std::string_view, const dyld4::PrebuiltLoaderSet*> prebuiltsMap;
uint64_t prebuiltsSpace = 0;
- uint64_t requiredPrebuiltsSpace = 0;
for ( uint64_t i = 0; i != this->exeInputFiles.size(); ++i ) {
const InputFile* exeFile = this->exeInputFiles[i];
const dyld4::PrebuiltLoaderSet* loaderSet = executableLoaders[i];
if ( loaderSet == nullptr )
continue;
- uint64_t requiredSpace = alignTo(loaderSet->size(), alignof(dyld4::PrebuiltLoaderSet));
- requiredPrebuiltsSpace += requiredSpace;
- if ( (prebuiltsSpace + requiredSpace) > loaderBufferSize ) {
- // skip this loader if it doesn't fit
- this->warning("cache buffer too small to hold executable PrebuiltLoaderSet '%s' (size=%lldKB)",
- exeFile->path.c_str(), requiredSpace / 1024);
- continue;
- }
-
prebuiltsMap[exeFile->path.c_str()] = loaderSet;
- prebuiltsSpace += requiredSpace;
- }
-
- if ( requiredPrebuiltsSpace != prebuiltsSpace ) {
- this->warning("cache buffer too small to hold executable PrebuiltLoaderSet (prebuiltLoaderSet size=%lluKB, buffer size=%lldKB)",
- requiredPrebuiltsSpace / 1024, loaderBufferSize / 1024);
- }
+ prebuiltsSpace += alignTo(loaderSet->size(), 8);
+ }
+
+ const PrebuiltLoaderChunk* loaderChunk = this->prebuiltLoaderBuilder.executablesLoaderChunk;
+ uint64_t loaderBufferSize = loaderChunk->subCacheFileSize.rawValue();
if ( this->config.log.printStats ) {
stats.add(" dyld4 executable Loader's : used %lld out of %lld bytes of buffer\n", prebuiltsSpace, loaderBufferSize);
+ }
+
+ if ( prebuiltsSpace > loaderBufferSize ) {
+ if ( dylibHasMissingDependency ) {
+ // At least one dylib was evicted. If it was soemthing common, like UIKit/AppKit, then its going to
+ // end up being included in every executable loader and the buffer will overflow
+ this->warning("cache buffer too small to hold executable PrebuiltLoaderSet (prebuiltLoaderSet size=%lluKB, buffer size=%lldKB)",
+ prebuiltsSpace / 1024, loaderBufferSize / 1024);
+
+ // For now, just empty the map. That'll let us emit an empty Trie and PBLS
+ prebuiltsMap.clear();
+ } else {
+ return Error("cache buffer too small to hold executable PrebuiltLoaderSet (prebuiltLoaderSet size=%lluKB, buffer size=%lldKB)",
+ prebuiltsSpace / 1024, loaderBufferSize / 1024);
+ }
}
// copy all PrebuiltLoaderSets into cache
@@ -6567,7 +5233,7 @@
size_t size = pbls->size();
::memcpy(poolBase + currentPoolOffset, pbls, size);
- currentPoolOffset += alignTo(size, alignof(dyld4::PrebuiltLoaderSet));
+ currentPoolOffset += alignTo(size, 8);
}
const CacheTrieChunk* trieChunk = this->prebuiltLoaderBuilder.executableTrieChunk;
@@ -6752,9 +5418,9 @@
Diagnostics diag;
// Find the subCache with the hash tables
- cache_builder::ObjCSelectorHashTableChunk* selectorsHashTable = nullptr;
- cache_builder::ObjCClassHashTableChunk* classesHashTable = nullptr;
- cache_builder::ObjCProtocolHashTableChunk* protocolsHashTable = nullptr;
+ cache_builder::ObjCSelectorHashTableChunk* selectorsHashTable = nullptr;
+ cache_builder::ObjCClassHashTableChunk* classesHashTable = nullptr;
+ cache_builder::ObjCProtocolHashTableChunk* protocolsHashTable = nullptr;
for ( SubCache& subCache : this->subCaches ) {
if ( subCache.objcSelectorsHashTable ) {
assert(selectorsHashTable == nullptr);
@@ -6831,32 +5497,15 @@
Timer::Scope timedScope(this->config, "emitObjCHeaderInfo time");
- // We need the prebuilt loaders from the cache dylibs as they contain SectionLocations.
- auto* cachedDylibsLoaderSet = this->prebuiltLoaderBuilder.cachedDylibsLoaderSet;
- assert(cachedDylibsLoaderSet != nullptr);
-
// Emit header info RO
auto* readOnlyList = (ObjCOptimizer::header_info_ro_list_t*)this->objcOptimizer.headerInfoReadOnlyChunk->subCacheBuffer;
readOnlyList->count = (uint32_t)this->objcOptimizer.objcDylibs.size();
readOnlyList->entsize = this->config.layout.is64 ? sizeof(ObjCOptimizer::header_info_ro_64_t) : sizeof(ObjCOptimizer::header_info_ro_32_t);
- // We're also going to populate the objc image info array with the image infos we have here
- auto* imageInfoArray = (objc::objc_image_info*)this->objcOptimizer.imageInfoChunk->subCacheBuffer;
- CacheVMAddress cacheImageInfoBaseAddress = this->objcOptimizer.imageInfoChunk->cacheVMAddress;
- assert(this->objcOptimizer.imageInfoChunk->subCacheFileSize.rawValue() == (readOnlyList->count * sizeof(objc::objc_image_info)));
-
for ( uint32_t i = 0; i != readOnlyList->count; ++i ) {
CacheDylib& cacheDylib = *this->objcOptimizer.objcDylibs[i];
- const uint64_t dyldCategoriesOptimizedFlag = (1 << 0);
- const uint64_t optimizedByDyldFlag = (1 << 3);
-
- // We want the headerinfo_ro_t to point to the imageInfo in the contiguous buffer
- // not the imageinfo in the original dylib
- // Note: We only have standard (8-byte) image infos in the array right now. We'll ignore
- // the array for any elements which use a different sized image info.
- __block CacheVMAddress cacheImageInfoAddress = cacheImageInfoBaseAddress + VMOffset((uint64_t)i * sizeof(objc::objc_image_info));
-
+ __block CacheVMAddress cacheImageInfoAddress;
__block uint8_t* cacheImageInfoBuffer = nullptr;
cacheDylib.forEachCacheSection(^(std::string_view segmentName, std::string_view sectionName,
uint8_t* sectionBuffer, CacheVMAddress sectionVMAddr,
@@ -6866,11 +5515,7 @@
if ( sectionName != "__objc_imageinfo" )
return;
- if ( sectionVMSize.rawValue() != sizeof(objc::objc_image_info) ) {
- // Skip the optimized array and use this element directly
- cacheImageInfoAddress = sectionVMAddr;
- }
-
+ cacheImageInfoAddress = sectionVMAddr;
cacheImageInfoBuffer = sectionBuffer;
stop = true;
});
@@ -6879,12 +5524,6 @@
void* arrayElement = &readOnlyList->arrayBase[0] + (i * readOnlyList->entsize);
CacheVMAddress machHeaderVMAddr = cacheDylib.cacheLoadAddress;
-
- // Get the PrebuiltLoader* for this cache dylib
- const PrebuiltLoader* ldr = cachedDylibsLoaderSet->atIndex(cacheDylib.cacheIndex);
- //assert(ldr->path(state) == cacheDylib.installName); // can't do assert because state is not passed to this method
-
- CacheVMAddress ldrVMAddr = getVMAddressInSection(*this->prebuiltLoaderBuilder.cacheDylibsLoaderChunk, ldr);
if ( this->config.layout.is64 ) {
ObjCOptimizer::header_info_ro_64_t* element = (ObjCOptimizer::header_info_ro_64_t*)arrayElement;
@@ -6902,13 +5541,6 @@
element->info_offset = infoOffset;
// Check for truncation
assert(element->info_offset == infoOffset);
-
- // metadata_offset
- CacheVMAddress metadataOffsetVMAddr = getVMAddressInSection(*this->objcOptimizer.headerInfoReadOnlyChunk, &element->metadata_offset);
- int64_t metadataOffset = ldrVMAddr.rawValue() - metadataOffsetVMAddr.rawValue();
- element->metadata_offset = metadataOffset;
- // Check for truncation
- assert(element->metadata_offset == metadataOffset);
}
else {
ObjCOptimizer::header_info_ro_32_t* element = (ObjCOptimizer::header_info_ro_32_t*)arrayElement;
@@ -6926,25 +5558,15 @@
element->info_offset = (int32_t)infoOffset;
// Check for truncation
assert(element->info_offset == infoOffset);
-
- // metadata_offset
- CacheVMAddress metadataOffsetVMAddr = getVMAddressInSection(*this->objcOptimizer.headerInfoReadOnlyChunk, &element->metadata_offset);
- int64_t metadataOffset = ldrVMAddr.rawValue() - metadataOffsetVMAddr.rawValue();
- element->metadata_offset = (int32_t)metadataOffset;
- // Check for truncation
- assert(element->metadata_offset == metadataOffset);
}
// Set the dylib to be optimized, which lets it use this header info
- objc::objc_image_info* info = (objc::objc_image_info*)cacheImageInfoBuffer;
- info->flags |= optimizedByDyldFlag;
- if ( this->objcCategoryOptimizer.preAttachedDylibs.contains(i) ) {
- if ( !this->objcCategoryOptimizer.excludedDylibs.contains(i) )
- info->flags |= dyldCategoriesOptimizedFlag;
- }
-
- // Also copy in to the contiguous space
- memcpy(&imageInfoArray[i], info, sizeof(objc::objc_image_info));
+ struct objc_image_info {
+ int32_t version;
+ uint32_t flags;
+ };
+ objc_image_info* info = (objc_image_info*)cacheImageInfoBuffer;
+ info->flags = info->flags | (1 << 3);
}
// Emit header info RW
@@ -7011,25 +5633,26 @@
if ( cacheDylib.installName != "/usr/lib/libobjc.A.dylib" )
continue;
- cacheDylib.cacheHdr->forEachSection(^(const Header::SegmentInfo &segInfo, const Header::SectionInfo §Info, bool &stop) {
- if ( sectInfo.segmentName != "__TEXT" )
+ cacheDylib.cacheMF->forEachSection(^(const dyld3::MachOFile::SectionInfo §Info,
+ bool malformedSectionRange, bool &stop) {
+ if (strcmp(sectInfo.segInfo.segName, "__TEXT") != 0)
return;
- if ( sectInfo.sectionName != "__objc_opt_ro" )
+ if (strcmp(sectInfo.sectName, "__objc_opt_ro") != 0)
return;
// Find the buffer for the section
stop = true;
- const DylibSegmentChunk& segment = cacheDylib.segments[sectInfo.segIndex];
-
- VMAddress sectionVMAddr(sectInfo.address);
- VMAddress segmentVMAddr(segInfo.vmaddr);
+ const DylibSegmentChunk& segment = cacheDylib.segments[sectInfo.segInfo.segIndex];
+
+ VMAddress sectionVMAddr(sectInfo.sectAddr);
+ VMAddress segmentVMAddr(sectInfo.segInfo.vmAddr);
VMOffset sectionOffsetInSegment = sectionVMAddr - segmentVMAddr;
uint8_t* sectionBuffer = segment.subCacheBuffer + sectionOffsetInSegment.rawValue();
// All fields in the old header are offsets from the header. This is how much to
// shift them by
- uint64_t headerCacheOffset = sectInfo.address - this->config.layout.cacheBaseAddress.rawValue();
+ uint64_t headerCacheOffset = sectInfo.sectAddr - this->config.layout.cacheBaseAddress.rawValue();
// Found the section, now write the content
objc_opt::objc_opt_t* libROHeader = (objc_opt::objc_opt_t *)sectionBuffer;
@@ -7075,12 +5698,22 @@
}
}
+// Struct matching dyld4::LibdyldDyld4Section to be used with a variable pointer size.
+// This is so we can use it in the shared cache builder, which is always
+// 64-bit but can emit 32-bit structs
+template <typename P>
+struct FixedSizeLibdyldDyld4Section {
+ P apis;
+ P allImageInfos;
+ P defaultVars[5];
+ P dyldLookupFuncAddr;
+ P tlv_get_addrAddr;
+};
+
+static_assert(sizeof(FixedSizeLibdyldDyld4Section<intptr_t>) == sizeof(dyld4::LibdyldDyld4Section));
+
void SharedCacheBuilder::optimizeTLVs()
{
- // driverkit does not support thread-locals, all other platforms do
- if ( options.platform == Platform::driverKit )
- return;
-
Stats stats(this->config);
Timer::Scope timedScope(this->config, "optimizeTLVs time");
@@ -7107,20 +5740,60 @@
return;
}
- // Find _tlv_get_addr function in libdyld.dylib
- const Image& libdyldImg = *libdyldDylib->inputImage;
-
- Symbol tlvBootstrapSymbol;
- if ( !libdyldImg.hasExportsTrie() || !libdyldImg.exportsTrie().hasExportedSymbol("__tlv_bootstrap", tlvBootstrapSymbol) ) {
- this->warning("Could not find '_tlv_get_addr' in libdyld");
+ // Find the tlv_get_addrAddr from inside the __dyld4 section
+ __block CacheVMAddress getAddrVMAddr;
+ __block bool foundTLVGetAddr = false;
+ libdyldDylib->cacheMF->forEachSection(^(const MachOFile::SectionInfo §Info,
+ bool malformedSectionRange, bool &stop) {
+ if ( strcmp(sectInfo.sectName, "__dyld4") != 0 )
+ return;
+
+ if ( (strncmp(sectInfo.segInfo.segName, "__DATA", 6) != 0)
+ && (strncmp(sectInfo.segInfo.segName, "__AUTH", 6) != 0) )
+ return;
+
+ // Found the section we need. Now to check if its valid
+ stop = true;
+
+ const DylibSegmentChunk& segment = libdyldDylib->segments[sectInfo.segInfo.segIndex];
+
+ VMAddress sectionVMAddr(sectInfo.sectAddr);
+ VMAddress segmentVMAddr(sectInfo.segInfo.vmAddr);
+ VMOffset sectionOffsetInSegment = sectionVMAddr - segmentVMAddr;
+ uint8_t* sectionBuffer = segment.subCacheBuffer + sectionOffsetInSegment.rawValue();
+
+
+ if ( this->config.layout.is64 ) {
+ typedef FixedSizeLibdyldDyld4Section<uint64_t> dyld4_section_t;
+ if ( sectInfo.sectSize < sizeof(dyld4_section_t) ) {
+ // Old libdyld without the field we need
+ return;
+ }
+
+ const dyld4_section_t* dyldSection = (dyld4_section_t*)sectionBuffer;
+ CacheVMAddress cacheBaseAddress = this->config.layout.cacheBaseAddress;
+ getAddrVMAddr = Fixup::Cache64::getCacheVMAddressFromLocation(cacheBaseAddress,
+ &dyldSection->tlv_get_addrAddr);
+ } else {
+ typedef FixedSizeLibdyldDyld4Section<uint32_t> dyld4_section_t;
+ if ( sectInfo.sectSize < sizeof(dyld4_section_t) ) {
+ // Old libdyld without the field we need
+ return;
+ }
+
+ const dyld4_section_t* dyldSection = (dyld4_section_t*)sectionBuffer;
+ CacheVMAddress cacheBaseAddress = this->config.layout.cacheBaseAddress;
+ getAddrVMAddr = Fixup::Cache32::getCacheVMAddressFromLocation(cacheBaseAddress,
+ &dyldSection->tlv_get_addrAddr);
+ }
+
+ foundTLVGetAddr = true;
+ });
+
+ if ( !foundTLVGetAddr ) {
+ this->warning("Could not find tlv_get_addr (TLVs not optimized)");
return;
}
- // NOTE: magic here:
- // To cleanly error of if TLVs are not set up, the on-disk thunks bind to _tlv_bootstrap, which aborts in invoked.
- // The thunks when setup are changed to point to _tlv_get_addr, which is not an exported symbol. Therefore
- // the cache builder has no way to find it. The fix/hack is that _tlv_bootstrap and _tlv_get_addr are written
- // in assembly and always 8 bytes apart.
- CacheVMAddress getAddrVMAddr(libdyldDylib->cacheLoadAddress.rawValue() + tlvBootstrapSymbol.implOffset() + 8);
// We read the value for this symbol to know the first key we can allocate for TLVs
// We then have to stop optimizing if and when we reach "end", that's the
@@ -7172,7 +5845,7 @@
return;
}
- if ( !cacheDylib.cacheHdr->hasThreadLocalVariables() )
+ if ( !cacheDylib.cacheMF->hasThreadLocalVariables() )
continue;
// Get the next available key (one key per dylib)
@@ -7189,117 +5862,69 @@
return;
}
- // find initial content for all thread locals in this dylib
- __block bool initialContentNonZero = false;
- __block uint64_t initialContentAddr = 0;
- __block size_t initialContentSize = 0;
- cacheDylib.cacheHdr->forEachSection(^(const Header::SectionInfo& sectInfo, bool& stop) {
- switch ( sectInfo.flags & SECTION_TYPE ) {
- case S_THREAD_LOCAL_REGULAR:
- initialContentNonZero = true;
- [[clang::fallthrough]];
- case S_THREAD_LOCAL_ZEROFILL:
- if ( initialContentSize == 0 ) {
- // first of N contiguous TLV template sections, record as if this was only section
- initialContentAddr = sectInfo.address;
- initialContentSize = sectInfo.size;
- }
- else {
- // non-first of N contiguous TLV template sections, accumlate values
- initialContentSize = sectInfo.address + sectInfo.size - initialContentAddr;
- }
- break;
- }
- });
-
- cacheDylib.cacheHdr->forEachSection(^(const Header::SegmentInfo &segInfo, const Header::SectionInfo& sectInfo, bool& stop) {
- if ( (sectInfo.flags & SECTION_TYPE) != S_THREAD_LOCAL_VARIABLES )
+ cacheDylib.cacheMF->forEachSection(^(const MachOFile::SectionInfo& sectInfo,
+ bool malformedSectionRange, bool& stop) {
+ if ( (sectInfo.sectFlags & SECTION_TYPE) != S_THREAD_LOCAL_VARIABLES )
return;
- DylibSegmentChunk& segment = cacheDylib.segments[sectInfo.segIndex];
-
- VMAddress sectionVMAddr(sectInfo.address);
- VMAddress segmentVMAddr(segInfo.vmaddr);
+ DylibSegmentChunk& segment = cacheDylib.segments[sectInfo.segInfo.segIndex];
+
+ VMAddress sectionVMAddr(sectInfo.sectAddr);
+ VMAddress segmentVMAddr(sectInfo.segInfo.vmAddr);
VMOffset sectionOffsetInSegment = sectionVMAddr - segmentVMAddr;
uint8_t* sectionBuffer = segment.subCacheBuffer + sectionOffsetInSegment.rawValue();
if ( this->config.layout.is64 ) {
- struct tlv_disk_thunk64_t
+ struct tlv_thunk_t
{
- uint64_t func;
+ uint64_t thunk;
uint64_t key;
uint64_t offset;
};
- uint32_t count = (uint32_t)(sectInfo.size / sizeof(tlv_disk_thunk64_t));
- tlv_disk_thunk64_t* thunkBuffer = (tlv_disk_thunk64_t*)sectionBuffer;
- for ( uint32_t i = 0; i < count; ++i ) {
- size_t offset = thunkBuffer[i].offset; // only value needed from disk format
- dyld::ThreadLocalVariables::TLV_Thunkv2& newThunk = *((dyld::ThreadLocalVariables::TLV_Thunkv2*)&thunkBuffer[i]);
-
- // Reset the fields using TLV_Thunkv2 format
- newThunk.key = key;
- newThunk.offset = (uint32_t)offset;
- newThunk.initialContentSize = (uint32_t)initialContentSize;
- newThunk.initialContentDelta = 0;
- if ( initialContentNonZero ) {
- uint64_t newThunkDeltaFieldAddr = (sectInfo.address + i*sizeof(tlv_disk_thunk64_t) + offsetof(dyld::ThreadLocalVariables::TLV_Thunkv2,initialContentDelta));
- newThunk.initialContentDelta = (int32_t)(initialContentAddr - newThunkDeltaFieldAddr);
- }
+ uint32_t count = (uint32_t)(sectInfo.sectSize / sizeof(tlv_thunk_t));
+ tlv_thunk_t* thunkBuffer = (tlv_thunk_t*)sectionBuffer;
+ for ( uint32_t i = 0; i != count; ++i ) {
+ tlv_thunk_t& tlvThunk = thunkBuffer[i];
+
+ // Set the key to the next available key
+ tlvThunk.key = key;
// Set the thunk to tlv_get_addr()
- uint8_t high8 = 0;
- uint16_t authDiversity = 0;
- bool authHasAddrDiv = false;
- uint8_t authKey = ptrauth_key_asia;
- bool isAuth = this->config.layout.hasAuthRegion;
- Fixup::Cache64::setLocation(this->config.layout.cacheBaseAddress, &newThunk.func,
+ uint8_t high8 = 0;
+ uint16_t authDiversity = 0;
+ bool authHasAddrDiv = false;
+ uint8_t authKey = ptrauth_key_asia;
+ bool isAuth = this->config.layout.hasAuthRegion;
+ Fixup::Cache64::setLocation(this->config.layout.cacheBaseAddress, &tlvThunk.thunk,
getAddrVMAddr, high8,
authDiversity, authHasAddrDiv, authKey, isAuth);
// Add to ASLR tracker
- segment.tracker.add(&newThunk.func);
+ segment.tracker.add(&tlvThunk.thunk);
}
- }
- else {
- struct tlv_disk_thunk32_t
+ } else {
+ struct tlv_thunk_t
{
- uint32_t func;
+ uint32_t thunk;
uint32_t key;
uint32_t offset;
};
- struct tlv_cache_thunk32_t
- {
- uint32_t func;
- uint16_t key;
- uint16_t offset;
- int32_t machHeaderDelta; // if < 0, content is found by walking load commands. If > 0, then it is size and content is all zeros
- };
- static_assert(sizeof(tlv_disk_thunk32_t) == sizeof(tlv_cache_thunk32_t));
- uint32_t loadAddr = (uint32_t)(cacheDylib.cacheHdr->preferredLoadAddress());
- uint32_t count = (uint32_t)(sectInfo.size / sizeof(tlv_disk_thunk32_t));
- tlv_disk_thunk32_t* thunkBuffer = (tlv_disk_thunk32_t*)sectionBuffer;
- for ( uint32_t i = 0; i < count; ++i ) {
- uint32_t offset = thunkBuffer[i].offset; // only value needed from disk format
- tlv_cache_thunk32_t& newThunk = *((tlv_cache_thunk32_t*)&thunkBuffer[i]);
-
- if ( offset > 0xFFFF )
- this->warning("thread-local too large (%u max 65535) in %s", offset, cacheDylib.inputFile->path.c_str());
-
- // Reset the fields using TLV_Thunkv2 format
- newThunk.key = (uint16_t)key;
- newThunk.offset = (uint16_t)offset; // FIXME: error if offset > 0xFFFF
- newThunk.machHeaderDelta = (uint32_t)initialContentSize;
- if ( initialContentNonZero ) {
- uint64_t newThunkDeltaFieldAddr = (sectInfo.address + i*sizeof(tlv_disk_thunk32_t) + offsetof(tlv_cache_thunk32_t,machHeaderDelta));
- newThunk.machHeaderDelta = (int32_t)(loadAddr - newThunkDeltaFieldAddr);
- }
+
+ uint32_t count = (uint32_t)(sectInfo.sectSize / sizeof(tlv_thunk_t));
+ tlv_thunk_t* thunkBuffer = (tlv_thunk_t*)sectionBuffer;
+ for ( uint32_t i = 0; i != count; ++i ) {
+ tlv_thunk_t& tlvThunk = thunkBuffer[i];
+
+ // Set the key to the next available key
+ tlvThunk.key = key;
// Set the thunk to tlv_get_addr()
- Fixup::Cache32::setLocation(this->config.layout.cacheBaseAddress, &newThunk.func, getAddrVMAddr);
+ Fixup::Cache32::setLocation(this->config.layout.cacheBaseAddress, &tlvThunk.thunk,
+ getAddrVMAddr);
// Add to ASLR tracker
- segment.tracker.add(&newThunk.func);
+ segment.tracker.add(&tlvThunk.thunk);
}
}
});
@@ -7339,36 +5964,22 @@
if ( (dataConstRegion == nullptr) && (authConstRegion == nullptr) )
continue;
- for ( UniquedGOTKind sectionKind : { UniquedGOTKind::regular, UniquedGOTKind::authGot, UniquedGOTKind::authPtr } ) {
-
- Region* region = nullptr;
- CoalescedGOTSection* subCacheUniquedGOTs = nullptr;
-
- // Skip sections if their segment doesn't exist
- switch ( sectionKind ) {
- case UniquedGOTKind::regular:
- region = dataConstRegion;
- subCacheUniquedGOTs = &subCache.uniquedGOTsOptimizer.regularGOTs;
- break;
- case UniquedGOTKind::authGot:
- region = authConstRegion;
- subCacheUniquedGOTs = &subCache.uniquedGOTsOptimizer.authGOTs;
- break;
- case UniquedGOTKind::authPtr:
- region = authConstRegion;
- subCacheUniquedGOTs = &subCache.uniquedGOTsOptimizer.authPtrs;
- break;
- }
-
- if ( region == nullptr )
+ for ( bool auth : { false, true } ) {
+ if ( auth && (authConstRegion == nullptr) )
continue;
-
- if ( subCacheUniquedGOTs->empty() )
+ if ( !auth && (dataConstRegion == nullptr) )
continue;
+
+ Region& region = auth ? *authConstRegion : *dataConstRegion;
+ CoalescedGOTSection& subCacheUniquedGOTs = auth ? subCache.uniquedGOTsOptimizer.authGOTs : subCache.uniquedGOTsOptimizer.regularGOTs;
+ if ( subCacheUniquedGOTs.cacheChunk == nullptr )
+ continue;
+
+ UniquedGOTsChunk* subCacheGOTChunk = subCacheUniquedGOTs.cacheChunk->isUniquedGOTsChunk();
std::set<const void*> seenFixups;
std::vector<PatchInfo::GOTInfo> gots;
- for ( const Chunk* chunk : region->chunks ) {
+ for ( const Chunk* chunk : region.chunks ) {
const DylibSegmentChunk* segmentChunk = chunk->isDylibSegmentChunk();
if ( !segmentChunk )
continue;
@@ -7379,55 +5990,46 @@
// Walk all the binds in this dylib, looking for GOT uses of the bind
assert(cacheDylib->bindTargets.size() == dylibPatchInfo.bindGOTUses.size());
assert(cacheDylib->bindTargets.size() == dylibPatchInfo.bindAuthGOTUses.size());
- assert(cacheDylib->bindTargets.size() == dylibPatchInfo.bindAuthPtrUses.size());
for ( uint32_t bindIndex = 0; bindIndex != cacheDylib->bindTargets.size(); ++bindIndex ) {
const CacheDylib::BindTarget& bindTarget = cacheDylib->bindTargets[bindIndex];
- std::span<PatchInfo::GOTInfo> bindUses;
- switch ( sectionKind ) {
- case UniquedGOTKind::regular:
- bindUses = dylibPatchInfo.bindGOTUses[bindIndex];
- break;
- case UniquedGOTKind::authGot:
- bindUses = dylibPatchInfo.bindAuthGOTUses[bindIndex];
- break;
- case UniquedGOTKind::authPtr:
- bindUses = dylibPatchInfo.bindAuthPtrUses[bindIndex];
- break;
+ std::vector<PatchInfo::GOTInfo>* bindUses = nullptr;
+ if ( auth ) {
+ bindUses = &dylibPatchInfo.bindAuthGOTUses[bindIndex];
+ } else {
+ bindUses = &dylibPatchInfo.bindGOTUses[bindIndex];
}
-
- if ( bindUses.empty() )
- continue;
// For absolute binds, just set the pointers and move on
if ( bindTarget.kind == CacheDylib::BindTarget::Kind::absolute ) {
- for ( const PatchInfo::GOTInfo& got : bindUses ) {
- CacheVMAddress gotVMAddr = got.useLocation.clientGOT->cacheVMAddress + got.useLocation.clientGOTOffset;
- const void* fixupLoc = subCacheUniquedGOTs->gotLocation(gotVMAddr);
- uint64_t targetValue = std::get<uint64_t>(got.targetValue);
+ for ( const PatchInfo::GOTInfo& got : *bindUses ) {
+ CacheVMAddress gotVMAddr = got.patchInfo.cacheVMAddr;
+ assert(gotVMAddr >= subCacheGOTChunk->cacheVMAddress);
+ assert(gotVMAddr < (subCacheGOTChunk->cacheVMAddress + subCacheGOTChunk->cacheVMSize));
+ VMOffset cacheSectionVMOffset = gotVMAddr - subCacheGOTChunk->cacheVMAddress;
+
+ const void* fixupLoc = subCacheGOTChunk->subCacheBuffer + cacheSectionVMOffset.rawValue();
if ( this->config.layout.is64 ) {
- *(uint64_t*)fixupLoc = targetValue;
+ *(uint64_t*)fixupLoc = got.targetValue.rawValue();
} else {
- *(uint32_t*)fixupLoc = (uint32_t)targetValue;
+ *(uint32_t*)fixupLoc = (uint32_t)got.targetValue.rawValue();
}
}
continue;
}
- assert(bindTarget.kind == CacheDylib::BindTarget::Kind::cacheImage);
- gots.insert(gots.end(), bindUses.begin(), bindUses.end());
+ gots.insert(gots.end(), bindUses->begin(), bindUses->end());
}
}
// Found all the GOTs/authGOTS for this subCache. Now we need to emit them
for ( const PatchInfo::GOTInfo& got : gots ) {
- CacheVMAddress gotVMAddr = got.useLocation.clientGOT->cacheVMAddress + got.useLocation.clientGOTOffset;
-
- // Function variants don't get emitted right now
- if ( !subCacheUniquedGOTs->shouldEmitGOT(gotVMAddr) )
- continue;
-
- void* rawFixupLoc = subCacheUniquedGOTs->gotLocation(gotVMAddr);
+ CacheVMAddress gotVMAddr = got.patchInfo.cacheVMAddr;
+ assert(gotVMAddr >= subCacheGOTChunk->cacheVMAddress);
+ assert(gotVMAddr < (subCacheGOTChunk->cacheVMAddress + subCacheGOTChunk->cacheVMSize));
+ VMOffset cacheSectionVMOffset = gotVMAddr - subCacheGOTChunk->cacheVMAddress;
+
+ void* rawFixupLoc = subCacheGOTChunk->subCacheBuffer + cacheSectionVMOffset.rawValue();
// Ignore dupes
if ( seenFixups.count(rawFixupLoc) )
@@ -7435,13 +6037,11 @@
seenFixups.insert(rawFixupLoc);
- const DylibOffset& dylibOffset = std::get<DylibOffset>(got.targetValue);
- InputDylibVMAddress inputVMAddr = dylibOffset.cacheDylib->inputLoadAddress + dylibOffset.vmOffset;
- CacheVMAddress targetVMAddr = dylibOffset.cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
+ CacheVMAddress targetVMAddr = config.layout.cacheBaseAddress + got.targetValue;
if ( this->config.layout.is64 ) {
uint64_t high8 = 0;
uint64_t finalVMAddr = targetVMAddr.rawValue();
- if ( !got.useLocation.authenticated ) {
+ if ( !got.patchInfo.authenticated ) {
high8 = (finalVMAddr >> 56);
if ( high8 != 0 ) {
// Remove high8 from the vmAddr
@@ -7452,16 +6052,16 @@
rawFixupLoc,
CacheVMAddress(finalVMAddr),
high8,
- got.useLocation.discriminator,
- got.useLocation.usesAddressDiversity, got.useLocation.key,
- got.useLocation.authenticated);
+ got.patchInfo.discriminator,
+ got.patchInfo.usesAddressDiversity, got.patchInfo.key,
+ got.patchInfo.authenticated);
} else {
Fixup::Cache32::setLocation(this->config.layout.cacheBaseAddress,
rawFixupLoc,
CacheVMAddress(targetVMAddr));
}
- subCacheUniquedGOTs->trackFixup(rawFixupLoc);
+ subCacheGOTChunk->tracker.add(rawFixupLoc);
}
}
}
@@ -7492,8 +6092,7 @@
for ( CacheDylib* cacheDylib : this->objcOptimizer.objcDylibs ) {
objcVisitors.push_back(cacheDylib->makeCacheObjCVisitor(config, nullptr,
- this->objcProtocolOptimizer.canonicalProtocolsChunk,
- this->objcCategoryOptimizer.categoriesChunk));
+ this->objcProtocolOptimizer.canonicalProtocolsChunk));
}
// The offset in the protocol buffer for the next protocol to emit
@@ -7518,7 +6117,7 @@
objc_visitor::Visitor& objcVisitor = objcVisitors[dylibObjCIndex];
if ( log ) {
- printf(" at 0x%llx in %s\n", protocolVMAddr, objcVisitor.hdr()->installName());
+ printf(" at 0x%llx in %s\n", protocolVMAddr, objcVisitor.mf()->installName());
}
__block bool foundProtocol = false;
@@ -7659,7 +6258,7 @@
objcVisitors.reserve(this->cacheDylibs.size());
for ( CacheDylib& cacheDylib : this->cacheDylibs ) {
- objcVisitors.push_back(cacheDylib.makeCacheObjCVisitor(config, nullptr, nullptr, nullptr));
+ objcVisitors.push_back(cacheDylib.makeCacheObjCVisitor(config, nullptr, nullptr));
}
// Check for missing superclasses, but only error on customer/universal caches
@@ -7676,7 +6275,7 @@
error = Error("Superclass of class '%s' is weak-import"
"and missing. Referenced in %s",
objcClass.getName(objcVisitor),
- objcVisitor.hdr()->installName());
+ objcVisitor.mf()->installName());
stopClass = true;
this->objcOptimizer.foundMissingWeakSuperclass = true;
}
@@ -7709,7 +6308,7 @@
classMap[classVMAddr] = (uint32_t)classInfos.size();
if ( log ) {
- printf("%s: [0x%08llx] %s%s\n", objcVisitor.hdr()->installName(), classVMAddr.rawValue(),
+ printf("%s: [0x%08llx] %s%s\n", objcVisitor.mf()->installName(), classVMAddr.rawValue(),
objcClass.getName(objcVisitor), objcClass.isMetaClass ? " (meta)" : "");
}
@@ -7764,7 +6363,7 @@
worklist.insert(worklist.end(), classInfo->subClasses.begin(), classInfo->subClasses.end());
bool elidedSomething = false;
const objc_visitor::Class& objcClass = classInfo->classPos;
- const bool isSwiftClass = objcClass.isSwift(*classInfo->objcVisitor);
+
auto& map = objcClass.isMetaClass ? metaclassMap : classMap;
std::optional<VMAddress> superclassVMAddr = objcClass.getSuperclassVMAddr(*classInfo->objcVisitor);
@@ -7805,7 +6404,7 @@
continue;
// skip ivars that swiftc has optimized away
- if ( isSwiftClass && ivar.elided(*classInfo->objcVisitor) ) {
+ if ( ivar.elided(*classInfo->objcVisitor) ) {
if ( log ) {
if ( !elidedSomething )
printf("adjusting ivars for %s\n", objcClass.getName(*classInfo->objcVisitor));
@@ -7823,396 +6422,6 @@
objcClass.setInstanceStart(*classInfo->objcVisitor, objcClass.getInstanceStart(*classInfo->objcVisitor) + diff);
objcClass.setInstanceSize(*classInfo->objcVisitor, objcClass.getInstanceSize(*classInfo->objcVisitor) + diff);
}
- }
-
- return Error();
-}
-
-Error SharedCacheBuilder::emitPreAttachedObjCCategories()
-{
-
- if ( this->objcOptimizer.objcDylibs.empty() )
- return Error();
-
- Timer::Scope timedScope(this->config, "dylib emitPreAttachedObjCCategories time");
-
- ObjCPreAttachedCategoriesChunk* categoriesChunk = this->objcCategoryOptimizer.categoriesChunk;
-
- // Build reverse map of categories for fast lookup during optimization
- __block std::unordered_multimap<size_t, uint64_t> dylibsToClasses;
- __block std::unordered_multimap<uint64_t, const ObjCCategoryOptimizer::Category*> classMap;
- __block std::unordered_multimap<uint64_t, const ObjCCategoryOptimizer::Category*> metaClassMap;
- auto& categories = this->objcCategoryOptimizer.categories;
- for (size_t i = 0; i < categories.size(); i++) {
- dylibsToClasses.insert({ categories[i].classDylibIndex.value(), categories[i].classVMAddress.value().rawValue() });
- classMap.insert({ categories[i].classVMAddress.value().rawValue(), &categories[i] });
- }
-
- __block dyld3::MachOFile::PointerMetaData methodListPMD;
- if ( this->config.layout.hasAuthRegion ) {
- methodListPMD.diversity = 0xC310;
- methodListPMD.high8 = 0;
- methodListPMD.authenticated = 1;
- methodListPMD.key = ptrauth_key_asda;
- methodListPMD.usesAddrDiversity = 1;
- }
-
- __block uint64_t chunkOffset = 0;
-
- // The very first thing we want to do is make an empty class method list
- // All classes which have no method lists will have this added as the last entry
- // in their list
- assert(categoriesChunk->subCacheFileSize.rawValue() >= 8);
- void* emptyMethodListLocation = categoriesChunk->subCacheBuffer;
- CacheVMAddress emptyMethodListVMAddr = categoriesChunk->cacheVMAddress;
- chunkOffset += objc_visitor::MethodList::makeEmptyMethodList(emptyMethodListLocation);
-
- auto visitCategoryMetaClass = ^(objc_visitor::Visitor &objCVisitor,
- size_t classCacheIndex, size_t classObjcIndex,
- objc_visitor::Class &objcClass,
- std::span<DylibSegmentChunk> cacheDylibSegments) {
- auto metaClassRange = metaClassMap.equal_range(objcClass.getVMAddress().rawValue());
- if ( metaClassRange.first == metaClassRange.second )
- return;
-
- //fprintf(stderr, "meta class %s %s 0x%llx\n", this->cacheDylibs[classCacheIndex].installName.data(), objcClass.getName(objCVisitor), objcClass.getVMAddress().rawValue());
- // class method lists
- {
- uint8_t* listBuffer = categoriesChunk->subCacheBuffer + chunkOffset;
- uint64_t firstEntryOffset = chunkOffset;
- ListOfListsEntry* listHeader = (ListOfListsEntry*)listBuffer;
- ListOfListsEntry* listEntry = listHeader + 1;
-
- bool createdNewList = false;
- for (auto classEntry = metaClassRange.first; classEntry != metaClassRange.second; classEntry++) {
- const ObjCCategoryOptimizer::Category* category = classEntry->second;
- if ( !category->cMethodListVMAddress.has_value() )
- continue;
-
- //fprintf(stderr, " meta cat %s 0x%llx %s (%llu) => %zu\n", category->name.data(), category->vmAddress.value().rawValue(), this->objcOptimizer.objcDylibs[category->dylibObjcIndex.value()]->installName.data(), category->dylibObjcIndex.value(), classObjcIndex);
- if ( !createdNewList ) {
- createdNewList = true;
- // New list with count and entry size
- listHeader->entsize = sizeof(ListOfListsEntry);
- listHeader->count = 0;
- chunkOffset += sizeof(ListOfListsEntry);
- }
-
- listEntry->imageIndex = category->dylibObjcIndex.value();
- int64_t destVMAddr = (int64_t)category->cMethodListVMAddress.value().rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- listEntry++;
- listHeader->count++;
- chunkOffset += sizeof(ListOfListsEntry);
- }
-
- if ( createdNewList ) {
- objc_visitor::MethodList methodList = objcClass.getBaseMethods(objCVisitor);
- // add original method list
- if ( methodList.numMethods() > 0 ) {
- listEntry->imageIndex = classObjcIndex;
- int64_t destVMAddr = (int64_t)methodList.getVMAddress().value().rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- } else {
- // add an empty list entry if no original was found
- listEntry->imageIndex = classObjcIndex;
- int64_t destVMAddr = (int64_t)emptyMethodListVMAddr.rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- }
- listEntry++;
- listHeader->count++;
- chunkOffset += sizeof(ListOfListsEntry);
- //fprintf(stderr, "Total cMethod list lists: %d\n", listHeader->count);
- // store new base methods pointer to update
- CacheVMAddress newBaseMethods = categoriesChunk->cacheVMAddress + VMOffset(firstEntryOffset);
- metadata_visitor::ResolvedValue field = objcClass.setBaseMethodsVMAddr(objCVisitor,
- VMAddress(newBaseMethods.rawValue() | 1),
- methodListPMD);
- cacheDylibSegments[field.segmentIndex()].tracker.add(field.value());
- }
- }
- // class property lists
- {
- uint8_t* listBuffer = categoriesChunk->subCacheBuffer + chunkOffset;
- uint64_t firstEntryOffset = chunkOffset;
- ListOfListsEntry* listHeader = (ListOfListsEntry*)listBuffer;
- ListOfListsEntry* listEntry = listHeader + 1;
-
- bool createdNewList = false;
- for (auto classEntry = metaClassRange.first; classEntry != metaClassRange.second; classEntry++) {
- const ObjCCategoryOptimizer::Category* category = classEntry->second;
- if ( !category->cPropertyListVMAddress.has_value() )
- continue;
-
- //fprintf(stderr, " meta cat %s 0x%llx %s (%llu) => %zu\n", category->name.data(), category->vmAddress.value().rawValue(), this->objcOptimizer.objcDylibs[category->dylibObjcIndex.value()]->installName.data(), category->dylibObjcIndex.value(), classObjcIndex);
- if ( !createdNewList ) {
- createdNewList = true;
- // New list with count and entry size
- listHeader->entsize = sizeof(ListOfListsEntry);
- listHeader->count = 0;
- chunkOffset += sizeof(ListOfListsEntry);
- }
-
- listEntry->imageIndex = category->dylibObjcIndex.value();
- int64_t destVMAddr = (int64_t)category->cPropertyListVMAddress.value().rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- listEntry++;
- listHeader->count++;
- chunkOffset += sizeof(ListOfListsEntry);
- }
-
- if ( createdNewList ) {
- objc_visitor::PropertyList propertyList = objcClass.getBaseProperties(objCVisitor);
- // add original method list
- if ( propertyList.numProperties() > 0 ) {
- listEntry->imageIndex = classObjcIndex;
- int64_t destVMAddr = (int64_t)propertyList.getVMAddress().value().rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- } else {
- // add an empty list entry if no original was found
- // Zeroing the entry will make the offset point to itself
- // That will then be interpreted as a ListOfListsEntry of count 0
- // This also means that the image at index 0 needs to always be libobjc.A.dylib
- listEntry->imageIndex = 0;
- listEntry->offset = 0;
- }
- listEntry++;
- listHeader->count++;
- chunkOffset += sizeof(ListOfListsEntry);
-
- //fprintf(stderr, "Total cProperty list lists: %d\n", listHeader->count);
- // store new base properties pointer to update
- CacheVMAddress newBaseProperties = categoriesChunk->cacheVMAddress + VMOffset(firstEntryOffset);
- metadata_visitor::ResolvedValue field = objcClass.setBasePropertiesVMAddr(objCVisitor,
- VMAddress(newBaseProperties.rawValue() | 1));
- cacheDylibSegments[field.segmentIndex()].tracker.add(field.value());
- }
- }
- return;
- };
- auto visitCategoryClass = ^(objc_visitor::Visitor &objCVisitor,
- size_t classCacheIndex, size_t classObjcIndex,
- objc_visitor::Class &objcClass,
- std::span<DylibSegmentChunk> cacheDylibSegments) {
- auto classRange = classMap.equal_range(objcClass.getVMAddress().rawValue());
- if ( classRange.first == classRange.second )
- return;
-
- //fprintf(stderr, "class %s %s 0x%llx\n", this->cacheDylibs[classCacheIndex].installName.data(), objcClass.getName(objCVisitor), objcClass.getVMAddress().rawValue());
- // instance method lists
- {
- uint8_t* listBuffer = categoriesChunk->subCacheBuffer + chunkOffset;
- uint64_t firstEntryOffset = chunkOffset;
- ListOfListsEntry* listHeader = (ListOfListsEntry*)listBuffer;
- ListOfListsEntry* listEntry = listHeader + 1;
-
- bool createdNewList = false;
- for (auto classEntry = classRange.first; classEntry != classRange.second; classEntry++) {
- const ObjCCategoryOptimizer::Category* category = classEntry->second;
- if ( !category->iMethodListVMAddress.has_value() )
- continue;
-
- //fprintf(stderr, " cat %s 0x%llx %s (%llu) => %zu\n", category->name.data(), category->vmAddress.value().rawValue(), this->objcOptimizer.objcDylibs[category->dylibObjcIndex.value()]->installName.data(), category->dylibObjcIndex.value(), classObjcIndex);
- if ( !createdNewList ) {
- createdNewList = true;
- // New list with count and entry size
- listHeader->entsize = sizeof(ListOfListsEntry);
- listHeader->count = 0;
- chunkOffset += sizeof(ListOfListsEntry);
- }
-
- listEntry->imageIndex = category->dylibObjcIndex.value();
- int64_t destVMAddr = (int64_t)category->iMethodListVMAddress.value().rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- listEntry++;
- listHeader->count++;
- chunkOffset += sizeof(ListOfListsEntry);
- }
-
- if ( createdNewList ) {
- objc_visitor::MethodList methodList = objcClass.getBaseMethods(objCVisitor);
- // add original method list
- if ( methodList.numMethods() > 0 ) {
- listEntry->imageIndex = classObjcIndex;
- int64_t destVMAddr = (int64_t)methodList.getVMAddress().value().rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- } else {
- // add an empty list entry if no original was found
- listEntry->imageIndex = classObjcIndex;
- int64_t destVMAddr = (int64_t)emptyMethodListVMAddr.rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- }
- listEntry++;
- listHeader->count++;
- chunkOffset += sizeof(ListOfListsEntry);
- //fprintf(stderr, "Total iMethod list lists: %d\n", listHeader->count);
- // store new base methods pointer to update
- CacheVMAddress newBaseMethods = categoriesChunk->cacheVMAddress + VMOffset(firstEntryOffset);
- metadata_visitor::ResolvedValue field = objcClass.setBaseMethodsVMAddr(objCVisitor,
- VMAddress(newBaseMethods.rawValue() | 1),
- methodListPMD);
- cacheDylibSegments[field.segmentIndex()].tracker.add(field.value());
- }
- }
-
- // protocol lists
- {
- uint8_t* listBuffer = categoriesChunk->subCacheBuffer + chunkOffset;
- uint64_t firstEntryOffset = chunkOffset;
- ListOfListsEntry* listHeader = (ListOfListsEntry*)listBuffer;
- ListOfListsEntry* listEntry = listHeader + 1;
-
- bool createdNewList = false;
- for (auto classEntry = classRange.first; classEntry != classRange.second; classEntry++) {
- const ObjCCategoryOptimizer::Category* category = classEntry->second;
- if ( !category->protocolListVMAddress.has_value() )
- continue;
-
- //fprintf(stderr, " cat %s 0x%llx %s (%llu) => %zu\n", category->name.data(), category->vmAddress.value().rawValue(), this->objcOptimizer.objcDylibs[category->dylibObjcIndex.value()]->installName.data(), category->dylibObjcIndex.value(), classObjcIndex);
- if ( !createdNewList ) {
- createdNewList = true;
- // New list with count and entry size
- listHeader->entsize = sizeof(ListOfListsEntry);
- listHeader->count = 0;
- chunkOffset += sizeof(ListOfListsEntry);
- }
-
- listEntry->imageIndex = category->dylibObjcIndex.value();
- int64_t destVMAddr = (int64_t)category->protocolListVMAddress.value().rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- listEntry++;
- listHeader->count++;
- chunkOffset += sizeof(ListOfListsEntry);
- }
-
- if ( createdNewList ) {
- objc_visitor::ProtocolList protocolList = objcClass.getBaseProtocols(objCVisitor);
- // add original protocol list
- if ( protocolList.numProtocols(objCVisitor) > 0 ) {
- listEntry->imageIndex = classObjcIndex;
- int64_t destVMAddr = (int64_t)protocolList.getVMAddress().value().rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- } else {
- // add an empty list entry if no original was found
- // Zeroing the entry will make the offset point to itself
- // That will then be interpreted as a ListOfListsEntry of count 0
- // This also means that the image at index 0 needs to always be libobjc.A.dylib
- listEntry->imageIndex = 0;
- listEntry->offset = 0;
- }
- listEntry++;
- listHeader->count++;
- chunkOffset += sizeof(ListOfListsEntry);
-
- //fprintf(stderr, "Total Protocol list lists: %d\n", listHeader->count);
- // store new base protocols pointer to update
- CacheVMAddress newBaseProtocols = categoriesChunk->cacheVMAddress + VMOffset(firstEntryOffset);
- metadata_visitor::ResolvedValue field = objcClass.setBaseProtocolsVMAddr(objCVisitor,
- VMAddress(newBaseProtocols.rawValue() | 1));
- cacheDylibSegments[field.segmentIndex()].tracker.add(field.value());
- }
- }
-
- // instance properties
- {
- uint8_t* listBuffer = categoriesChunk->subCacheBuffer + chunkOffset;
- uint64_t firstEntryOffset = chunkOffset;
- ListOfListsEntry* listHeader = (ListOfListsEntry*)listBuffer;
- ListOfListsEntry* listEntry = listHeader + 1;
-
- bool createdNewList = false;
- for (auto classEntry = classRange.first; classEntry != classRange.second; classEntry++) {
- const ObjCCategoryOptimizer::Category* category = classEntry->second;
- if ( !category->iPropertyListVMAddress.has_value() )
- continue;
-
- //fprintf(stderr, " cat %s 0x%llx %s (%llu) => %zu\n", category->name.data(), category->vmAddress.value().rawValue(), this->objcOptimizer.objcDylibs[category->dylibObjcIndex.value()]->installName.data(), category->dylibObjcIndex.value(), classObjcIndex);
- if ( !createdNewList ) {
- createdNewList = true;
- // New list with count and entry size
- listHeader->entsize = sizeof(ListOfListsEntry);
- listHeader->count = 0;
- chunkOffset += sizeof(ListOfListsEntry);
- }
-
- listEntry->imageIndex = category->dylibObjcIndex.value();
- int64_t destVMAddr = (int64_t)category->iPropertyListVMAddress.value().rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- listEntry++;
- listHeader->count++;
- chunkOffset += sizeof(ListOfListsEntry);
- }
-
- if ( createdNewList ) {
- objc_visitor::PropertyList propertyList = objcClass.getBaseProperties(objCVisitor);
- // add original property list
- if ( propertyList.numProperties() > 0 ) {
- listEntry->imageIndex = classObjcIndex;
- int64_t destVMAddr = (int64_t)propertyList.getVMAddress().value().rawValue();
- listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
- } else {
- // add an empty list entry if no original was found
- // Zeroing the entry will make the offset point to itself
- // That will then be interpreted as a ListOfListsEntry of count 0
- // This also means that the image at index 0 needs to always be libobjc.A.dylib
- listEntry->imageIndex = 0;
- listEntry->offset = 0;
- }
- listEntry++;
- listHeader->count++;
- chunkOffset += sizeof(ListOfListsEntry);
- //fprintf(stderr, "Total iProperty list lists: %d\n", listHeader->count);
- // store new base properties pointer to update
- CacheVMAddress newBaseProperties = categoriesChunk->cacheVMAddress + VMOffset(firstEntryOffset);
- metadata_visitor::ResolvedValue field = objcClass.setBasePropertiesVMAddr(objCVisitor,
- VMAddress(newBaseProperties.rawValue() | 1));
- cacheDylibSegments[field.segmentIndex()].tracker.add(field.value());
- }
- }
-
- // class method lists and class properties
- {
- // check if we need to update the meta class
- bool isPatchableClass;
- VMAddress metaClassVMAddr = objcClass.getISA(objCVisitor, isPatchableClass).vmAddress();
-
- bool visitMetaClassMap = false;
- for (auto classEntry = classRange.first; classEntry != classRange.second; classEntry++) {
- const ObjCCategoryOptimizer::Category* category = classEntry->second;
- if ( category->cPropertyListVMAddress.has_value() || category->cMethodListVMAddress.has_value() ) {
- metaClassMap.insert({ metaClassVMAddr.rawValue(), category });
- visitMetaClassMap = true;
- }
- }
- if ( visitMetaClassMap ) {
- metadata_visitor::ResolvedValue metaClassValue = objCVisitor.getValueFor(metaClassVMAddr);
- objc_visitor::Class objcMetaClass(metaClassValue, /*isMetaClass*/ true, /*isPatchable*/ false);
- visitCategoryMetaClass(objCVisitor, classCacheIndex, classObjcIndex, objcMetaClass, cacheDylibSegments);
- }
- }
- };
-
- size_t objcIndex = 0;
- for (size_t cacheIndex = 0; cacheIndex < this->cacheDylibs.size(); cacheIndex++) {
- CacheDylib& cacheDylib = this->cacheDylibs[cacheIndex];
- if ( !cacheDylib.inputHdr->hasObjC() )
- continue;
-
- this->objcCategoryOptimizer.preAttachedDylibs.insert(objcIndex);
- __block objc_visitor::Visitor objCVisitor = cacheDylib.makeCacheObjCVisitor(config, nullptr, nullptr, categoriesChunk);
-
- // dylibsToClasses can contain multiple entries with the same pair of key/values
- std::set<uint64_t> visitedClasses;
- auto dylibToClassRange = dylibsToClasses.equal_range(cacheIndex);
- for (auto dylibToClass = dylibToClassRange.first; dylibToClass != dylibToClassRange.second; dylibToClass++) {
- if ( visitedClasses.contains(dylibToClass->second) )
- continue;
- visitedClasses.insert(dylibToClass->second);
- metadata_visitor::ResolvedValue classValue = objCVisitor.getValueFor(VMAddress(dylibToClass->second));
- objc_visitor::Class objcClass(classValue, /*isMetaClass*/ false, /*isPatchable*/ false);
- visitCategoryClass(objCVisitor, cacheIndex, objcIndex, objcClass, cacheDylib.segments);
- }
- objcIndex++;
}
return Error();
@@ -8247,71 +6456,19 @@
Diagnostics diag;
auto objcClassOpt = (objc::ClassHashTable*)this->objcClassOptimizer.classHashTableChunk->subCacheBuffer;
- buildSwiftHashTables(this->config, diag, this->objcOptimizer.objcDylibs,
+ buildSwiftHashTables(this->config, diag, this->cacheDylibs,
extraRegions, objcClassOpt,
this->objcOptimizer.headerInfoReadOnlyChunk->subCacheBuffer,
this->objcOptimizer.headerInfoReadWriteChunk->subCacheBuffer,
this->objcOptimizer.headerInfoReadOnlyChunk->cacheVMAddress,
- swiftPrespecializedDylib,
- this->swiftOptimizer);
+ this->swiftProtocolConformanceOptimizer);
+
if ( diag.hasError() )
return Error("Couldn't build Swift protocol opts because: %s", diag.errorMessageCStr());
return Error();
}
-static void getFunctionVariantTable(const CacheDylib& cacheDylib, uint64_t& vmAddr, uint64_t& vmSize)
-{
- vmAddr = 0;
- vmSize = 0;
- for ( const LinkeditDataChunk& chunk : cacheDylib.linkeditChunks ) {
- if ( chunk.isFunctionVariantsTable() ) {
- vmAddr = chunk.cacheVMAddress.rawValue();
- vmSize = chunk.cacheVMSize.rawValue();
- }
- }
-}
-
-void SharedCacheBuilder::emitFunctionVariants()
-{
- // find uniqued GOT slots in each subcache that point to function variants
- for ( SubCache& subCache : this->subCaches ) {
- subCache.uniquedGOTsOptimizer.forEachFunctionVariant(^(const CoalescedGOTSection::FunctionVariantInfo& fv, uint64_t gotVMAddr,
- dyld3::MachOFile::PointerMetaData pmd) {
- uint32_t targetDylibIndex = fv.dylibIndex;
- uint64_t fvTableVmAddr;
- uint64_t fvTableVmSize;
- getFunctionVariantTable(this->cacheDylibs[targetDylibIndex], fvTableVmAddr, fvTableVmSize);
- dyld_cache_function_variant_entry entry;
- entry.fixupLocVmAddr = gotVMAddr;
- entry.functionVariantTableVmAddr = fvTableVmAddr;
- entry.functionVariantTableSizeDiv4 = fvTableVmSize/4;
- entry.dylibHeaderVmAddr = this->cacheDylibs[targetDylibIndex].cacheLoadAddress.rawValue();
- entry.variantIndex = fv.variantIndex;
- entry.pacAuth = pmd.authenticated;
- entry.pacAddress = pmd.usesAddrDiversity;
- entry.pacKey = pmd.key;
- entry.pacDiversity = pmd.diversity;
- entry.targetDylibIndex = targetDylibIndex;
- assert(entry.variantIndex == fv.variantIndex);
- this->functionVariantsOptimizer.infos.push_back(entry);
- });
- }
-
- // make sure it fits in estimated size
- size_t actualSize = offsetof(dyld_cache_function_variant_info, entries[this->functionVariantsOptimizer.infos.size()]);
- assert(actualSize < this->functionVariantsOptimizer.fvInfoTotalByteSize);
- this->functionVariantsOptimizer.fvInfoTotalByteSize = actualSize;
-
- // write table to LINKEDIT
- dyld_cache_function_variant_info* table = (dyld_cache_function_variant_info*)(this->functionVariantsOptimizer.chunk->subCacheBuffer);
- table->version = 1;
- table->count = (uint32_t)this->functionVariantsOptimizer.infos.size();
- if ( table->count > 0 )
- memcpy(table->entries, &this->functionVariantsOptimizer.infos[0], sizeof(dyld_cache_function_variant_entry)*this->functionVariantsOptimizer.infos.size());
-}
-
-
void SharedCacheBuilder::emitCacheDylibsTrie()
{
Timer::Scope timedScope(this->config, "emitCacheDylibsTrie time");
@@ -8326,7 +6483,7 @@
Timer::Scope timedScope(this->config, "computeSlideInfo time");
if ( !this->config.slideInfo.slideInfoFormat.has_value() ) {
- assert(this->options.isSimulator());
+ assert(this->options.isSimultor());
}
Error err = parallel::forEach(this->subCaches, ^(size_t index, SubCache& subCache) {
@@ -8362,14 +6519,11 @@
switch ( region.kind ) {
case Region::Kind::text:
case Region::Kind::dynamicConfig:
- case Region::Kind::readOnly:
case Region::Kind::linkedit:
maxSlide = std::min(maxSlide, subCacheLimit - region.subCacheVMSize);
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:
if ( firstDataRegion == nullptr )
@@ -8394,11 +6548,6 @@
// We must be a largeContiguous cache. Others were dealt with above in the x86_64 and/or sim cases
assert(this->config.layout.contiguous.has_value());
- // Some caches have a fixed max slide
- if ( this->config.layout.cacheFixedSlide.has_value() ) {
- return this->config.layout.cacheFixedSlide.value();
- }
-
// Start off making sure we can't slide past the end of the cache
CacheVMAddress maxVMAddress(0ULL);
for ( const Region& region : this->subCaches.back().regions ) {
@@ -8429,10 +6578,6 @@
if ( sizeUpToTextEnd <= twoGB )
maxSlide = CacheVMSize(twoGB - sizeUpToTextEnd);
- if ( this->config.layout.cacheMaxSlide.has_value() ) {
- maxSlide = std::min(maxSlide, CacheVMSize(this->config.layout.cacheMaxSlide.value()));
- }
-
return maxSlide.rawValue();
}
@@ -8446,231 +6591,36 @@
Diagnostics diag;
cacheDylib.addObjcSegments(diag, aggregateTimer,
this->objcOptimizer.headerInfoReadOnlyChunk,
- this->objcOptimizer.imageInfoChunk,
this->objcProtocolOptimizer.protocolHashTableChunk,
- this->objcCategoryOptimizer.categoriesChunk,
this->objcOptimizer.headerInfoReadWriteChunk,
this->objcProtocolOptimizer.canonicalProtocolsChunk);
}
}
-Error SharedCacheBuilder::patchLinkedDylibs(CacheDylib& cacheDylib)
-{
- if ( swiftPrespecializedDylib == nullptr )
- return Error::none();
-
- Diagnostics diag;
- if ( &cacheDylib == swiftPrespecializedDylib ) {
- // remove all but libSystem
- cacheDylib.removeLinkedDylibs(diag);
- } else if ( cacheDylib.installName.find("libswiftCore.dylib") != std::string_view::npos ) {
- // add Swift prespecialized dylib dependency to libswiftCore
- cacheDylib.addLinkedDylib(diag, *swiftPrespecializedDylib);
- }
-
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
-
- return Error::none();
-}
-
-void SharedCacheBuilder::addSubCacheFileInfo(uint64_t cacheVMAddress, PropertyList::Array &files, const SubCache &subCache) {
- using Array = PropertyList::Array;
- using Integer = PropertyList::Integer;
- using String = PropertyList::String;
- using Dictionary = PropertyList::Dictionary;
-
- uuid_t subcacheUUID;
- uuid_parse(subCache.uuidString, subcacheUUID);
- auto& subCacheFile = files.addObject<Dictionary>();
-
- subCacheFile.addObjectForKey<String>("name", options.mainCacheFileName + subCache.fileSuffix);
- subCacheFile.addObjectForKey<PropertyList::UUID>(kDyldAtlasSharedCacheUUIDKey,subcacheUUID);
- subCacheFile.addObjectForKey<Integer>("voff",subCache.subCacheVMAddress.rawValue()-cacheVMAddress);
- subCacheFile.addObjectForKey<Integer>("fsze", subCache.bufferSize);
- subCacheFile.addObjectForKey<Integer>("padr", subCache.subCacheVMAddress.rawValue());
- auto& mappingsArray = subCacheFile.addObjectForKey<Array>(kDyldAtlasSharedCacheMappingArrayKey);
-
- dyld_cache_header* subCacheHeader = (dyld_cache_header*)subCache.buffer;
- auto* mappings = (dyld_cache_mapping_info*)((uint8_t*)subCacheHeader + subCacheHeader->mappingOffset);
-
- uint64_t lastAddress = 0;
- for ( auto i = 0; i < subCacheHeader->mappingCount; ++i) {
- auto& mapping = mappingsArray.addObject<Dictionary>();
- mapping.addObjectForKey<Integer>(kDyldAtlasSharedCacheMappingsSizeKey, mappings[i].size);
- mapping.addObjectForKey<Integer>(kDyldAtlasSharedCacheMappingsPreferredLoadAddressKey, mappings[i].address);
- mapping.addObjectForKey<Integer>(kDyldAtlasSharedCacheMappingsFileOffsetKey, mappings[i].fileOffset);
- mapping.addObjectForKey<Integer>(kDyldAtlasSharedCacheMappingsMaxProtKey, mappings[i].maxProt);
- if (mappings[i].address + mappings[i].size > lastAddress) {
- lastAddress = mappings[i].address + mappings[i].size;
- }
- }
- subCacheFile.addObjectForKey<Integer>("size", lastAddress-subCacheHeader->sharedRegionStart);
-}
-
-void SharedCacheBuilder::addCacheAtlasInfo(PropertyList::Dictionary *cacheAtlas, const SubCache &mainCache) {
- using Array = PropertyList::Array;
- using Integer = PropertyList::Integer;
- using String = PropertyList::String;
-
- uint64_t cacheVMAddress = mainCache.subCacheVMAddress.rawValue();
- uuid_t cacheUUID;
- uuid_parse(mainCache.uuidString, cacheUUID);
- cacheAtlas->addObjectForKey<PropertyList::UUID>(kDyldAtlasSharedCacheUUIDKey, cacheUUID);
- cacheAtlas->addObjectForKey<Integer>(kDyldAtlasSharedCachePreferredLoadAddressKey, mainCache.subCacheVMAddress.rawValue());
- cacheAtlas->addObjectForKey<Integer>(kDyldAtlasSharedCacheVMSizeKey, this->totalVMSize.rawValue());
- dyld_cache_header* cacheHeader = (dyld_cache_header*)mainCache.buffer;
-
- if (!uuid_is_null(cacheHeader->symbolFileUUID)) {
- cacheAtlas->addObjectForKey<String>(kDyldAtlasSharedCacheSymbolFileName, options.mainCacheFileName + ".symbols");
- cacheAtlas->addObjectForKey<PropertyList::UUID>(kDyldAtlasSharedCacheSymbolFileName, cacheHeader->symbolFileUUID);
- }
-
- auto& files = cacheAtlas->addObjectForKey<Array>("dscs");
- addSubCacheFileInfo(cacheVMAddress, files, mainCache);
- for (auto& subCache : mainCache.subCaches) {
- addSubCacheFileInfo(cacheVMAddress, files, *subCache);
- }
-
- // FIXME: Remove once deprecate legacy APIs that need this
- if (strncmp(cacheHeader->magic, "dyld_v1arm64_32", 15) != 0) {
- cacheAtlas->addObjectForKey<Integer>("psze", 4);
- } else {
- cacheAtlas->addObjectForKey<Integer>("psze", 8);
- }
-}
-
-// This generates the atlas binary plist included with the shared cache
-void SharedCacheBuilder::buildAtlas() {
- STACK_ALLOCATOR(allocator, 0);
- using Array = PropertyList::Array;
- using Dictionary = PropertyList::Dictionary;
- using Integer = PropertyList::Integer;
- using String = PropertyList::String;
- auto propertyListEncoder = PropertyList(allocator);
- auto& rootDictionary = propertyListEncoder.rootDictionary();
- // The same plist contains both the customer and shared cache data, since they share layouts
- // We include dictionaries at the root so they can be lookup by leaf name or UUID
- auto& byUuidDictionary = rootDictionary.addObjectForKey<Dictionary>("uuids");
- auto& byNameDictionary = rootDictionary.addObjectForKey<Dictionary>("names");
- Dictionary* customerCacheAtlas = nullptr;
- Dictionary* devCacheAtlas = nullptr;
- Array* images = nullptr;
- uuid_string_t customerCacheUUID = {0};
- uuid_string_t devCacheUUID = {0};
- std::string customerCacheName;
- std::string devCacheName;
-
- for ( const SubCache& subCache : this->subCaches ) {
- if ( subCache.isMainCustomerCache() ) {
- customerCacheName = options.mainCacheFileName + subCache.fileSuffix;
- customerCacheAtlas = &byUuidDictionary.addObjectForKey<Dictionary>(subCache.uuidString);
- byNameDictionary.insertObjectForKey(customerCacheName, *customerCacheAtlas);
- addCacheAtlasInfo(customerCacheAtlas, subCache);
- strcpy(customerCacheUUID, subCache.uuidString);
- } else if ( subCache.isMainDevelopmentCache() ) {
- devCacheName = options.mainCacheFileName + subCache.fileSuffix;
- devCacheAtlas = &byUuidDictionary.addObjectForKey<Dictionary>(subCache.uuidString);
- byNameDictionary.insertObjectForKey(devCacheName, *devCacheAtlas);
- addCacheAtlasInfo(devCacheAtlas, subCache);
- strcpy(devCacheUUID, subCache.uuidString);
- }
- }
- assert((customerCacheAtlas != nullptr) || (devCacheAtlas != nullptr));
-
- // The bplist00 format supports uniquing objects, but uniquing collections is slow. Since we know a priori the images for both cache variants
- // will be the same we create it int he first cache atlas, and if there is a second we insert a reference to the already existing one.
- if (customerCacheAtlas) {
- images = &customerCacheAtlas->addObjectForKey<Array>(kDyldAtlasSharedCacheImageArrayKey);
- if (devCacheAtlas) {
- devCacheAtlas->insertObjectForKey(kDyldAtlasSharedCacheImageArrayKey, *images);
- }
- } else {
- images = &devCacheAtlas->addObjectForKey<Array>(kDyldAtlasSharedCacheImageArrayKey);
- }
-
- for ( const CacheDylib& cacheDylib : this->cacheDylibs ) {
- auto& image = images->addObject<Dictionary>();
- auto& segments = image.addObjectForKey<Array>(kDyldAtlasImageSegmentArrayKey);
- image.addObjectForKey<String>(kDyldAtlasImageInstallnameKey, cacheDylib.installName);
- image.addObjectForKey<Integer>(kDyldAtlasImagePreferredLoadAddressKey, cacheDylib.cacheLoadAddress.rawValue());
- uuid_t uuid;
- assert(cacheDylib.cacheHdr->getUuid(uuid));
- image.addObjectForKey<PropertyList::UUID>(kDyldAtlasImageUUIDKey, uuid);
-
- for ( const DylibSegmentChunk& segmentChunk : cacheDylib.segments ) {
- auto& segment = segments.addObject<Dictionary>();
- segment.addObjectForKey<String>(kDyldAtlasSegmentNameKey, segmentChunk.name());
- segment.addObjectForKey<Integer>(kDyldAtlasSegmentPreferredLoadAddressKey, segmentChunk.cacheVMAddress.rawValue());
- segment.addObjectForKey<Integer>(kDyldAtlasSegmentSizeKey, segmentChunk.cacheVMSize.rawValue());
- segment.addObjectForKey<Integer>(kDyldAtlasSegmentFileOffsetKey, segmentChunk.subCacheFileOffset.rawValue());
- segment.addObjectForKey<Integer>(kDyldAtlasSegmentFileSizeKey, segmentChunk.subCacheFileSize.rawValue());
- if ( strcmp("__TEXT", segmentChunk.name()) == 0 ) {
- segment.addObjectForKey<Integer>(kDyldAtlasSegmentPermissionsKey, VM_PROT_READ | VM_PROT_EXECUTE);
- } else if ( strcmp("__LINKEDIT", segmentChunk.name()) == 0 ) {
- segment.addObjectForKey<Integer>(kDyldAtlasSegmentPermissionsKey, VM_PROT_READ);
- } else {
- segment.addObjectForKey<Integer>(kDyldAtlasSegmentPermissionsKey, VM_PROT_READ | VM_PROT_WRITE);
- }
- }
- }
-
- ByteStream fileStream(allocator);
- propertyListEncoder.encode(fileStream);
- AAREncoder aarEncoder(allocator);
-
- if (customerCacheUUID[0]) {
- std::string plistPath = std::string("caches/uuids/") + customerCacheUUID + ".plist";
- std::string symlinkTarget = std::string("../uuids/") + customerCacheUUID + ".plist";
- std::string symlinkSource = std::string("caches/names/") + customerCacheName + ".plist";
-
- aarEncoder.addFile(plistPath, fileStream.span());
- aarEncoder.addSymLink(symlinkSource, symlinkTarget);
- if (devCacheUUID[0]) {
- std::string devPlistPath = std::string("caches/uuids/") + devCacheUUID + ".plist";
- std::string devSymlinkSource = std::string("caches/names/") + devCacheName + ".plist";
- aarEncoder.addSymLink(devPlistPath, symlinkTarget);
- aarEncoder.addSymLink(devSymlinkSource, symlinkTarget);
- }
- } else {
- assert(devCacheUUID[0] != 0);
- std::string plistPath = std::string("caches/uuids/") + devCacheUUID + ".plist";
- std::string symlinkTarget = std::string("../uuids/") + devCacheUUID + ".plist";
- std::string symlinkSource = std::string("caches/names/") + devCacheName + ".plist";
-
- aarEncoder.addFile(plistPath, fileStream.span());
- aarEncoder.addSymLink(symlinkSource, symlinkTarget);
- }
-
- ByteStream outputStream(allocator);
- aarEncoder.encode(outputStream);
- std::copy(outputStream.begin(), outputStream.end(), std::back_insert_iterator(atlasData));
-}
-
void SharedCacheBuilder::computeCacheHeaders()
{
Timer::Scope timedScope(this->config, "computeCacheHeaders time");
- // Content for all subcaches
+ for ( SubCache& subCache : this->subCaches )
+ subCache.writeCacheHeader(this->options, this->config, this->cacheDylibs);
+
+ // Content for the first (main) subCache only
__block uint32_t osVersion = 0;
__block uint32_t altPlatform = 0;
__block uint32_t altOsVersion = 0;
-
- // Content for the first (main) subCache only
CacheVMAddress dyldInCacheUnslidAddr = CacheVMAddress(0ULL);
CacheVMAddress dyldInCacheEntryUnslidAddr = CacheVMAddress(0ULL);
-
{
// look for libdyld.dylib and record OS verson info into cache header
for ( const CacheDylib& cacheDylib : this->cacheDylibs ) {
if ( endsWith(cacheDylib.installName, "/libdyld.dylib") ) {
- cacheDylib.inputHdr->platformAndVersions().unzip(^(mach_o::PlatformAndVersions pvs) {
- if ( pvs.platform == options.platform ) {
- osVersion = pvs.minOS.value();
+ cacheDylib.inputMF->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
+ if ( platform == options.platform ) {
+ osVersion = minOS;
}
else {
- altPlatform = pvs.platform.value();
- altOsVersion = pvs.minOS.value();
+ altPlatform = (uint32_t)platform;
+ altOsVersion = minOS;
}
});
}
@@ -8679,17 +6629,13 @@
dyldInCacheUnslidAddr = cacheDylib.cacheLoadAddress;
uint64_t dyldEntryOffset;
bool usesCRT;
- if ( cacheDylib.cacheHdr->getEntry(dyldEntryOffset, usesCRT) ) {
+ if ( cacheDylib.cacheMF->getEntry(dyldEntryOffset, usesCRT) ) {
// the "pc" value in the LC_UNIXTHREAD was adjusted when dyld was placed in the cache
dyldInCacheEntryUnslidAddr = dyldInCacheUnslidAddr + VMOffset(dyldEntryOffset);
}
}
}
}
-
- for ( SubCache& subCache : this->subCaches )
- subCache.writeCacheHeader(this->options, this->config, this->cacheDylibs,
- osVersion, altPlatform, altOsVersion);
// Fill in info for the main caches. This must be after addCacheHeaderImageInfo().
for ( SubCache& subCache : this->subCaches ) {
@@ -8697,11 +6643,11 @@
subCache.addMainCacheHeaderInfo(this->options, this->config,
this->cacheDylibs,
this->totalVMSize, getMaxSlide(),
+ osVersion, altPlatform, altOsVersion,
dyldInCacheUnslidAddr, dyldInCacheEntryUnslidAddr,
this->dylibTrieOptimizer,
- this->objcOptimizer, this->swiftOptimizer,
- this->patchTableOptimizer, this->functionVariantsOptimizer,
- this->prebuiltLoaderBuilder, this->prewarmingOptimizer);
+ this->objcOptimizer, this->swiftProtocolConformanceOptimizer,
+ this->patchTableOptimizer, this->prebuiltLoaderBuilder);
continue;
}
@@ -8779,17 +6725,7 @@
return buff;
}
-std::span<const std::string_view> SharedCacheBuilder::getEvictedDylibs() const
-{
- return this->evictedDylibs;
-}
-
-std::string_view SharedCacheBuilder::getSwiftPrespecializedDylibBuildError() const
-{
- return swiftPrespecializedDylibBuildError;
-}
-
-void SharedCacheBuilder::getResults(std::vector<CacheBuffer>& results, std::vector<std::byte>& atlas) const
+void SharedCacheBuilder::getResults(std::vector<CacheBuffer>& results) const
{
for ( const SubCache& subCache : this->subCaches ) {
CacheBuffer buffer;
@@ -8797,8 +6733,6 @@
buffer.bufferSize = subCache.bufferSize;
buffer.cdHash = cdHashToString(subCache.cdHash);
- if ( this->config.codeSign.mode == CodeSign::Mode::agile )
- buffer.agilecdHash = cdHashToString(subCache.agilecdHash);
buffer.uuid = subCache.uuidString;
buffer.cacheFileSuffix = subCache.fileSuffix;
@@ -8806,8 +6740,13 @@
buffer.usedByCustomerConfig = subCache.shouldKeepCache(false, true);
buffer.usedByDevelopmentConfig = subCache.shouldKeepCache(true, false);
+ // The builder executable also passes back the fd. This should typically be used instead of the data buffer
+#if !SUPPORT_CACHE_BUILDER_MEMORY_BUFFERS
+ buffer.fd = subCache.fd;
+ buffer.tempPath = subCache.tempPath;
+#endif
+
results.push_back(std::move(buffer));
- atlas = std::move(this->atlasData);
}
}
@@ -8823,15 +6762,12 @@
case Region::Kind::text:
prot = "EX";
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:
prot = "RW";
break;
- case Region::Kind::readOnly:
case Region::Kind::linkedit:
prot = "RO";
break;
@@ -8881,32 +6817,31 @@
assert(mainSubCache.isMainCache());
- json::Node cacheNode;
+ dyld3::json::Node cacheNode;
cacheNode.map["version"].value = "1";
cacheNode.map["disposition"].value = disposition;
- cacheNode.map["arch"].value = this->options.arch.name();
- cacheNode.map["platform"].value = mach_o::Platform(this->options.platform).name();
- cacheNode.map["base-address"].value = json::hex(baseAddress);
+ cacheNode.map["base-address"].value = dyld3::json::hex(baseAddress);
cacheNode.map["uuid"].value = mainSubCache.uuidString;
- json::Node imagesNode;
+ dyld3::json::Node imagesNode;
for ( const CacheDylib& cacheDylib : this->cacheDylibs ) {
- json::Node imageNode;
+ dyld3::json::Node imageNode;
imageNode.map["path"].value = cacheDylib.installName;
+ const dyld3::MachOFile* mf = cacheDylib.cacheMF;
uuid_t uuid;
- if ( cacheDylib.cacheHdr->getUuid(uuid) ) {
+ if ( mf->getUuid(uuid) ) {
uuid_string_t uuidStr;
uuid_unparse(uuid, uuidStr);
imageNode.map["uuid"].value = uuidStr;
}
- __block json::Node segmentsNode;
- cacheDylib.cacheHdr->forEachSegment(^(const Header::SegmentInfo &info, bool &stop) {
- json::Node segmentNode;
- segmentNode.map["name"].value = info.segmentName;
- segmentNode.map["start-vmaddr"].value = json::hex(info.vmaddr);
- segmentNode.map["end-vmaddr"].value = json::hex(info.vmaddr + info.vmsize);
+ __block dyld3::json::Node segmentsNode;
+ mf->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
+ dyld3::json::Node segmentNode;
+ segmentNode.map["name"].value = info.segName;
+ segmentNode.map["start-vmaddr"].value = dyld3::json::hex(info.vmAddr);
+ segmentNode.map["end-vmaddr"].value = dyld3::json::hex(info.vmAddr + info.vmSize);
// Add sections in verbose mode
segmentsNode.array.push_back(segmentNode);
@@ -8952,14 +6887,14 @@
return "";
}
-std::string SharedCacheBuilder::customerJSONMap(std::string_view disposition) const
+std::optional<std::string> SharedCacheBuilder::customerJSONMap(std::string_view disposition) const
{
for ( const SubCache& subCache : this->subCaches ) {
if ( subCache.isMainCustomerCache() )
return this->generateJSONMap(disposition, subCache);
}
- return "";
+ return std::nullopt;
}
std::string SharedCacheBuilder::developmentCacheUUID() const
@@ -8973,23 +6908,23 @@
return "";
}
-std::string SharedCacheBuilder::customerCacheUUID() const
+std::optional<std::string> SharedCacheBuilder::customerCacheUUID() const
{
for ( const SubCache& subCache : this->subCaches ) {
if ( subCache.isMainCustomerCache() )
return subCache.uuidString;
}
- return "";
+ return std::nullopt;
}
void SharedCacheBuilder::warning(const char *format, ...)
{
va_list list;
va_start(list, format);
- _SIMPLE_STRING buffer = _simple_salloc();
+ void* buffer = _simple_salloc();
_simple_vsprintf(buffer, format, list);
- this->warnings.push_back(_simple_string(buffer));
+ this->warnings.push_back((const char*)buffer);
_simple_sfree(buffer);
va_end(list);
@@ -9013,219 +6948,3 @@
fprintf(stderr, "Didn't find a dylib with install name: %s\n", installName);
}
-
-// This name is used only to create a placeholder input file and determine the library order.
-const std::string_view swiftPrespecializedDylibInstallName = "/usr/lib/libswiftPrespecialized.dylib";
-
-bool SharedCacheBuilder::shouldBuildSwiftPrespecializedDylib()
-{
- if ( options.platform == Platform::driverKit )
- return false;
-
- if ( options.platform.isExclaveKit() )
- return false;
-
- // build the dylib, only if the order file is defined
- if ( options.swiftGenericMetadataFile.empty() )
- return false;
-
- // check if the metadata builder is available
-#if !BUILDING_CACHE_BUILDER_UNIT_TESTS && !BUILDING_SIM_CACHE_BUILDER
- if ( swift_externalMetadataBuilder_create == nullptr )
- return false;
-#endif // !BUILDING_CACHE_BUILDER_UNIT_TESTS
-
- return true;
-}
-
-Error SharedCacheBuilder::buildSwiftPrespecializedDylibJSON()
-{
-#if !BUILDING_CACHE_BUILDER_UNIT_TESTS && !BUILDING_SIM_CACHE_BUILDER
- Timer::Scope timedScope(this->config, "buildSwiftPrespecializedDylibJSON time");
-
- SwiftExternalMetadataBuilder* builder = swift_externalMetadataBuilder_create(options.platform.value(), options.arch.name());
- if ( !builder )
- return Error("swift_externalMetadataBuilder_create failed");
-
- for ( const CacheDylib& dylib : this->cacheDylibs ) {
- if ( dylib.inputHdr == nullptr ) continue;
-
- // TODO: rdar://132262275 (dyld shared cache builder should tell Swift Metadata builder also about dyld)
- if ( dylib.inputHdr->isDylinker() ) continue;
-
- if ( const char* err = swift_externalMetadataBuilder_addDylib(builder, dylib.inputHdr->installName(),
- (const struct mach_header*)dylib.inputHdr, dylib.inputFile->size) )
- return Error("swift_externalMetadataBuilder_addDylib failed: %s", err);
- }
-
- if ( const char* err = swift_externalMetadataBuilder_readNamesJSON(builder, options.swiftGenericMetadataFile.c_str()) )
- return Error("swift_externalMetadataBuilder_readNamesJSON failed: %s", err);
-
- if ( const char* err = swift_externalMetadataBuilder_buildMetadata(builder) )
- return Error("swift_externalMetadataBuilder_buildMetadata failed: %s", err);
-
- if ( const char* json = swift_externalMetadataBuilder_getMetadataJSON(builder) )
- swiftPrespecializedDylibJSON = json;
- else
- return Error("swift_externalMetadataBuilder_getMetadataJSON returned an empty JSON");
-
- const std::string_view placeholderVersion = R"("platformVersion": "1.0")";
- // Patch platformVersion if it's 1.0 until rdar://122585868 is fixed
- if ( auto pos = swiftPrespecializedDylibJSON.find(placeholderVersion);
- pos != swiftPrespecializedDylibJSON.npos ) {
-
- __block mach_o::Version32 newMinOS;
- // determine new deployment target based on dyld's version
- for ( const InputFile& inputFile : allInputFiles ) {
- if ( !inputFile.mf )
- continue;
-
- if ( !endsWith(inputFile.path, "dyld") )
- continue;
-
- ((Header*)inputFile.mf)->platformAndVersions().unzip(^(mach_o::PlatformAndVersions pvs) {
- if ( pvs.platform == options.platform )
- newMinOS = pvs.minOS;
- });
- break;
- }
-
- if ( newMinOS > mach_o::Version32(1, 0) ) {
- char verStr[32];
- newMinOS.toString(verStr);
- std::string newVersion = "\"platformVersion\": \"";
- newVersion += verStr;
- newVersion += "\"";
-
- swiftPrespecializedDylibJSON.replace(pos, placeholderVersion.size(), newVersion);
- }
- }
-
- swift_externalMetadataBuilder_destroy(builder);
-
- if ( options.debug ) {
- std::string path;
- if ( const char* dir = getenv("TMPDIR") )
- path = dir;
- if ( path.empty() )
- path = "/tmp";
- path += "/swift-prespecialized.json-XXXXXX";
-
- int outFileFd = mkstemp(path.data());
- if ( outFileFd != -1 ) {
- write(outFileFd, swiftPrespecializedDylibJSON.data(), swiftPrespecializedDylibJSON.size());
- }
- }
-#endif // !BUILDING_CACHE_BUILDER_UNIT_TESTS
-
- return Error::none();
-}
-
-bool SharedCacheBuilder::reserveSwiftPrespecializedInputFile()
-{
- if ( !shouldBuildSwiftPrespecializedDylib() )
- return false;
-
- InputFile inputFile;
- inputFile.mf = nullptr;
- inputFile.inode = 0;
- inputFile.mtime = 0;
- inputFile.path = swiftPrespecializedDylibInstallName;
- allInputFiles.push_back(std::move(inputFile));
- cacheDylibs.push_back(CacheDylib(swiftPrespecializedDylibInstallName));
- return true;
-}
-
-Error SharedCacheBuilder::createSwiftPrespecializedDylib()
-{
- if ( !shouldBuildSwiftPrespecializedDylib() )
- return Error::none();
-
- if ( Error err = buildSwiftPrespecializedDylibJSON() )
- return err;
-
- Timer::Scope timedScope(this->config, "createSwiftPrespecializedDylib time");
-
- InputFile* inputFile = nullptr;
- if ( allInputFiles.empty() || allInputFiles.back().path != swiftPrespecializedDylibInstallName )
- return Error("missing input file placeholder for Swift prespecialized dylib");
- inputFile = &allInputFiles.back();
-
- std::vector<const char*> dylibsList;
- // the dylib list needs to be in order of objc dylibs
- for ( const CacheDylib* dylib : this->objcOptimizer.objcDylibs )
- dylibsList.push_back(CString::dup(dylib->installName).c_str());
-
- // TODO: support in-memory file buffer
- std::string path;
- if ( const char* dir = getenv("TMPDIR") )
- path = dir;
- if ( path.empty() )
- path = "/tmp";
- path += "/libswiftPrespecialized.dylib-XXXXXX";
-
- int outFileFd = mkstemp(path.data());
- if ( outFileFd == -1 )
- return Error("couldn't create a temporary file for Swift prespecialized dylib: %s", (const char*)strerror(errno));
-
- close(outFileFd);
- if ( const char* err = ldMakeDylibFromJSON(swiftPrespecializedDylibJSON, dylibsList, path.c_str()) )
- return Error("%s", err);
-
- // cleanup dylibs list
- for ( const char* str : dylibsList )
- free((void*)str);
-
- // re-open output file
- outFileFd = open(path.c_str(), O_RDONLY);
- if ( outFileFd < 0 )
- return Error("could not open swift dylib file because: %s", (const char*)strerror(errno));
-
- struct stat stat_buf;
- if ( fstat(outFileFd, &stat_buf) == -1 )
- return Error("could not stat swift dylib file because: %s", (const char*)strerror(errno));
-
- vm_size_t bufferSize = stat_buf.st_size;
- void* buffer = mmap(nullptr, bufferSize, PROT_READ, MAP_FILE | MAP_SHARED, outFileFd, 0);
- if ( buffer == MAP_FAILED ) {
- // Failed to mmap the file
- return Error("could not mmap swift dylib file because: %s", (const char*)strerror(errno));
- }
-
- Diagnostics diag;
- uint64_t sliceOffset = 0;
- inputFile->mf = MachOFile::compatibleSlice(diag, sliceOffset, inputFile->size, buffer, bufferSize, path.data(),
- this->options.platform, /* isOSBinary */ false,
- this->options.gradedArchs);
- if ( diag.hasError() )
- return Error("%s", diag.errorMessageCStr());
-
- // recreate cache dylib at the reserved slot
- auto cacheDylibIt = std::find_if(cacheDylibs.begin(), cacheDylibs.end(), [](CacheDylib& dylib) {
- return dylib.inputHdr == nullptr && dylib.installName == swiftPrespecializedDylibInstallName;
- });
- if ( cacheDylibIt == cacheDylibs.end() )
- return Error("missing cache dylib slot for Swift prespecialized dylib");
-
- // save previously computed cache index
- uint32_t index = cacheDylibIt->cacheIndex;
- // recreate cache dylib with the updated input file
- *cacheDylibIt = CacheDylib(*inputFile);
- cacheDylibIt->cacheIndex = index;
- // rdar://122906481 (Shared cache builder - explicitly model dylibs without a need for a patch table)
- cacheDylibIt->needsPatchTable = false;
- this->swiftPrespecializedDylib = &*cacheDylibIt;
-
- // sanity check Swift dylib compatibility
- __block Error err = Error::none();
- inputFile->mf->withFileLayout(diag, ^(const mach_o::Layout& layout) {
- mach_o::SplitSeg splitSeg(layout);
-
- if ( !splitSeg.isV2() )
- err = Error("Swift prespecialized dylib must use split seg V2");
- });
- if ( !inputFile->mf->hasChainedFixups() )
- err = Error("Swift prespecialized dylib must use chained fixups");
-
- return std::move(err);
-}