X-Git-Url: https://git.saurik.com/apple/dyld.git/blobdiff_plain/dd1e3476105179eb3efd5ebd2af97f6de91170b3..16b475fcb248267b8b51f759bc62a49ec2afa88d:/dyld3/shared-cache/SharedCacheBuilder.cpp diff --git a/dyld3/shared-cache/SharedCacheBuilder.cpp b/dyld3/shared-cache/SharedCacheBuilder.cpp new file mode 100644 index 0000000..b1523d1 --- /dev/null +++ b/dyld3/shared-cache/SharedCacheBuilder.cpp @@ -0,0 +1,3535 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ClosureBuilder.h" +#include "Closure.h" +#include "ClosureFileSystemNull.h" +#include "CodeSigningTypes.h" +#include "MachOFileAbstraction.hpp" +#include "SharedCacheBuilder.h" + +#include "FileUtils.h" +#include "StringUtils.h" +#include "Trie.hpp" + +#if __has_include("dyld_cache_config.h") + #include "dyld_cache_config.h" +#else + #define ARM_SHARED_REGION_START 0x1A000000ULL + #define ARM_SHARED_REGION_SIZE 0x26000000ULL + #define ARM64_SHARED_REGION_START 0x180000000ULL + #define ARM64_SHARED_REGION_SIZE 0x100000000ULL +#endif + +#ifndef ARM64_32_SHARED_REGION_START + #define ARM64_32_SHARED_REGION_START 0x1A000000ULL + #define ARM64_32_SHARED_REGION_SIZE 0x26000000ULL +#endif + +#if ARM_SHARED_REGION_SIZE > 0x26000000ULL + #define ARMV7K_CHAIN_BITS 0xC0000000 + #define ARMV7K_MAX 0x0 +#else + #define ARMV7K_CHAIN_BITS 0xE0000000 + #define ARMV7K_MAX 0x20000000 +#endif + +const SharedCacheBuilder::ArchLayout SharedCacheBuilder::_s_archLayout[] = { + { 0x7FFF20000000ULL, 0xEFE00000ULL, 0x0, 0x40000000, 0x00FFFF0000000000, "x86_64", CS_PAGE_SIZE_4K, 12, 2, true, true, true }, + { 0x7FFF20000000ULL, 0xEFE00000ULL, 0x0, 0x40000000, 0x00FFFF0000000000, "x86_64h", CS_PAGE_SIZE_4K, 12, 2, true, true, true }, + { SHARED_REGION_BASE_I386, SHARED_REGION_SIZE_I386, 0x0, 0x00200000, 0x0, "i386", CS_PAGE_SIZE_4K, 12, 0, false, false, true }, + { ARM64_SHARED_REGION_START, ARM64_SHARED_REGION_SIZE, 0x0, 0x02000000, 0x00FFFF0000000000, "arm64", CS_PAGE_SIZE_4K, 14, 2, false, true, false }, +#if SUPPORT_ARCH_arm64e + { ARM64_SHARED_REGION_START, ARM64_SHARED_REGION_SIZE, 0x0, 0x02000000, 0x00FFFF0000000000, "arm64e", CS_PAGE_SIZE_16K, 14, 2, false, true, false }, +#endif +#if SUPPORT_ARCH_arm64_32 + { ARM64_32_SHARED_REGION_START, ARM64_32_SHARED_REGION_SIZE,0x0, 0x02000000, 0xC0000000, "arm64_32", CS_PAGE_SIZE_16K, 14, 6, false, false, true }, +#endif + { ARM_SHARED_REGION_START, ARM_SHARED_REGION_SIZE, 0x0, 0x02000000, 0xE0000000, "armv7s", CS_PAGE_SIZE_4K, 14, 4, false, false, true }, + { ARM_SHARED_REGION_START, ARM_SHARED_REGION_SIZE, ARMV7K_MAX, 0x00400000, ARMV7K_CHAIN_BITS, "armv7k", CS_PAGE_SIZE_4K, 14, 4, false, false, true }, + { 0x40000000, 0x40000000, 0x0, 0x02000000, 0x0, "sim-x86", CS_PAGE_SIZE_4K, 14, 0, false, false, true } +}; + + +// These are dylibs that may be interposed, so stubs calling into them should never be bypassed +const char* const SharedCacheBuilder::_s_neverStubEliminateDylibs[] = { + "/usr/lib/system/libdispatch.dylib", + nullptr +}; + +// These are functions that are interposed by Instruments.app or ASan +const char* const SharedCacheBuilder::_s_neverStubEliminateSymbols[] = { + "___bzero", + "___cxa_atexit", + "___cxa_throw", + "__longjmp", + "__objc_autoreleasePoolPop", + "_accept", + "_access", + "_asctime", + "_asctime_r", + "_asprintf", + "_atoi", + "_atol", + "_atoll", + "_calloc", + "_chmod", + "_chown", + "_close", + "_confstr", + "_ctime", + "_ctime_r", + "_dispatch_after", + "_dispatch_after_f", + "_dispatch_async", + "_dispatch_async_f", + "_dispatch_barrier_async_f", + "_dispatch_group_async", + "_dispatch_group_async_f", + "_dispatch_source_set_cancel_handler", + "_dispatch_source_set_event_handler", + "_dispatch_sync_f", + "_dlclose", + "_dlopen", + "_dup", + "_dup2", + "_endgrent", + "_endpwent", + "_ether_aton", + "_ether_hostton", + "_ether_line", + "_ether_ntoa", + "_ether_ntohost", + "_fchmod", + "_fchown", + "_fclose", + "_fdopen", + "_fflush", + "_fopen", + "_fork", + "_fprintf", + "_free", + "_freopen", + "_frexp", + "_frexpf", + "_frexpl", + "_fscanf", + "_fstat", + "_fstatfs", + "_fstatfs64", + "_fsync", + "_ftime", + "_getaddrinfo", + "_getattrlist", + "_getcwd", + "_getgrent", + "_getgrgid", + "_getgrgid_r", + "_getgrnam", + "_getgrnam_r", + "_getgroups", + "_gethostbyaddr", + "_gethostbyname", + "_gethostbyname2", + "_gethostent", + "_getifaddrs", + "_getitimer", + "_getnameinfo", + "_getpass", + "_getpeername", + "_getpwent", + "_getpwnam", + "_getpwnam_r", + "_getpwuid", + "_getpwuid_r", + "_getsockname", + "_getsockopt", + "_gmtime", + "_gmtime_r", + "_if_indextoname", + "_if_nametoindex", + "_index", + "_inet_aton", + "_inet_ntop", + "_inet_pton", + "_initgroups", + "_ioctl", + "_lchown", + "_lgamma", + "_lgammaf", + "_lgammal", + "_link", + "_listxattr", + "_localtime", + "_localtime_r", + "_longjmp", + "_lseek", + "_lstat", + "_malloc", + "_malloc_create_zone", + "_malloc_default_purgeable_zone", + "_malloc_default_zone", + "_malloc_good_size", + "_malloc_make_nonpurgeable", + "_malloc_make_purgeable", + "_malloc_set_zone_name", + "_mbsnrtowcs", + "_mbsrtowcs", + "_mbstowcs", + "_memchr", + "_memcmp", + "_memcpy", + "_memmove", + "_memset", + "_mktime", + "_mlock", + "_mlockall", + "_modf", + "_modff", + "_modfl", + "_munlock", + "_munlockall", + "_objc_autoreleasePoolPop", + "_objc_setProperty", + "_objc_setProperty_atomic", + "_objc_setProperty_atomic_copy", + "_objc_setProperty_nonatomic", + "_objc_setProperty_nonatomic_copy", + "_objc_storeStrong", + "_open", + "_opendir", + "_poll", + "_posix_memalign", + "_pread", + "_printf", + "_pthread_attr_getdetachstate", + "_pthread_attr_getguardsize", + "_pthread_attr_getinheritsched", + "_pthread_attr_getschedparam", + "_pthread_attr_getschedpolicy", + "_pthread_attr_getscope", + "_pthread_attr_getstack", + "_pthread_attr_getstacksize", + "_pthread_condattr_getpshared", + "_pthread_create", + "_pthread_getschedparam", + "_pthread_join", + "_pthread_mutex_lock", + "_pthread_mutex_unlock", + "_pthread_mutexattr_getprioceiling", + "_pthread_mutexattr_getprotocol", + "_pthread_mutexattr_getpshared", + "_pthread_mutexattr_gettype", + "_pthread_rwlockattr_getpshared", + "_pwrite", + "_rand_r", + "_read", + "_readdir", + "_readdir_r", + "_readv", + "_readv$UNIX2003", + "_realloc", + "_realpath", + "_recv", + "_recvfrom", + "_recvmsg", + "_remquo", + "_remquof", + "_remquol", + "_scanf", + "_send", + "_sendmsg", + "_sendto", + "_setattrlist", + "_setgrent", + "_setitimer", + "_setlocale", + "_setpwent", + "_shm_open", + "_shm_unlink", + "_sigaction", + "_sigemptyset", + "_sigfillset", + "_siglongjmp", + "_signal", + "_sigpending", + "_sigprocmask", + "_sigwait", + "_snprintf", + "_sprintf", + "_sscanf", + "_stat", + "_statfs", + "_statfs64", + "_strcasecmp", + "_strcat", + "_strchr", + "_strcmp", + "_strcpy", + "_strdup", + "_strerror", + "_strerror_r", + "_strlen", + "_strncasecmp", + "_strncat", + "_strncmp", + "_strncpy", + "_strptime", + "_strtoimax", + "_strtol", + "_strtoll", + "_strtoumax", + "_tempnam", + "_time", + "_times", + "_tmpnam", + "_tsearch", + "_unlink", + "_valloc", + "_vasprintf", + "_vfprintf", + "_vfscanf", + "_vprintf", + "_vscanf", + "_vsnprintf", + "_vsprintf", + "_vsscanf", + "_wait", + "_wait$UNIX2003", + "_wait3", + "_wait4", + "_waitid", + "_waitid$UNIX2003", + "_waitpid", + "_waitpid$UNIX2003", + "_wcslen", + "_wcsnrtombs", + "_wcsrtombs", + "_wcstombs", + "_wordexp", + "_write", + "_writev", + "_writev$UNIX2003", + // always use stubs for C++ symbols that can be overridden + "__ZdaPv", + "__ZdlPv", + "__Znam", + "__Znwm", + + nullptr +}; + + +inline uint32_t absolutetime_to_milliseconds(uint64_t abstime) +{ + return (uint32_t)(abstime/1000/1000); +} + +// Handles building a list of input files to the SharedCacheBuilder itself. +class CacheInputBuilder { +public: + CacheInputBuilder(const dyld3::closure::FileSystem& fileSystem, + const dyld3::GradedArchs& archs, dyld3::Platform reqPlatform) + : fileSystem(fileSystem), reqArchs(archs), reqPlatform(reqPlatform) { } + + // Loads and maps any MachOs in the given list of files. + void loadMachOs(std::vector& inputFiles, + std::vector& dylibsToCache, + std::vector& otherDylibs, + std::vector& executables, + std::vector& couldNotLoadFiles) { + + std::map dylibInstallNameMap; + for (CacheBuilder::InputFile& inputFile : inputFiles) { + char realerPath[MAXPATHLEN]; + dyld3::closure::LoadedFileInfo loadedFileInfo = dyld3::MachOAnalyzer::load(inputFile.diag, fileSystem, inputFile.path, reqArchs, reqPlatform, realerPath); + const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)loadedFileInfo.fileContent; + if (ma == nullptr) { + couldNotLoadFiles.emplace_back((CacheBuilder::LoadedMachO){ DyldSharedCache::MappedMachO(), loadedFileInfo, &inputFile }); + continue; + } + + DyldSharedCache::MappedMachO mappedFile(inputFile.path, ma, loadedFileInfo.sliceLen, false, false, + loadedFileInfo.sliceOffset, loadedFileInfo.mtime, loadedFileInfo.inode); + + // The file can be loaded with the given slice, but we may still want to exlude it from the cache. + if (ma->isDylib()) { + std::string installName = ma->installName(); + + // Let the platform exclude the file before we do anything else. + if (platformExcludesInstallName(installName)) { + inputFile.diag.verbose("Platform excluded file\n"); + fileSystem.unloadFile(loadedFileInfo); + continue; + } + + if (!ma->canBePlacedInDyldCache(inputFile.path, ^(const char* msg) { + inputFile.diag.warning("Dylib located at '%s' cannot be placed in cache because: %s", inputFile.path, msg); + })) { + // TODO: Add exclusion lists here? + // Probably not as we already applied the dylib exclusion list. + if (!ma->canHavePrecomputedDlopenClosure(inputFile.path, ^(const char* msg) { + inputFile.diag.verbose("Dylib located at '%s' cannot prebuild dlopen closure in cache because: %s", inputFile.path, msg); + }) ) { + fileSystem.unloadFile(loadedFileInfo); + continue; + } + otherDylibs.emplace_back((CacheBuilder::LoadedMachO){ mappedFile, loadedFileInfo, &inputFile }); + continue; + } + + // Otherwise see if we have another file with this install name + auto iteratorAndInserted = dylibInstallNameMap.insert(std::make_pair(installName, dylibsToCache.size())); + if (iteratorAndInserted.second) { + // We inserted the dylib so we haven't seen another with this name. + if (installName[0] != '@' && installName != inputFile.path) { + inputFile.diag.warning("Dylib located at '%s' has installname '%s'", inputFile.path, installName.c_str()); + } + + dylibsToCache.emplace_back((CacheBuilder::LoadedMachO){ mappedFile, loadedFileInfo, &inputFile }); + } else { + // We didn't insert this one so we've seen it before. + CacheBuilder::LoadedMachO& previousLoadedMachO = dylibsToCache[iteratorAndInserted.first->second]; + inputFile.diag.warning("Multiple dylibs claim installname '%s' ('%s' and '%s')", installName.c_str(), inputFile.path, previousLoadedMachO.mappedFile.runtimePath.c_str()); + + // This is the "Good" one, overwrite + if (inputFile.path == installName) { + // Unload the old one + fileSystem.unloadFile(previousLoadedMachO.loadedFileInfo); + + // And replace with this one. + previousLoadedMachO.mappedFile = mappedFile; + previousLoadedMachO.loadedFileInfo = loadedFileInfo; + } + } + } else if (ma->isBundle()) { + // TODO: Add exclusion lists here? + if (!ma->canHavePrecomputedDlopenClosure(inputFile.path, ^(const char* msg) { + inputFile.diag.verbose("Dylib located at '%s' cannot prebuild dlopen closure in cache because: %s", inputFile.path, msg); + }) ) { + fileSystem.unloadFile(loadedFileInfo); + continue; + } + otherDylibs.emplace_back((CacheBuilder::LoadedMachO){ mappedFile, loadedFileInfo, &inputFile }); + } else if (ma->isDynamicExecutable()) { + if (platformExcludesExecutablePath_macOS(inputFile.path)) { + inputFile.diag.verbose("Platform excluded file\n"); + fileSystem.unloadFile(loadedFileInfo); + continue; + } + executables.emplace_back((CacheBuilder::LoadedMachO){ mappedFile, loadedFileInfo, &inputFile }); + } else { + inputFile.diag.verbose("Unsupported mach file type\n"); + fileSystem.unloadFile(loadedFileInfo); + } + } + } + +private: + + + + static bool platformExcludesInstallName_macOS(const std::string& installName) { + return false; + } + + static bool platformExcludesInstallName_iOS(const std::string& installName) { + if ( installName == "/System/Library/Caches/com.apple.xpc/sdk.dylib" ) + return true; + if ( installName == "/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib" ) + return true; + return false; + } + + static bool platformExcludesInstallName_tvOS(const std::string& installName) { + return platformExcludesInstallName_iOS(installName); + } + + static bool platformExcludesInstallName_watchOS(const std::string& installName) { + return platformExcludesInstallName_iOS(installName); + } + + static bool platformExcludesInstallName_bridgeOS(const std::string& installName) { + return platformExcludesInstallName_iOS(installName); + } + + // Returns true if the current platform requires that this install name be excluded from the shared cache + // Note that this overrides any exclusion from anywhere else. + bool platformExcludesInstallName(const std::string& installName) { + switch (reqPlatform) { + case dyld3::Platform::unknown: + return false; + case dyld3::Platform::macOS: + return platformExcludesInstallName_macOS(installName); + case dyld3::Platform::iOS: + return platformExcludesInstallName_iOS(installName); + case dyld3::Platform::tvOS: + return platformExcludesInstallName_tvOS(installName); + case dyld3::Platform::watchOS: + return platformExcludesInstallName_watchOS(installName); + case dyld3::Platform::bridgeOS: + return platformExcludesInstallName_bridgeOS(installName); + case dyld3::Platform::iOSMac: + return false; + case dyld3::Platform::iOS_simulator: + return false; + case dyld3::Platform::tvOS_simulator: + return false; + case dyld3::Platform::watchOS_simulator: + return false; + case dyld3::Platform::driverKit: + return false; + } + } + + + + + static bool platformExcludesExecutablePath_macOS(const std::string& path) { + return false; + } + + static bool platformExcludesExecutablePath_iOS(const std::string& path) { + //HACK exclude all launchd and installd variants until we can do something about xpcd_cache.dylib and friends + if (path == "/sbin/launchd" + || path == "/usr/local/sbin/launchd.debug" + || path == "/usr/local/sbin/launchd.development" + || path == "/usr/libexec/installd") { + return true; + } + return false; + } + + static bool platformExcludesExecutablePath_tvOS(const std::string& path) { + return platformExcludesExecutablePath_iOS(path); + } + + static bool platformExcludesExecutablePath_watchOS(const std::string& path) { + return platformExcludesExecutablePath_iOS(path); + } + + static bool platformExcludesExecutablePath_bridgeOS(const std::string& path) { + return platformExcludesExecutablePath_iOS(path); + } + + // Returns true if the current platform requires that this path be excluded from the shared cache + // Note that this overrides any exclusion from anywhere else. + bool platformExcludesExecutablePath(const std::string& path) { + switch (reqPlatform) { + case dyld3::Platform::unknown: + return false; + case dyld3::Platform::macOS: + return platformExcludesExecutablePath_macOS(path); + case dyld3::Platform::iOS: + return platformExcludesExecutablePath_iOS(path); + case dyld3::Platform::tvOS: + return platformExcludesExecutablePath_tvOS(path); + case dyld3::Platform::watchOS: + return platformExcludesExecutablePath_watchOS(path); + case dyld3::Platform::bridgeOS: + return platformExcludesExecutablePath_bridgeOS(path); + case dyld3::Platform::iOSMac: + return false; + case dyld3::Platform::iOS_simulator: + return false; + case dyld3::Platform::tvOS_simulator: + return false; + case dyld3::Platform::watchOS_simulator: + return false; + case dyld3::Platform::driverKit: + return false; + } + } + + const dyld3::closure::FileSystem& fileSystem; + const dyld3::GradedArchs& reqArchs; + dyld3::Platform reqPlatform; +}; + +SharedCacheBuilder::SharedCacheBuilder(const DyldSharedCache::CreateOptions& options, + const dyld3::closure::FileSystem& fileSystem) + : CacheBuilder(options, fileSystem) { + + std::string targetArch = options.archs->name(); + if ( options.forSimulator && (options.archs == &dyld3::GradedArchs::i386) ) + targetArch = "sim-x86"; + + for (const ArchLayout& layout : _s_archLayout) { + if ( layout.archName == targetArch ) { + _archLayout = &layout; + _is64 = _archLayout->is64; + break; + } + } + + if (!_archLayout) { + _diagnostics.error("Tool was built without support for: '%s'", targetArch.c_str()); + } +} + +static void verifySelfContained(std::vector& dylibsToCache, + std::vector& otherDylibs, + std::vector& couldNotLoadFiles) +{ + // build map of dylibs + __block std::map knownDylibs; + __block std::map allDylibs; + for (const CacheBuilder::LoadedMachO& dylib : dylibsToCache) { + knownDylibs.insert({ dylib.mappedFile.runtimePath, &dylib }); + allDylibs.insert({ dylib.mappedFile.runtimePath, &dylib }); + if (const char* installName = dylib.mappedFile.mh->installName()) { + knownDylibs.insert({ installName, &dylib }); + allDylibs.insert({ installName, &dylib }); + } + } + + for (const CacheBuilder::LoadedMachO& dylib : otherDylibs) { + allDylibs.insert({ dylib.mappedFile.runtimePath, &dylib }); + if (const char* installName = dylib.mappedFile.mh->installName()) + allDylibs.insert({ installName, &dylib }); + } + + for (const CacheBuilder::LoadedMachO& dylib : couldNotLoadFiles) { + allDylibs.insert({ dylib.inputFile->path, &dylib }); + } + + // check all dependencies to assure every dylib in cache only depends on other dylibs in cache + __block std::map> badDylibs; + __block bool doAgain = true; + while ( doAgain ) { + doAgain = false; + // scan dylib list making sure all dependents are in dylib list + for (const CacheBuilder::LoadedMachO& dylib : dylibsToCache) { + if ( badDylibs.count(dylib.mappedFile.runtimePath) != 0 ) + continue; + dylib.mappedFile.mh->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if (isWeak) + return; + if ( knownDylibs.count(loadPath) == 0 ) { + badDylibs[dylib.mappedFile.runtimePath].insert(std::string("Could not find dependency '") + loadPath + "'"); + knownDylibs.erase(dylib.mappedFile.runtimePath); + knownDylibs.erase(dylib.mappedFile.mh->installName()); + doAgain = true; + } + }); + } + } + + // Now walk the dylibs which depend on missing dylibs and see if any of them are required binaries. + for (auto badDylibsIterator : badDylibs) { + const std::string& dylibRuntimePath = badDylibsIterator.first; + auto requiredDylibIterator = allDylibs.find(dylibRuntimePath); + if (requiredDylibIterator == allDylibs.end()) + continue; + if (!requiredDylibIterator->second->inputFile->mustBeIncluded()) + continue; + // This dylib is required so mark all dependencies as requried too + __block std::vector worklist; + worklist.push_back(requiredDylibIterator->second); + while (!worklist.empty()) { + const CacheBuilder::LoadedMachO* dylib = worklist.back(); + worklist.pop_back(); + if (!dylib->mappedFile.mh) + continue; + dylib->mappedFile.mh->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if (isWeak) + return; + auto dylibIterator = allDylibs.find(loadPath); + if (dylibIterator != allDylibs.end()) { + if (dylibIterator->second->inputFile->state == CacheBuilder::InputFile::Unset) { + dylibIterator->second->inputFile->state = CacheBuilder::InputFile::MustBeIncludedForDependent; + worklist.push_back(dylibIterator->second); + } + } + }); + } + } + + // FIXME: Make this an option we can pass in + const bool evictLeafDylibs = true; + if (evictLeafDylibs) { + doAgain = true; + while ( doAgain ) { + doAgain = false; + + // build count of how many references there are to each dylib + __block std::set referencedDylibs; + for (const CacheBuilder::LoadedMachO& dylib : dylibsToCache) { + if ( badDylibs.count(dylib.mappedFile.runtimePath) != 0 ) + continue; + dylib.mappedFile.mh->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) { + referencedDylibs.insert(loadPath); + }); + } + + // find all dylibs not referenced + for (const CacheBuilder::LoadedMachO& dylib : dylibsToCache) { + if ( badDylibs.count(dylib.mappedFile.runtimePath) != 0 ) + continue; + const char* installName = dylib.mappedFile.mh->installName(); + if ( (referencedDylibs.count(installName) == 0) && (dylib.inputFile->state == CacheBuilder::InputFile::MustBeExcludedIfUnused) ) { + badDylibs[dylib.mappedFile.runtimePath].insert(std::string("It has been explicitly excluded as it is unused")); + doAgain = true; + } + } + } + } + + // Move bad dylibs from dylibs to cache to other dylibs. + for (const CacheBuilder::LoadedMachO& dylib : dylibsToCache) { + auto i = badDylibs.find(dylib.mappedFile.runtimePath); + if ( i != badDylibs.end()) { + otherDylibs.push_back(dylib); + for (const std::string& reason : i->second ) + otherDylibs.back().inputFile->diag.warning("Dylib located at '%s' not placed in shared cache because: %s", dylib.mappedFile.runtimePath.c_str(), reason.c_str()); + } + } + + const auto& badDylibsLambdaRef = badDylibs; + dylibsToCache.erase(std::remove_if(dylibsToCache.begin(), dylibsToCache.end(), [&](const CacheBuilder::LoadedMachO& dylib) { + if (badDylibsLambdaRef.find(dylib.mappedFile.runtimePath) != badDylibsLambdaRef.end()) + return true; + return false; + }), dylibsToCache.end()); +} + +// This is the new build API which takes the raw files (which could be FAT) and tries to build a cache from them. +// We should remove the other build() method, or make it private so that this can wrap it. +void SharedCacheBuilder::build(std::vector& inputFiles, + std::vector& aliases) { + // First filter down to files which are actually MachO's + CacheInputBuilder cacheInputBuilder(_fileSystem, *_options.archs, _options.platform); + + std::vector dylibsToCache; + std::vector otherDylibs; + std::vector executables; + std::vector couldNotLoadFiles; + cacheInputBuilder.loadMachOs(inputFiles, dylibsToCache, otherDylibs, executables, couldNotLoadFiles); + + verifySelfContained(dylibsToCache, otherDylibs, couldNotLoadFiles); + + // Check for required binaries before we try to build the cache + if (!_diagnostics.hasError()) { + // If we succeeded in building, then now see if there was a missing required file, and if so why its missing. + std::string errorString; + for (const LoadedMachO& dylib : otherDylibs) { + if (dylib.inputFile->mustBeIncluded()) { + // An error loading a required file must be propagated up to the top level diagnostic handler. + bool gotWarning = false; + for (const std::string& warning : dylib.inputFile->diag.warnings()) { + gotWarning = true; + std::string message = warning; + if (message.back() == '\n') + message.pop_back(); + if (!errorString.empty()) + errorString += "ERROR: "; + errorString += "Required binary was not included in the shared cache '" + std::string(dylib.inputFile->path) + "' because: " + message + "\n"; + } + if (!gotWarning) { + if (!errorString.empty()) + errorString += "ERROR: "; + errorString += "Required binary was not included in the shared cache '" + std::string(dylib.inputFile->path) + "' because: 'unknown error. Please report to dyld'\n"; + } + } + } + for (const LoadedMachO& dylib : couldNotLoadFiles) { + if (dylib.inputFile->mustBeIncluded()) { + if (dylib.inputFile->diag.hasError()) { + if (!errorString.empty()) + errorString += "ERROR: "; + errorString += "Required binary was not included in the shared cache '" + std::string(dylib.inputFile->path) + "' because: " + dylib.inputFile->diag.errorMessage() + "\n"; + } else { + if (!errorString.empty()) + errorString += "ERROR: "; + errorString += "Required binary was not included in the shared cache '" + std::string(dylib.inputFile->path) + "' because: 'unknown error. Please report to dyld'\n"; + + } + } + } + if (!errorString.empty()) { + _diagnostics.error("%s", errorString.c_str()); + } + } + + if (!_diagnostics.hasError()) + build(dylibsToCache, otherDylibs, executables, aliases); + + if (!_diagnostics.hasError()) { + // If we succeeded in building, then now see if there was a missing required file, and if so why its missing. + std::string errorString; + for (CacheBuilder::InputFile& inputFile : inputFiles) { + if (inputFile.mustBeIncluded() && inputFile.diag.hasError()) { + // An error loading a required file must be propagated up to the top level diagnostic handler. + std::string message = inputFile.diag.errorMessage(); + if (message.back() == '\n') + message.pop_back(); + errorString += "Required binary was not included in the shared cache '" + std::string(inputFile.path) + "' because: " + message + "\n"; + } + } + if (!errorString.empty()) { + _diagnostics.error("%s", errorString.c_str()); + } + } + + // Add all the warnings from the input files to the top level warnings on the main diagnostics object. + for (CacheBuilder::InputFile& inputFile : inputFiles) { + for (const std::string& warning : inputFile.diag.warnings()) + _diagnostics.warning("%s", warning.c_str()); + } + + // Clean up the loaded files + for (LoadedMachO& loadedMachO : dylibsToCache) + _fileSystem.unloadFile(loadedMachO.loadedFileInfo); + for (LoadedMachO& loadedMachO : otherDylibs) + _fileSystem.unloadFile(loadedMachO.loadedFileInfo); + for (LoadedMachO& loadedMachO : executables) + _fileSystem.unloadFile(loadedMachO.loadedFileInfo); +} + +void SharedCacheBuilder::build(const std::vector& dylibs, + const std::vector& otherOsDylibsInput, + const std::vector& osExecutables, + std::vector& aliases) { + + std::vector dylibsToCache; + std::vector otherDylibs; + std::vector executables; + + for (const DyldSharedCache::MappedMachO& mappedMachO : dylibs) { + dyld3::closure::LoadedFileInfo loadedFileInfo; + loadedFileInfo.fileContent = mappedMachO.mh; + loadedFileInfo.fileContentLen = mappedMachO.length; + loadedFileInfo.sliceOffset = mappedMachO.sliceFileOffset; + loadedFileInfo.sliceLen = mappedMachO.length; + loadedFileInfo.inode = mappedMachO.inode; + loadedFileInfo.mtime = mappedMachO.modTime; + loadedFileInfo.path = mappedMachO.runtimePath.c_str(); + dylibsToCache.emplace_back((LoadedMachO){ mappedMachO, loadedFileInfo, nullptr }); + } + + for (const DyldSharedCache::MappedMachO& mappedMachO : otherOsDylibsInput) { + dyld3::closure::LoadedFileInfo loadedFileInfo; + loadedFileInfo.fileContent = mappedMachO.mh; + loadedFileInfo.fileContentLen = mappedMachO.length; + loadedFileInfo.sliceOffset = mappedMachO.sliceFileOffset; + loadedFileInfo.sliceLen = mappedMachO.length; + loadedFileInfo.inode = mappedMachO.inode; + loadedFileInfo.mtime = mappedMachO.modTime; + loadedFileInfo.path = mappedMachO.runtimePath.c_str(); + otherDylibs.emplace_back((LoadedMachO){ mappedMachO, loadedFileInfo, nullptr }); + } + + for (const DyldSharedCache::MappedMachO& mappedMachO : osExecutables) { + dyld3::closure::LoadedFileInfo loadedFileInfo; + loadedFileInfo.fileContent = mappedMachO.mh; + loadedFileInfo.fileContentLen = mappedMachO.length; + loadedFileInfo.sliceOffset = mappedMachO.sliceFileOffset; + loadedFileInfo.sliceLen = mappedMachO.length; + loadedFileInfo.inode = mappedMachO.inode; + loadedFileInfo.mtime = mappedMachO.modTime; + loadedFileInfo.path = mappedMachO.runtimePath.c_str(); + executables.emplace_back((LoadedMachO){ mappedMachO, loadedFileInfo, nullptr }); + } + + build(dylibsToCache, otherDylibs, executables, aliases); +} + +void SharedCacheBuilder::build(const std::vector& dylibs, + const std::vector& otherOsDylibsInput, + const std::vector& osExecutables, + std::vector& aliases) +{ + // error out instead of crash if cache has no dylibs + // FIXME: plist should specify required vs optional dylibs + if ( dylibs.size() < 30 ) { + _diagnostics.error("missing required minimum set of dylibs"); + return; + } + uint64_t t1 = mach_absolute_time(); + + // make copy of dylib list and sort + makeSortedDylibs(dylibs, _options.dylibOrdering); + + // allocate space used by largest possible cache plus room for LINKEDITS before optimization + _allocatedBufferSize = _archLayout->sharedMemorySize * 1.50; + if ( vm_allocate(mach_task_self(), &_fullAllocatedBuffer, _allocatedBufferSize, VM_FLAGS_ANYWHERE) != 0 ) { + _diagnostics.error("could not allocate buffer"); + return; + } + + // assign addresses for each segment of each dylib in new cache + parseCoalescableSegments(); + processSelectorStrings(osExecutables); + assignSegmentAddresses(); + std::vector overflowDylibs; + while ( cacheOverflowAmount() != 0 ) { + if ( !_options.evictLeafDylibsOnOverflow ) { + _diagnostics.error("cache overflow by %lluMB", cacheOverflowAmount() / 1024 / 1024); + return; + } + size_t evictionCount = evictLeafDylibs(cacheOverflowAmount(), overflowDylibs); + // re-layout cache + for (DylibInfo& dylib : _sortedDylibs) + dylib.cacheLocation.clear(); + _coalescedText.clear(); + parseCoalescableSegments(); + processSelectorStrings(osExecutables); + assignSegmentAddresses(); + + _diagnostics.verbose("cache overflow, evicted %lu leaf dylibs\n", evictionCount); + } + markPaddingInaccessible(); + + // copy all segments into cache + uint64_t t2 = mach_absolute_time(); + writeCacheHeader(); + copyRawSegments(); + + // rebase all dylibs for new location in cache + uint64_t t3 = mach_absolute_time(); + _aslrTracker.setDataRegion(_readWriteRegion.buffer, _readWriteRegion.sizeInUse); + if ( !_options.cacheSupportsASLR ) + _aslrTracker.disable(); + adjustAllImagesForNewSegmentLocations(); + if ( _diagnostics.hasError() ) + return; + + // build ImageArray for dyld3, which has side effect of binding all cached dylibs + uint64_t t4 = mach_absolute_time(); + buildImageArray(aliases); + if ( _diagnostics.hasError() ) + return; + + // optimize ObjC + uint64_t t5 = mach_absolute_time(); + DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; + if ( _options.optimizeObjC ) + optimizeObjC(); + if ( _diagnostics.hasError() ) + return; + + + // optimize away stubs + uint64_t t6 = mach_absolute_time(); + if ( _options.optimizeStubs ) + optimizeAwayStubs(); + + + // FIPS seal corecrypto, This must be done after stub elimination (so that __TEXT,__text is not changed after sealing) + fipsSign(); + + // merge and compact LINKEDIT segments + uint64_t t7 = mach_absolute_time(); + optimizeLinkedit(); + + // copy ImageArray to end of read-only region + addImageArray(); + if ( _diagnostics.hasError() ) + return; + uint64_t t8 = mach_absolute_time(); + + // don't add dyld3 closures to simulator cache + if ( !dyld3::MachOFile::isSimulatorPlatform(_options.platform) ) { + // compute and add dlopen closures for all other dylibs + addOtherImageArray(otherOsDylibsInput, overflowDylibs); + if ( _diagnostics.hasError() ) + return; + + // compute and add launch closures to end of read-only region + addClosures(osExecutables); + if ( _diagnostics.hasError() ) + return; + } + + // update final readOnly region size + dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(_readExecuteRegion.buffer + dyldCache->header.mappingOffset); + mappings[2].size = _readOnlyRegion.sizeInUse; + if ( _options.excludeLocalSymbols ) + dyldCache->header.localSymbolsOffset = _readOnlyRegion.cacheFileOffset + _readOnlyRegion.sizeInUse; + + // record max slide now that final size is established + if ( _archLayout->sharedRegionsAreDiscontiguous ) { + // special case x86_64 which has three non-contiguous chunks each in their own 1GB regions + uint64_t maxSlide0 = 0x60000000 - _readExecuteRegion.sizeInUse; // TEXT region has 1.5GB region + uint64_t maxSlide1 = 0x40000000 - _readWriteRegion.sizeInUse; + uint64_t maxSlide2 = 0x3FE00000 - _readOnlyRegion.sizeInUse; + dyldCache->header.maxSlide = std::min(std::min(maxSlide0, maxSlide1), maxSlide2); + } + else { + // branch predictor on arm64 currently only looks at low 32-bits, so don't slide cache more than 2GB + if ( (_archLayout->sharedMemorySize == 0x100000000) && (_readExecuteRegion.sizeInUse < 0x80000000) ) + dyldCache->header.maxSlide = 0x80000000 - _readExecuteRegion.sizeInUse; + else + dyldCache->header.maxSlide = (_archLayout->sharedMemoryStart + _archLayout->sharedMemorySize) - (_readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse); + } + + // mark if any input dylibs were built with chained fixups + dyldCache->header.builtFromChainedFixups = _someDylibsUsedChainedFixups; + + uint64_t t9 = mach_absolute_time(); + + // fill in slide info at start of region[2] + // do this last because it modifies pointers in DATA segments + if ( _options.cacheSupportsASLR ) { +#if SUPPORT_ARCH_arm64e + if ( strcmp(_archLayout->archName, "arm64e") == 0 ) + writeSlideInfoV3(_aslrTracker.bitmap(), _aslrTracker.dataPageCount()); + else +#endif + if ( _archLayout->is64 ) + writeSlideInfoV2>(_aslrTracker.bitmap(), _aslrTracker.dataPageCount()); +#if SUPPORT_ARCH_arm64_32 || SUPPORT_ARCH_armv7k + else if ( _archLayout->pointerDeltaMask == 0xC0000000 ) + writeSlideInfoV4>(_aslrTracker.bitmap(), _aslrTracker.dataPageCount()); +#endif + else + writeSlideInfoV2>(_aslrTracker.bitmap(), _aslrTracker.dataPageCount()); + } + + uint64_t t10 = mach_absolute_time(); + + // last sanity check on size + if ( cacheOverflowAmount() != 0 ) { + _diagnostics.error("cache overflow after optimizations 0x%llX -> 0x%llX", _readExecuteRegion.unslidLoadAddress, _readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse); + return; + } + + // codesignature is part of file, but is not mapped + codeSign(); + if ( _diagnostics.hasError() ) + return; + + uint64_t t11 = mach_absolute_time(); + + if ( _options.verbose ) { + fprintf(stderr, "time to layout cache: %ums\n", absolutetime_to_milliseconds(t2-t1)); + fprintf(stderr, "time to copy cached dylibs into buffer: %ums\n", absolutetime_to_milliseconds(t3-t2)); + fprintf(stderr, "time to adjust segments for new split locations: %ums\n", absolutetime_to_milliseconds(t4-t3)); + fprintf(stderr, "time to bind all images: %ums\n", absolutetime_to_milliseconds(t5-t4)); + fprintf(stderr, "time to optimize Objective-C: %ums\n", absolutetime_to_milliseconds(t6-t5)); + fprintf(stderr, "time to do stub elimination: %ums\n", absolutetime_to_milliseconds(t7-t6)); + fprintf(stderr, "time to optimize LINKEDITs: %ums\n", absolutetime_to_milliseconds(t8-t7)); + fprintf(stderr, "time to build %lu closures: %ums\n", osExecutables.size(), absolutetime_to_milliseconds(t9-t8)); + fprintf(stderr, "time to compute slide info: %ums\n", absolutetime_to_milliseconds(t10-t9)); + fprintf(stderr, "time to compute UUID and codesign cache file: %ums\n", absolutetime_to_milliseconds(t11-t10)); + } + + return; +} + +const std::set SharedCacheBuilder::warnings() +{ + return _diagnostics.warnings(); +} + +const std::set SharedCacheBuilder::evictions() +{ + return _evictions; +} + +void SharedCacheBuilder::deleteBuffer() +{ + // Cache buffer + vm_deallocate(mach_task_self(), _fullAllocatedBuffer, _archLayout->sharedMemorySize); + _fullAllocatedBuffer = 0; + _allocatedBufferSize = 0; + // Local symbols buffer + if ( _localSymbolsRegion.bufferSize != 0 ) { + vm_deallocate(mach_task_self(), (vm_address_t)_localSymbolsRegion.buffer, _localSymbolsRegion.bufferSize); + _localSymbolsRegion.buffer = 0; + _localSymbolsRegion.bufferSize = 0; + } + // Code singatures + vm_deallocate(mach_task_self(), (vm_address_t)_codeSignatureRegion.buffer, _codeSignatureRegion.bufferSize); + _codeSignatureRegion.buffer = 0; + _codeSignatureRegion.bufferSize = 0; +} + + +void SharedCacheBuilder::makeSortedDylibs(const std::vector& dylibs, const std::unordered_map sortOrder) +{ + for (const LoadedMachO& dylib : dylibs) { + _sortedDylibs.push_back({ &dylib, dylib.mappedFile.runtimePath, {} }); + } + + std::sort(_sortedDylibs.begin(), _sortedDylibs.end(), [&](const DylibInfo& a, const DylibInfo& b) { + const auto& orderA = sortOrder.find(a.input->mappedFile.runtimePath); + const auto& orderB = sortOrder.find(b.input->mappedFile.runtimePath); + bool foundA = (orderA != sortOrder.end()); + bool foundB = (orderB != sortOrder.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 + bool isIOSMacA = strncmp(a.input->mappedFile.runtimePath.c_str(), "/System/iOSSupport/", 19) == 0; + bool isIOSMacB = strncmp(b.input->mappedFile.runtimePath.c_str(), "/System/iOSSupport/", 19) == 0; + if (isIOSMacA != isIOSMacB) + return !isIOSMacA; + + // Finally sort by path + return a.input->mappedFile.runtimePath < b.input->mappedFile.runtimePath; + }); +} + +struct DylibAndSize +{ + const CacheBuilder::LoadedMachO* input; + const char* installName; + uint64_t size; +}; + +uint64_t SharedCacheBuilder::cacheOverflowAmount() +{ + if ( _archLayout->sharedRegionsAreDiscontiguous ) { + // for macOS x86_64 cache, need to check each region for overflow + if ( _readExecuteRegion.sizeInUse > 0x60000000 ) + return (_readExecuteRegion.sizeInUse - 0x60000000); + + if ( _readWriteRegion.sizeInUse > 0x40000000 ) + return (_readWriteRegion.sizeInUse - 0x40000000); + + if ( _readOnlyRegion.sizeInUse > 0x3FE00000 ) + return (_readOnlyRegion.sizeInUse - 0x3FE00000); + } + else if ( _archLayout->textAndDataMaxSize != 0 ) { + // for armv7k, limit is 512MB of TEX+DATA + uint64_t totalTextAndData = _readWriteRegion.unslidLoadAddress + _readWriteRegion.sizeInUse - _readExecuteRegion.unslidLoadAddress; + if ( totalTextAndData < _archLayout->textAndDataMaxSize ) + return 0; + else + return totalTextAndData - _archLayout->textAndDataMaxSize; + } + else { + bool alreadyOptimized = (_readOnlyRegion.sizeInUse != _readOnlyRegion.bufferSize); + uint64_t vmSize = _readOnlyRegion.unslidLoadAddress - _readExecuteRegion.unslidLoadAddress; + if ( alreadyOptimized ) + vmSize += _readOnlyRegion.sizeInUse; + else if ( _options.excludeLocalSymbols ) + vmSize += (_readOnlyRegion.sizeInUse * 37/100); // assume locals removal and LINKEDIT optimzation reduces LINKEDITs %37 of original size + else + vmSize += (_readOnlyRegion.sizeInUse * 80/100); // assume LINKEDIT optimzation reduces LINKEDITs to %80 of original size + if ( vmSize > _archLayout->sharedMemorySize ) + return vmSize - _archLayout->sharedMemorySize; + } + // fits in shared region + return 0; +} + +size_t SharedCacheBuilder::evictLeafDylibs(uint64_t reductionTarget, std::vector& overflowDylibs) +{ + // build a reverse map of all dylib dependencies + __block std::map> references; + std::map>* referencesPtr = &references; + for (const DylibInfo& dylib : _sortedDylibs) { + // Esnure we have an entry (even if it is empty) + if (references.count(dylib.input->mappedFile.mh->installName()) == 0) { + references[dylib.input->mappedFile.mh->installName()] = std::set(); + }; + dylib.input->mappedFile.mh->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) { + references[loadPath].insert(dylib.input->mappedFile.mh->installName()); + }); + } + + // Find the sizes of all the dylibs + std::vector dylibsToSort; + std::vector sortedDylibs; + for (const DylibInfo& dylib : _sortedDylibs) { + const char* installName = dylib.input->mappedFile.mh->installName(); + __block uint64_t segsSize = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) { + if ( strcmp(info.segName, "__LINKEDIT") != 0 ) + segsSize += info.vmSize; + }); + dylibsToSort.push_back({ dylib.input, installName, 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 + + bool candidateFound = true; + while (candidateFound) { + candidateFound = false; + DylibAndSize candidate; + uint64_t candidateOrder = 0; + for(const auto& dylib : dylibsToSort) { + const auto &i = referencesPtr->find(dylib.installName); + assert(i != referencesPtr->end()); + if (!i->second.empty()) { + continue; + } + const auto& j = _options.dylibOrdering.find(dylib.input->mappedFile.runtimePath); + 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); + referencesPtr->erase(candidate.installName); + for (auto& dependent : references) { + (void)dependent.second.erase(candidate.installName); + } + auto j = std::find_if(dylibsToSort.begin(), dylibsToSort.end(), [&candidate](const DylibAndSize& dylib) { + return (strcmp(candidate.installName, dylib.installName) == 0); + }); + if (j != dylibsToSort.end()) { + dylibsToSort.erase(j); + } + } + } + + // build set of dylibs that if removed will allow cache to build + for (DylibAndSize& dylib : sortedDylibs) { + if ( _options.verbose ) + _diagnostics.warning("to prevent cache overflow, not caching %s", dylib.installName); + _evictions.insert(dylib.input->mappedFile.mh); + // Track the evicted dylibs so we can try build "other" dlopen closures for them. + overflowDylibs.push_back(dylib.input); + if ( dylib.size > reductionTarget ) + break; + reductionTarget -= dylib.size; + } + + // prune _sortedDylibs + _sortedDylibs.erase(std::remove_if(_sortedDylibs.begin(), _sortedDylibs.end(), [&](const DylibInfo& dylib) { + return (_evictions.count(dylib.input->mappedFile.mh) != 0); + }),_sortedDylibs.end()); + + return _evictions.size(); +} + + +void SharedCacheBuilder::writeCacheHeader() +{ + // "dyld_v1" + spaces + archName(), with enough spaces to pad to 15 bytes + std::string magic = "dyld_v1"; + magic.append(15 - magic.length() - strlen(_options.archs->name()), ' '); + magic.append(_options.archs->name()); + assert(magic.length() == 15); + + // fill in header + dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)_readExecuteRegion.buffer; + memcpy(dyldCacheHeader->magic, magic.c_str(), 16); + dyldCacheHeader->mappingOffset = sizeof(dyld_cache_header); + dyldCacheHeader->mappingCount = 3; + dyldCacheHeader->imagesOffset = (uint32_t)(dyldCacheHeader->mappingOffset + 3*sizeof(dyld_cache_mapping_info)); + dyldCacheHeader->imagesCount = (uint32_t)_sortedDylibs.size() + _aliasCount; + dyldCacheHeader->dyldBaseAddress = 0; + dyldCacheHeader->codeSignatureOffset = 0; + dyldCacheHeader->codeSignatureSize = 0; + dyldCacheHeader->slideInfoOffset = _slideInfoFileOffset; + dyldCacheHeader->slideInfoSize = _slideInfoBufferSizeAllocated; + dyldCacheHeader->localSymbolsOffset = 0; + dyldCacheHeader->localSymbolsSize = 0; + dyldCacheHeader->cacheType = _options.optimizeStubs ? kDyldSharedCacheTypeProduction : kDyldSharedCacheTypeDevelopment; + dyldCacheHeader->accelerateInfoAddr = 0; + dyldCacheHeader->accelerateInfoSize = 0; + bzero(dyldCacheHeader->uuid, 16);// overwritten later by recomputeCacheUUID() + dyldCacheHeader->branchPoolsOffset = 0; + dyldCacheHeader->branchPoolsCount = 0; + dyldCacheHeader->imagesTextOffset = dyldCacheHeader->imagesOffset + sizeof(dyld_cache_image_info)*dyldCacheHeader->imagesCount; + dyldCacheHeader->imagesTextCount = _sortedDylibs.size(); + dyldCacheHeader->patchInfoAddr = 0; + dyldCacheHeader->patchInfoSize = 0; + dyldCacheHeader->otherImageGroupAddrUnused = 0; + dyldCacheHeader->otherImageGroupSizeUnused = 0; + dyldCacheHeader->progClosuresAddr = 0; + dyldCacheHeader->progClosuresSize = 0; + dyldCacheHeader->progClosuresTrieAddr = 0; + dyldCacheHeader->progClosuresTrieSize = 0; + dyldCacheHeader->platform = (uint8_t)_options.platform; + dyldCacheHeader->formatVersion = dyld3::closure::kFormatVersion; + dyldCacheHeader->dylibsExpectedOnDisk = !_options.dylibsRemovedDuringMastering; + dyldCacheHeader->simulator = _options.forSimulator; + dyldCacheHeader->locallyBuiltCache = _options.isLocallyBuiltCache; + dyldCacheHeader->builtFromChainedFixups= false; + dyldCacheHeader->formatVersion = dyld3::closure::kFormatVersion; + dyldCacheHeader->sharedRegionStart = _archLayout->sharedMemoryStart; + dyldCacheHeader->sharedRegionSize = _archLayout->sharedMemorySize; + + // fill in mappings + dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(_readExecuteRegion.buffer + dyldCacheHeader->mappingOffset); + mappings[0].address = _readExecuteRegion.unslidLoadAddress; + mappings[0].fileOffset = 0; + mappings[0].size = _readExecuteRegion.sizeInUse; + mappings[0].maxProt = VM_PROT_READ | VM_PROT_EXECUTE; + mappings[0].initProt = VM_PROT_READ | VM_PROT_EXECUTE; + mappings[1].address = _readWriteRegion.unslidLoadAddress; + mappings[1].fileOffset = _readExecuteRegion.sizeInUse; + mappings[1].size = _readWriteRegion.sizeInUse; + mappings[1].maxProt = VM_PROT_READ | VM_PROT_WRITE; + mappings[1].initProt = VM_PROT_READ | VM_PROT_WRITE; + mappings[2].address = _readOnlyRegion.unslidLoadAddress; + mappings[2].fileOffset = _readExecuteRegion.sizeInUse + _readWriteRegion.sizeInUse; + mappings[2].size = _readOnlyRegion.sizeInUse; + mappings[2].maxProt = VM_PROT_READ; + mappings[2].initProt = VM_PROT_READ; + + // fill in image table + dyld_cache_image_info* images = (dyld_cache_image_info*)(_readExecuteRegion.buffer + dyldCacheHeader->imagesOffset); + for (const DylibInfo& dylib : _sortedDylibs) { + const char* installName = dylib.input->mappedFile.mh->installName(); + images->address = dylib.cacheLocation[0].dstCacheUnslidAddress; + if ( _options.dylibsRemovedDuringMastering ) { + images->modTime = 0; + images->inode = pathHash(installName); + } + else { + images->modTime = dylib.input->mappedFile.modTime; + images->inode = dylib.input->mappedFile.inode; + } + uint32_t installNameOffsetInTEXT = (uint32_t)(installName - (char*)dylib.input->mappedFile.mh); + images->pathFileOffset = (uint32_t)dylib.cacheLocation[0].dstCacheFileOffset + installNameOffsetInTEXT; + ++images; + } + // append aliases image records and strings +/* + for (auto &dylib : _dylibs) { + if (!dylib->installNameAliases.empty()) { + for (const std::string& alias : dylib->installNameAliases) { + images->set_address(_segmentMap[dylib][0].address); + if (_manifest.platform() == "osx") { + images->modTime = dylib->lastModTime; + images->inode = dylib->inode; + } + else { + images->modTime = 0; + images->inode = pathHash(alias.c_str()); + } + images->pathFileOffset = offset; + //fprintf(stderr, "adding alias %s for %s\n", alias.c_str(), dylib->installName.c_str()); + ::strcpy((char*)&_buffer[offset], alias.c_str()); + offset += alias.size() + 1; + ++images; + } + } + } +*/ + // calculate start of text image array and trailing string pool + dyld_cache_image_text_info* textImages = (dyld_cache_image_text_info*)(_readExecuteRegion.buffer + dyldCacheHeader->imagesTextOffset); + uint32_t stringOffset = (uint32_t)(dyldCacheHeader->imagesTextOffset + sizeof(dyld_cache_image_text_info) * _sortedDylibs.size()); + + // write text image array and image names pool at same time + for (const DylibInfo& dylib : _sortedDylibs) { + dylib.input->mappedFile.mh->getUuid(textImages->uuid); + textImages->loadAddress = dylib.cacheLocation[0].dstCacheUnslidAddress; + textImages->textSegmentSize = (uint32_t)dylib.cacheLocation[0].dstCacheSegmentSize; + textImages->pathOffset = stringOffset; + const char* installName = dylib.input->mappedFile.mh->installName(); + ::strcpy((char*)_readExecuteRegion.buffer + stringOffset, installName); + stringOffset += (uint32_t)strlen(installName)+1; + ++textImages; + } + + // make sure header did not overflow into first mapped image + const dyld_cache_image_info* firstImage = (dyld_cache_image_info*)(_readExecuteRegion.buffer + dyldCacheHeader->imagesOffset); + assert(stringOffset <= (firstImage->address - mappings[0].address)); +} + +void SharedCacheBuilder::processSelectorStrings(const std::vector& executables) { + const bool log = false; + + _selectorStringsFromExecutables = 0; + + // Don't do this optimisation on watchOS where the shared cache is too small + if (_options.platform == dyld3::Platform::watchOS) + return; + + // Get the method name coalesced section as that is where we need to put these strings + CacheBuilder::CacheCoalescedText::StringSection& cacheStringSection = _coalescedText.getSectionData("__objc_methname"); + for (const LoadedMachO& executable : executables) { + const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)executable.loadedFileInfo.fileContent; + + uint64_t sizeBeforeProcessing = cacheStringSection.bufferSize; + + ma->forEachObjCMethodName(^(const char *methodName) { + std::string_view str = methodName; + auto itAndInserted = cacheStringSection.stringsToOffsets.insert({ str, cacheStringSection.bufferSize }); + if (itAndInserted.second) { + // If we inserted the string then we need to include it in the total + cacheStringSection.bufferSize += str.size() + 1; + // if (log) printf("Selector: %s -> %s\n", ma->installName(), methodName); + ++_selectorStringsFromExecutables; + } + }); + + uint64_t sizeAfterProcessing = cacheStringSection.bufferSize; + if ( log && (sizeBeforeProcessing != sizeAfterProcessing) ) { + printf("Pulled in % 6lld bytes of selectors from %s\n", + sizeAfterProcessing - sizeBeforeProcessing, executable.loadedFileInfo.path); + } + } + + _diagnostics.verbose("Pulled in %lld selector strings from executables\n", _selectorStringsFromExecutables); +} + +void SharedCacheBuilder::parseCoalescableSegments() { + const bool log = false; + + for (DylibInfo& dylib : _sortedDylibs) + _coalescedText.parseCoalescableText(dylib.input->mappedFile.mh, dylib.textCoalescer); + + if (log) { + for (const char* section : CacheCoalescedText::SupportedSections) { + CacheCoalescedText::StringSection& sectionData = _coalescedText.getSectionData(section); + printf("Coalesced %s from % 10lld -> % 10d, saving % 10lld bytes\n", section, + sectionData.bufferSize + sectionData.savedSpace, sectionData.bufferSize, sectionData.savedSpace); + } + } +} + +void SharedCacheBuilder::assignSegmentAddresses() +{ + // calculate size of header info and where first dylib's mach_header should start + size_t startOffset = sizeof(dyld_cache_header) + 3*sizeof(dyld_cache_mapping_info); + startOffset += sizeof(dyld_cache_image_info) * _sortedDylibs.size(); + startOffset += sizeof(dyld_cache_image_text_info) * _sortedDylibs.size(); + for (const DylibInfo& dylib : _sortedDylibs) { + startOffset += (strlen(dylib.input->mappedFile.mh->installName()) + 1); + } + //fprintf(stderr, "%s total header size = 0x%08lX\n", _options.archName.c_str(), startOffset); + startOffset = align(startOffset, 12); + + // HACK!: Rebase v4 assumes that values below 0x8000 are not pointers (encoding as offsets from the cache header). + // If using a minimal cache, we need to pad out the cache header to make sure a pointer doesn't fall within that range +#if SUPPORT_ARCH_arm64_32 || SUPPORT_ARCH_armv7k + if ( _options.cacheSupportsASLR && !_archLayout->is64 ) { + if ( _archLayout->pointerDeltaMask == 0xC0000000 ) + startOffset = std::max(startOffset, (size_t)0x8000); + } +#endif + + // assign TEXT segment addresses + _readExecuteRegion.buffer = (uint8_t*)_fullAllocatedBuffer; + _readExecuteRegion.bufferSize = 0; + _readExecuteRegion.sizeInUse = 0; + _readExecuteRegion.unslidLoadAddress = _archLayout->sharedMemoryStart; + _readExecuteRegion.cacheFileOffset = 0; + __block uint64_t addr = _readExecuteRegion.unslidLoadAddress + startOffset; // header + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != (VM_PROT_READ | VM_PROT_EXECUTE) ) + return; + // We may have coalesced the sections at the end of this segment. In that case, shrink the segment to remove them. + __block size_t sizeOfSections = 0; + __block bool foundCoalescedSection = false; + dylib.input->mappedFile.mh->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo §Info, bool malformedSectionRange, bool &stopSection) { + if (strcmp(sectInfo.segInfo.segName, segInfo.segName) != 0) + return; + if ( dylib.textCoalescer.sectionWasCoalesced(sectInfo.sectName)) { + foundCoalescedSection = true; + } else { + sizeOfSections = sectInfo.sectAddr + sectInfo.sectSize - segInfo.vmAddr; + } + }); + if (!foundCoalescedSection) + sizeOfSections = segInfo.sizeOfSections; + + // Keep __TEXT segments 4K or more aligned + addr = align(addr, std::max((int)segInfo.p2align, (int)12)); + uint64_t offsetInRegion = addr - _readExecuteRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readExecuteRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)offsetInRegion; + loc.dstCacheSegmentSize = (uint32_t)align(sizeOfSections, 12); + loc.dstCacheFileSize = (uint32_t)align(sizeOfSections, 12); + loc.copySegmentSize = (uint32_t)sizeOfSections; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.dstCacheSegmentSize; + }); + } + // move read-only segments to end of TEXT + if ( _archLayout->textAndDataMaxSize != 0 ) { + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != VM_PROT_READ ) + return; + if ( strcmp(segInfo.segName, "__LINKEDIT") == 0 ) + return; + + // Keep segments segments 4K or more aligned + addr = align(addr, std::max((int)segInfo.p2align, (int)12)); + uint64_t offsetInRegion = addr - _readExecuteRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readExecuteRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readExecuteRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12); + loc.dstCacheFileSize = (uint32_t)segInfo.sizeOfSections; + loc.copySegmentSize = (uint32_t)segInfo.sizeOfSections; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.dstCacheSegmentSize; + }); + } + } + + // reserve space for objc optimization tables and deduped strings + uint64_t objcReadOnlyBufferVMAddr = addr; + _objcReadOnlyBuffer = _readExecuteRegion.buffer + (addr - _readExecuteRegion.unslidLoadAddress); + + // First the strings as we'll fill in the objc tables later in the optimizer + for (const char* section: CacheCoalescedText::SupportedSections) { + CacheCoalescedText::StringSection& cacheStringSection = _coalescedText.getSectionData(section); + cacheStringSection.bufferAddr = _readExecuteRegion.buffer + (addr - _readExecuteRegion.unslidLoadAddress); + cacheStringSection.bufferVMAddr = addr; + addr += cacheStringSection.bufferSize; + } + + addr = align(addr, 14); + _objcReadOnlyBufferSizeUsed = addr - objcReadOnlyBufferVMAddr; + + uint32_t totalSelectorRefCount = (uint32_t)_selectorStringsFromExecutables; + uint32_t totalClassDefCount = 0; + uint32_t totalProtocolDefCount = 0; + for (DylibInfo& dylib : _sortedDylibs) { + dyld3::MachOAnalyzer::ObjCInfo info = dylib.input->mappedFile.mh->getObjCInfo(); + totalSelectorRefCount += info.selRefCount; + totalClassDefCount += info.classDefCount; + totalProtocolDefCount += info.protocolDefCount; + } + + // now that shared cache coalesces all selector strings, use that better count + uint32_t coalescedSelectorCount = (uint32_t)_coalescedText.objcMethNames.stringsToOffsets.size(); + if ( coalescedSelectorCount > totalSelectorRefCount ) + totalSelectorRefCount = coalescedSelectorCount; + addr += align(computeReadOnlyObjC(totalSelectorRefCount, totalClassDefCount, totalProtocolDefCount), 14); + _objcReadOnlyBufferSizeAllocated = addr - objcReadOnlyBufferVMAddr; + + + // align TEXT region end + uint64_t endTextAddress = align(addr, _archLayout->sharedRegionAlignP2); + _readExecuteRegion.bufferSize = endTextAddress - _readExecuteRegion.unslidLoadAddress; + _readExecuteRegion.sizeInUse = _readExecuteRegion.bufferSize; + + + // assign __DATA* addresses + if ( _archLayout->sharedRegionsAreDiscontiguous ) + addr = _archLayout->sharedMemoryStart + 0x60000000; + else + addr = align((addr + _archLayout->sharedRegionPadding), _archLayout->sharedRegionAlignP2); + _readWriteRegion.buffer = (uint8_t*)_fullAllocatedBuffer + addr - _archLayout->sharedMemoryStart; + _readWriteRegion.bufferSize = 0; + _readWriteRegion.sizeInUse = 0; + _readWriteRegion.unslidLoadAddress = addr; + _readWriteRegion.cacheFileOffset = _readExecuteRegion.sizeInUse; + + // layout all __DATA_CONST segments + __block int dataConstSegmentCount = 0; + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( _options.platform == dyld3::Platform::watchOS_simulator) + return; + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != (VM_PROT_READ | VM_PROT_WRITE) ) + return; + if ( strcmp(segInfo.segName, "__DATA_CONST") != 0 ) + return; + ++dataConstSegmentCount; + // Pack __DATA_CONST segments + addr = align(addr, segInfo.p2align); + size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections); + uint64_t offsetInRegion = addr - _readWriteRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readWriteRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readWriteRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)segInfo.sizeOfSections; + loc.dstCacheFileSize = (uint32_t)copySize; + loc.copySegmentSize = (uint32_t)copySize; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.dstCacheSegmentSize; + }); + } + + // layout all __DATA segments (and other r/w non-dirty, non-const) segments + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != (VM_PROT_READ | VM_PROT_WRITE) ) + return; + if ( _options.platform != dyld3::Platform::watchOS_simulator) { + if ( strcmp(segInfo.segName, "__DATA_CONST") == 0 ) + return; + if ( strcmp(segInfo.segName, "__DATA_DIRTY") == 0 ) + return; + } + bool forcePageAlignedData = false; + if (_options.platform == dyld3::Platform::macOS) { + forcePageAlignedData = dylib.input->mappedFile.mh->hasUnalignedPointerFixups(); + //if ( forcePageAlignedData ) + // warning("unaligned pointer in %s\n", dylib.input->mappedFile.runtimePath.c_str()); + } + if ( (dataConstSegmentCount > 10) && !forcePageAlignedData ) { + // Pack __DATA segments only if we also have __DATA_CONST segments + addr = align(addr, segInfo.p2align); + } + else { + // Keep __DATA segments 4K or more aligned + addr = align(addr, std::max((int)segInfo.p2align, (int)12)); + } + size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections); + uint64_t offsetInRegion = addr - _readWriteRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readWriteRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readWriteRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)segInfo.sizeOfSections; + loc.dstCacheFileSize = (uint32_t)copySize; + loc.copySegmentSize = (uint32_t)copySize; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.dstCacheSegmentSize; + }); + } + + // layout all __DATA_DIRTY segments, sorted (FIXME) + const size_t dylibCount = _sortedDylibs.size(); + uint32_t dirtyDataSortIndexes[dylibCount]; + for (size_t i=0; i < dylibCount; ++i) + dirtyDataSortIndexes[i] = (uint32_t)i; + std::sort(&dirtyDataSortIndexes[0], &dirtyDataSortIndexes[dylibCount], [&](const uint32_t& a, const uint32_t& b) { + const auto& orderA = _options.dirtyDataSegmentOrdering.find(_sortedDylibs[a].input->mappedFile.runtimePath); + const auto& orderB = _options.dirtyDataSegmentOrdering.find(_sortedDylibs[b].input->mappedFile.runtimePath); + bool foundA = (orderA != _options.dirtyDataSegmentOrdering.end()); + bool foundB = (orderB != _options.dirtyDataSegmentOrdering.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; + else + return _sortedDylibs[a].input->mappedFile.runtimePath < _sortedDylibs[b].input->mappedFile.runtimePath; + }); + addr = align(addr, 12); + for (size_t i=0; i < dylibCount; ++i) { + DylibInfo& dylib = _sortedDylibs[dirtyDataSortIndexes[i]]; + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( _options.platform == dyld3::Platform::watchOS_simulator) + return; + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != (VM_PROT_READ | VM_PROT_WRITE) ) + return; + if ( strcmp(segInfo.segName, "__DATA_DIRTY") != 0 ) + return; + // Pack __DATA_DIRTY segments + addr = align(addr, segInfo.p2align); + size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections); + uint64_t offsetInRegion = addr - _readWriteRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readWriteRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readWriteRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)segInfo.sizeOfSections; + loc.dstCacheFileSize = (uint32_t)copySize; + loc.copySegmentSize = (uint32_t)copySize; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.dstCacheSegmentSize; + }); + } + + // reserve space for objc r/w optimization tables + _objcReadWriteBufferSizeAllocated = align(computeReadWriteObjC((uint32_t)_sortedDylibs.size(), totalProtocolDefCount), 14); + addr = align(addr, 4); // objc r/w section contains pointer and must be at least pointer align + _objcReadWriteBuffer = _readWriteRegion.buffer + (addr - _readWriteRegion.unslidLoadAddress); + addr += _objcReadWriteBufferSizeAllocated; + + // align DATA region end + uint64_t endDataAddress = align(addr, _archLayout->sharedRegionAlignP2); + _readWriteRegion.bufferSize = endDataAddress - _readWriteRegion.unslidLoadAddress; + _readWriteRegion.sizeInUse = _readWriteRegion.bufferSize; + + // start read-only region + if ( _archLayout->sharedRegionsAreDiscontiguous ) + addr = _archLayout->sharedMemoryStart + 0xA0000000; + else + addr = align((addr + _archLayout->sharedRegionPadding), _archLayout->sharedRegionAlignP2); + _readOnlyRegion.buffer = (uint8_t*)_fullAllocatedBuffer + addr - _archLayout->sharedMemoryStart; + _readOnlyRegion.bufferSize = 0; + _readOnlyRegion.sizeInUse = 0; + _readOnlyRegion.unslidLoadAddress = addr; + _readOnlyRegion.cacheFileOffset = _readWriteRegion.cacheFileOffset + _readWriteRegion.sizeInUse; + + // reserve space for kernel ASLR slide info at start of r/o region + if ( _options.cacheSupportsASLR ) { + size_t slideInfoSize = sizeof(dyld_cache_slide_info); + slideInfoSize = std::max(slideInfoSize, sizeof(dyld_cache_slide_info2)); + slideInfoSize = std::max(slideInfoSize, sizeof(dyld_cache_slide_info3)); + slideInfoSize = std::max(slideInfoSize, sizeof(dyld_cache_slide_info4)); + _slideInfoBufferSizeAllocated = align(slideInfoSize + (_readWriteRegion.sizeInUse/4096) * _archLayout->slideInfoBytesPerPage + 0x4000, _archLayout->sharedRegionAlignP2); + _slideInfoFileOffset = _readOnlyRegion.cacheFileOffset; + addr += _slideInfoBufferSizeAllocated; + } + + // layout all read-only (but not LINKEDIT) segments + if ( _archLayout->textAndDataMaxSize == 0 ) { + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != VM_PROT_READ ) + return; + if ( strcmp(segInfo.segName, "__LINKEDIT") == 0 ) + return; + + // Keep segments segments 4K or more aligned + addr = align(addr, std::max((int)segInfo.p2align, (int)12)); + uint64_t offsetInRegion = addr - _readOnlyRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readOnlyRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readOnlyRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12); + loc.dstCacheFileSize = (uint32_t)segInfo.sizeOfSections; + loc.copySegmentSize = (uint32_t)segInfo.sizeOfSections; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.dstCacheSegmentSize; + }); + } + } + // layout all LINKEDIT segments (after other read-only segments), aligned to 16KB + addr = align(addr, 14); + _nonLinkEditReadOnlySize = addr - _readOnlyRegion.unslidLoadAddress; + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != VM_PROT_READ ) + return; + if ( strcmp(segInfo.segName, "__LINKEDIT") != 0 ) + return; + // Keep segments segments 4K or more aligned + addr = align(addr, std::max((int)segInfo.p2align, (int)12)); + size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections); + uint64_t offsetInRegion = addr - _readOnlyRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readOnlyRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readOnlyRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12); + loc.dstCacheFileSize = (uint32_t)copySize; + loc.copySegmentSize = (uint32_t)copySize; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.dstCacheSegmentSize; + }); + } + + // align r/o region end + uint64_t endReadOnlyAddress = align(addr, _archLayout->sharedRegionAlignP2); + _readOnlyRegion.bufferSize = endReadOnlyAddress - _readOnlyRegion.unslidLoadAddress; + _readOnlyRegion.sizeInUse = _readOnlyRegion.bufferSize; + + //fprintf(stderr, "RX region=%p -> %p, logical addr=0x%llX\n", _readExecuteRegion.buffer, _readExecuteRegion.buffer+_readExecuteRegion.bufferSize, _readExecuteRegion.unslidLoadAddress); + //fprintf(stderr, "RW region=%p -> %p, logical addr=0x%llX\n", _readWriteRegion.buffer, _readWriteRegion.buffer+_readWriteRegion.bufferSize, _readWriteRegion.unslidLoadAddress); + //fprintf(stderr, "RO region=%p -> %p, logical addr=0x%llX\n", _readOnlyRegion.buffer, _readOnlyRegion.buffer+_readOnlyRegion.bufferSize, _readOnlyRegion.unslidLoadAddress); + + // sort SegmentMappingInfo for each image to be in the same order as original segments + for (DylibInfo& dylib : _sortedDylibs) { + std::sort(dylib.cacheLocation.begin(), dylib.cacheLocation.end(), [&](const SegmentMappingInfo& a, const SegmentMappingInfo& b) { + return a.srcSegmentIndex < b.srcSegmentIndex; + }); + } +} + +static dyld_cache_patchable_location makePatchLocation(size_t cacheOff, uint64_t ad) { + int64_t signedAddend = (int64_t)ad; + assert(((signedAddend << 52) >> 52) == signedAddend); + dyld_cache_patchable_location patch; + patch.cacheOffset = cacheOff; + patch.addend = ad; + patch.authenticated = 0; + patch.usesAddressDiversity = 0; + patch.key = 0; + patch.discriminator = 0; + return patch; +} + +static dyld_cache_patchable_location makePatchLocation(size_t cacheOff, uint64_t ad, + dyld3::MachOLoaded::ChainedFixupPointerOnDisk authInfo) { + int64_t signedAddend = (int64_t)ad; + assert(((signedAddend << 52) >> 52) == signedAddend); + dyld_cache_patchable_location patch; + patch.cacheOffset = cacheOff; + patch.addend = ad; + patch.authenticated = authInfo.arm64e.authBind.auth; + patch.usesAddressDiversity = authInfo.arm64e.authBind.addrDiv; + patch.key = authInfo.arm64e.authBind.key; + patch.discriminator = authInfo.arm64e.authBind.diversity; + return patch; +} + +void SharedCacheBuilder::buildImageArray(std::vector& aliases) +{ + typedef dyld3::closure::ClosureBuilder::CachedDylibInfo CachedDylibInfo; + + // convert STL data structures to simple arrays to passe to makeDyldCacheImageArray() + __block std::vector dylibInfos; + __block std::unordered_map imageNumToML; + DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + cache->forEachImage(^(const mach_header* mh, const char* installName) { + uint64_t mtime; + uint64_t inode; + cache->getIndexedImageEntry((uint32_t)dylibInfos.size(), mtime, inode); + CachedDylibInfo entry; + entry.fileInfo.fileContent = mh; + entry.fileInfo.path = installName; + entry.fileInfo.sliceOffset = 0; + entry.fileInfo.inode = inode; + entry.fileInfo.mtime = mtime; + dylibInfos.push_back(entry); + imageNumToML[(dyld3::closure::ImageNum)(dylibInfos.size())] = (dyld3::MachOLoaded*)mh; + }); + + // Convert symlinks from STL to simple char pointers. + std::vector dylibAliases; + dylibAliases.reserve(aliases.size()); + for (const auto& alias : aliases) + dylibAliases.push_back({ alias.realPath.c_str(), alias.aliasPath.c_str() }); + + dyld3::closure::ClosureBuilder::CacheDylibsBindingHandlers handlers; + + handlers.chainedBind = ^(dyld3::closure::ImageNum, const dyld3::MachOLoaded* imageLoadAddress, + const dyld_chained_starts_in_image* starts, + const dyld3::Array& targets, + const dyld3::Array& targetInfos) { + _someDylibsUsedChainedFixups = true; + imageLoadAddress->forEachFixupInAllChains(_diagnostics, starts, true, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) { + uint64_t offsetInCache; + dyld3::closure::Image::ResolvedSymbolTarget target; + const dyld3::closure::ClosureBuilder::ResolvedTargetInfo* targetInfo; + dyld3::MachOLoaded::ChainedFixupPointerOnDisk orgValue; + uint64_t targetVMAddr; + uint64_t addend; + bool pointerIntoCache = true; + switch (segInfo->pointer_format) { + case DYLD_CHAINED_PTR_ARM64E: + orgValue = *fixupLoc; + if ( fixupLoc->arm64e.bind.bind ) { + target = targets[fixupLoc->arm64e.bind.ordinal]; + targetInfo = &targetInfos[fixupLoc->arm64e.bind.ordinal]; + switch ( target.sharedCache.kind ) { + case dyld3::closure::Image::ResolvedSymbolTarget::kindSharedCache: + offsetInCache = target.sharedCache.offset - targetInfo->addend; + _dylibToItsExports[targetInfo->foundInDylib].insert(offsetInCache); + _exportsToName[offsetInCache] = targetInfo->foundSymbolName; + addend = targetInfo->addend; + { + uint16_t top16 = addend >> 48; + uint8_t top8 = addend >> 56; + // if top byte is non-zero and this is not a negative addend, pull off high8 + if ( (top16 != 0xFFFF) && (top8 != 0) ) { + _aslrTracker.setHigh8(fixupLoc, top8); + addend &= 0x00FFFFFFFFFFFFFFULL; + } + } + targetVMAddr = _archLayout->sharedMemoryStart + target.sharedCache.offset; + if ( fixupLoc->arm64e.authBind.auth ) { + // store auth data in side table + _aslrTracker.setAuthData(fixupLoc, fixupLoc->arm64e.authBind.diversity, fixupLoc->arm64e.authBind.addrDiv, fixupLoc->arm64e.authBind.key); + _exportsToUses[offsetInCache].push_back(makePatchLocation((uint8_t*)fixupLoc - _readExecuteRegion.buffer, addend, *fixupLoc)); + } + else { + _exportsToUses[offsetInCache].push_back(makePatchLocation((uint8_t*)fixupLoc - _readExecuteRegion.buffer, addend)); + // plain binds can have addend in chain + targetVMAddr += fixupLoc->arm64e.bind.addend; + } + // change location from a chain ptr into a raw pointer to the target vmaddr + fixupLoc->raw64 = targetVMAddr; + _aslrTracker.add(fixupLoc); + break; + case dyld3::closure::Image::ResolvedSymbolTarget::kindAbsolute: + fixupLoc->raw64 = target.absolute.value; + pointerIntoCache = false; + // don't record absolute targets for ASLR + _aslrTracker.remove(fixupLoc); + if ( (targetInfo->libOrdinal > 0) && (targetInfo->libOrdinal <= (int)(imageLoadAddress->dependentDylibCount())) ) { + _missingWeakImports[fixupLoc] = imageLoadAddress->dependentDylibLoadPath(targetInfo->libOrdinal - 1); + } + break; + default: + assert(0 && "unsupported ResolvedSymbolTarget kind in dyld cache"); + } + } + else { + // convert rebase chain entry to raw pointer to target vmaddr + if ( fixupLoc->arm64e.rebase.auth ) { + // store auth data in side table + _aslrTracker.setAuthData(fixupLoc, fixupLoc->arm64e.authRebase.diversity, fixupLoc->arm64e.authRebase.addrDiv, fixupLoc->arm64e.authRebase.key); + targetVMAddr = fixupLoc->arm64e.authRebase.target; + fixupLoc->raw64 = targetVMAddr; + } + else { + targetVMAddr = fixupLoc->arm64e.rebase.target; + if ( targetVMAddr == CacheBuilder::kRebaseTargetInSideTableArm64e ) { + // target was stored in side table + _aslrTracker.hasRebaseTarget64(fixupLoc, &targetVMAddr); + } + // store high8 in side table + if ( fixupLoc->arm64e.rebase.high8 ) + _aslrTracker.setHigh8(fixupLoc, fixupLoc->arm64e.rebase.high8); + fixupLoc->raw64 = targetVMAddr; + } + } + if ( pointerIntoCache ) { + assert(fixupLoc->raw64 > _readExecuteRegion.unslidLoadAddress); + assert(fixupLoc->raw64 < _readOnlyRegion.unslidLoadAddress+_readOnlyRegion.sizeInUse); + } + break; + case DYLD_CHAINED_PTR_64: + if ( fixupLoc->generic64.bind.bind ) { + target = targets[fixupLoc->generic64.bind.ordinal]; + targetInfo = &targetInfos[fixupLoc->generic64.bind.ordinal]; + switch ( target.sharedCache.kind ) { + case dyld3::closure::Image::ResolvedSymbolTarget::kindSharedCache: + offsetInCache = target.sharedCache.offset - targetInfo->addend; + _dylibToItsExports[targetInfo->foundInDylib].insert(offsetInCache); + _exportsToName[offsetInCache] = targetInfo->foundSymbolName; + addend = targetInfo->addend + fixupLoc->generic64.bind.addend; + { + uint16_t top16 = addend >> 48; + uint8_t top8 = addend >> 56; + // if top byte is non-zero and this is not a negative addend, pull off high8 + if ( (top16 != 0xFFFF) && (top8 != 0) ) { + _aslrTracker.setHigh8(fixupLoc, top8); + addend &= 0x00FFFFFFFFFFFFFFULL; + } + } + // turn this bind into a flat vmaddr + fixupLoc->raw64 = _archLayout->sharedMemoryStart + target.sharedCache.offset + fixupLoc->generic64.bind.addend; + _exportsToUses[offsetInCache].push_back(makePatchLocation((uint8_t*)fixupLoc - _readExecuteRegion.buffer, addend)); + _aslrTracker.add(fixupLoc); + break; + case dyld3::closure::Image::ResolvedSymbolTarget::kindAbsolute: + fixupLoc->raw64 = target.absolute.value; + pointerIntoCache = false; + // don't record absolute targets for ASLR + _aslrTracker.remove(fixupLoc); + if ( (targetInfo->libOrdinal > 0) && (targetInfo->libOrdinal <= (int)(imageLoadAddress->dependentDylibCount())) ) { + _missingWeakImports[fixupLoc] = imageLoadAddress->dependentDylibLoadPath(targetInfo->libOrdinal - 1); + } + break; + default: + assert(0 && "unsupported ResolvedSymbolTarget kind in dyld cache"); + } + } + else { + // convert rebase chain entry to raw pointer to target vmaddr + targetVMAddr = fixupLoc->generic64.rebase.target; + if ( targetVMAddr == CacheBuilder::kRebaseTargetInSideTableArm64 ) { + // target was stored in side table + _aslrTracker.hasRebaseTarget64(fixupLoc, &targetVMAddr); + } + // store high8 in side table + if ( fixupLoc->generic64.rebase.high8 ) + _aslrTracker.setHigh8(fixupLoc, fixupLoc->generic64.rebase.high8); + fixupLoc->raw64 = targetVMAddr; + } + if ( pointerIntoCache ) { + assert(fixupLoc->raw64 > _readExecuteRegion.unslidLoadAddress); + assert(fixupLoc->raw64 < _readOnlyRegion.unslidLoadAddress+_readOnlyRegion.sizeInUse); + } + break; + case DYLD_CHAINED_PTR_32: + if ( fixupLoc->generic32.bind.bind ) { + // turn this bind into a flat vmaddr pointer + target = targets[fixupLoc->generic32.bind.ordinal]; + targetInfo = &targetInfos[fixupLoc->generic32.bind.ordinal]; + switch ( target.sharedCache.kind ) { + case dyld3::closure::Image::ResolvedSymbolTarget::kindSharedCache: + offsetInCache = target.sharedCache.offset - targetInfo->addend; + _dylibToItsExports[targetInfo->foundInDylib].insert(offsetInCache); + _exportsToName[offsetInCache] = targetInfo->foundSymbolName; + fixupLoc->raw32 = (uint32_t)(_archLayout->sharedMemoryStart + target.sharedCache.offset + fixupLoc->generic32.bind.addend); + _exportsToUses[offsetInCache].push_back(makePatchLocation((uint8_t*)fixupLoc - _readExecuteRegion.buffer, targetInfo->addend+fixupLoc->generic32.bind.addend)); + _aslrTracker.add(fixupLoc); + break; + case dyld3::closure::Image::ResolvedSymbolTarget::kindAbsolute: + fixupLoc->raw32 = (uint32_t)target.absolute.value; + pointerIntoCache = false; + // don't record absolute targets for ASLR + _aslrTracker.remove(fixupLoc); + if ( (targetInfo->libOrdinal > 0) && (targetInfo->libOrdinal <= (int)(imageLoadAddress->dependentDylibCount())) ) { + _missingWeakImports[fixupLoc] = imageLoadAddress->dependentDylibLoadPath(targetInfo->libOrdinal - 1); + } + break; + default: + assert(0 && "unsupported ResolvedSymbolTarget kind in dyld cache"); + } + } + else if ( fixupLoc->generic32.rebase.target == CacheBuilder::kRebaseTargetInSideTableGeneric32 ) { + uint32_t targetVmAddr; + if ( _aslrTracker.hasRebaseTarget32(fixupLoc, &targetVmAddr) ) + fixupLoc->raw32 = targetVmAddr; + else + assert(0 && "missing target for rebase"); + } + else if ( fixupLoc->generic32.rebase.target > segInfo->max_valid_pointer ) { + __block const char* badImagePath = nullptr; + cache->forEachImage(^(const mach_header* mh, const char* installName) { + if ( mh == imageLoadAddress ) + badImagePath = installName; + }); + _diagnostics.error("unexpected non-pointer in chain for image at %s", badImagePath); + stop = true; + pointerIntoCache = false; + } + if ( pointerIntoCache ) { + assert(fixupLoc->raw32 > _readExecuteRegion.unslidLoadAddress); + assert(fixupLoc->raw32 < _readOnlyRegion.unslidLoadAddress+_readOnlyRegion.sizeInUse); + } + break; + case DYLD_CHAINED_PTR_64_OFFSET: + case DYLD_CHAINED_PTR_ARM64E_OFFSET: + assert(0 && "unsupported chained bind type"); + break; + default: + assert(0 && "unsupported chained bind type"); + } + + }); + }; + + handlers.rebase = ^(dyld3::closure::ImageNum imageNum, const dyld3::MachOLoaded* imageToFix, uint32_t runtimeOffset) { + // record location in aslr tracker so kernel can slide this on page-in + uint8_t* fixupLoc = (uint8_t*)imageToFix+runtimeOffset; + _aslrTracker.add(fixupLoc); + }; + + handlers.bind = ^(dyld3::closure::ImageNum imageNum, const dyld3::MachOLoaded* mh, + uint32_t runtimeOffset, dyld3::closure::Image::ResolvedSymbolTarget target, + const dyld3::closure::ClosureBuilder::ResolvedTargetInfo& targetInfo) { + uint8_t* fixupLoc = (uint8_t*)mh+runtimeOffset; + + // binder is called a second time for weak_bind info, which we ignore when building cache + const bool weakDefUseAlreadySet = targetInfo.weakBindCoalese && _aslrTracker.has(fixupLoc); + + // do actual bind that sets pointer in image to unslid target address + uint64_t offsetInCache; + switch ( target.sharedCache.kind ) { + case dyld3::closure::Image::ResolvedSymbolTarget::kindSharedCache: + offsetInCache = target.sharedCache.offset - targetInfo.addend; + _dylibToItsExports[targetInfo.foundInDylib].insert(offsetInCache); + if (targetInfo.isWeakDef) + _dylibWeakExports.insert({ targetInfo.foundInDylib, offsetInCache }); + _exportsToUses[offsetInCache].push_back(makePatchLocation(fixupLoc - _readExecuteRegion.buffer, targetInfo.addend)); + _exportsToName[offsetInCache] = targetInfo.foundSymbolName; + if ( !weakDefUseAlreadySet ) { + if ( _archLayout->is64 ) + *((uint64_t*)fixupLoc) = _archLayout->sharedMemoryStart + target.sharedCache.offset; + else + *((uint32_t*)fixupLoc) = (uint32_t)(_archLayout->sharedMemoryStart + target.sharedCache.offset); + // record location in aslr tracker so kernel can slide this on page-in + _aslrTracker.add(fixupLoc); + } + break; + case dyld3::closure::Image::ResolvedSymbolTarget::kindAbsolute: + if ( _archLayout->is64 ) + *((uint64_t*)fixupLoc) = target.absolute.value; + else + *((uint32_t*)fixupLoc) = (uint32_t)(target.absolute.value); + // don't record absolute targets for ASLR + // HACK: Split seg may have added a target. Remove it + _aslrTracker.remove(fixupLoc); + if ( (targetInfo.libOrdinal > 0) && (targetInfo.libOrdinal <= (int)(mh->dependentDylibCount())) ) { + _missingWeakImports[fixupLoc] = mh->dependentDylibLoadPath(targetInfo.libOrdinal - 1); + } + break; + default: + assert(0 && "unsupported ResolvedSymbolTarget kind in dyld cache"); + } + }; + + // build ImageArray for all dylibs in dyld cache + dyld3::closure::PathOverrides pathOverrides; + dyld3::closure::ClosureBuilder cb(dyld3::closure::kFirstDyldCacheImageNum, _fileSystem, cache, false, *_options.archs, pathOverrides, + dyld3::closure::ClosureBuilder::AtPath::none, false, nullptr, _options.platform, &handlers); + dyld3::Array dylibs(&dylibInfos[0], dylibInfos.size(), dylibInfos.size()); + const dyld3::Array aliasesArray(dylibAliases.data(), dylibAliases.size(), dylibAliases.size()); + _imageArray = cb.makeDyldCacheImageArray(_options.optimizeStubs, dylibs, aliasesArray); + if ( cb.diagnostics().hasError() ) { + _diagnostics.error("%s", cb.diagnostics().errorMessage().c_str()); + return; + } + +} + +static bool operator==(const dyld_cache_patchable_location& a, const dyld_cache_patchable_location& b) { + return a.cacheOffset == b.cacheOffset; +} + +void SharedCacheBuilder::addImageArray() +{ + // build trie of dylib paths + __block std::vector dylibEntrys; + _imageArray->forEachImage(^(const dyld3::closure::Image* image, bool& stop) { + dylibEntrys.push_back(DylibIndexTrie::Entry(image->path(), DylibIndex(image->imageNum()-1))); + image->forEachAlias(^(const char *aliasPath, bool &innerStop) { + dylibEntrys.push_back(DylibIndexTrie::Entry(aliasPath, DylibIndex(image->imageNum()-1))); + }); + }); + DylibIndexTrie dylibsTrie(dylibEntrys); + std::vector trieBytes; + dylibsTrie.emit(trieBytes); + while ( (trieBytes.size() % 4) != 0 ) + trieBytes.push_back(0); + + // build set of functions to never stub-eliminate because tools may need to override them + std::unordered_set alwaysGeneratePatch; + for (const char* const* p=_s_neverStubEliminateSymbols; *p != nullptr; ++p) + alwaysGeneratePatch.insert(*p); + + // Add the patches for the image array. + __block uint64_t numPatchImages = _imageArray->size(); + __block uint64_t numPatchExports = 0; + __block uint64_t numPatchLocations = 0; + __block uint64_t numPatchExportNameBytes = 0; + + auto needsPatch = [&](bool dylibNeedsPatching, const dyld3::MachOLoaded* mh, + CacheOffset offset) -> bool { + if (dylibNeedsPatching) + return true; + if (_dylibWeakExports.find({ mh, offset }) != _dylibWeakExports.end()) + return true; + const std::string& exportName = _exportsToName[offset]; + return alwaysGeneratePatch.find(exportName) != alwaysGeneratePatch.end(); + }; + + std::set alwaysPatchDylibs; + for (const char* const* d= _s_neverStubEliminateDylibs; *d != nullptr; ++d) + alwaysPatchDylibs.insert(*d); + + // First calculate how much space we need + const DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + cache->forEachImage(^(const mach_header* mh, const char* installName) { + const dyld3::MachOLoaded* ml = (const dyld3::MachOLoaded*)mh; + const std::set& dylibExports = _dylibToItsExports[ml]; + + // On a customer cache, only store patch locations for interposable dylibs and weak binding + bool dylibNeedsPatching = !_options.optimizeStubs || alwaysPatchDylibs.count(installName); + + uint64_t numDylibExports = 0; + for (CacheOffset exportCacheOffset : dylibExports) { + if (!needsPatch(dylibNeedsPatching, ml, exportCacheOffset)) + continue; + std::vector& uses = _exportsToUses[exportCacheOffset]; + uses.erase(std::unique(uses.begin(), uses.end()), uses.end()); + numPatchLocations += uses.size(); + + std::string exportName = _exportsToName[exportCacheOffset]; + numPatchExportNameBytes += exportName.size() + 1; + } + numPatchExports += numDylibExports; + }); + + // Now reserve the space + __block std::vector patchImages; + __block std::vector patchExports; + __block std::vector patchLocations; + __block std::vector patchExportNames; + + patchImages.reserve(numPatchImages); + patchExports.reserve(numPatchExports); + patchLocations.reserve(numPatchLocations); + patchExportNames.reserve(numPatchExportNameBytes); + + // And now fill it with the patch data + cache->forEachImage(^(const mach_header* mh, const char* installName) { + const dyld3::MachOLoaded* ml = (const dyld3::MachOLoaded*)mh; + const std::set& dylibExports = _dylibToItsExports[ml]; + + // On a customer cache, only store patch locations for interposable dylibs and weak binding + bool dylibNeedsPatching = !_options.optimizeStubs || alwaysPatchDylibs.count(installName); + + // Add the patch image which points in to the exports + dyld_cache_image_patches patchImage; + patchImage.patchExportsStartIndex = (uint32_t)patchExports.size(); + patchImage.patchExportsCount = 0; + + // Then add each export which points to a list of locations and a name + for (CacheOffset exportCacheOffset : dylibExports) { + if (!needsPatch(dylibNeedsPatching, ml, exportCacheOffset)) + continue; + ++patchImage.patchExportsCount; + std::vector& uses = _exportsToUses[exportCacheOffset]; + + dyld_cache_patchable_export cacheExport; + cacheExport.cacheOffsetOfImpl = (uint32_t)exportCacheOffset; + cacheExport.patchLocationsStartIndex = (uint32_t)patchLocations.size(); + cacheExport.patchLocationsCount = (uint32_t)uses.size(); + cacheExport.exportNameOffset = (uint32_t)patchExportNames.size(); + patchExports.push_back(cacheExport); + + // Now add the list of locations. + patchLocations.insert(patchLocations.end(), uses.begin(), uses.end()); + + // And add the export name + const std::string& exportName = _exportsToName[exportCacheOffset]; + patchExportNames.insert(patchExportNames.end(), &exportName[0], &exportName[0] + exportName.size() + 1); + } + patchImages.push_back(patchImage); + }); + + while ( (patchExportNames.size() % 4) != 0 ) + patchExportNames.push_back('\0'); + + uint64_t patchInfoSize = sizeof(dyld_cache_patch_info); + patchInfoSize += sizeof(dyld_cache_image_patches) * patchImages.size(); + patchInfoSize += sizeof(dyld_cache_patchable_export) * patchExports.size(); + patchInfoSize += sizeof(dyld_cache_patchable_location) * patchLocations.size(); + patchInfoSize += patchExportNames.size(); + + // check for fit + uint64_t imageArraySize = _imageArray->size(); + size_t freeSpace = _readOnlyRegion.bufferSize - _readOnlyRegion.sizeInUse; + if ( (imageArraySize+trieBytes.size()+patchInfoSize) > freeSpace ) { + _diagnostics.error("cache buffer too small to hold ImageArray and Trie (buffer size=%lldMB, imageArray size=%lldMB, trie size=%luKB, patch size=%lluKB, free space=%ldMB)", + _allocatedBufferSize/1024/1024, imageArraySize/1024/1024, trieBytes.size()/1024, patchInfoSize/1024, freeSpace/1024/1024); + return; + } + + // copy into cache and update header + DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; + dyldCache->header.dylibsImageArrayAddr = _readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse; + dyldCache->header.dylibsImageArraySize = imageArraySize; + dyldCache->header.dylibsTrieAddr = dyldCache->header.dylibsImageArrayAddr + imageArraySize; + dyldCache->header.dylibsTrieSize = trieBytes.size(); + ::memcpy(_readOnlyRegion.buffer + _readOnlyRegion.sizeInUse, _imageArray, imageArraySize); + ::memcpy(_readOnlyRegion.buffer + _readOnlyRegion.sizeInUse + imageArraySize, &trieBytes[0], trieBytes.size()); + + // Also write out the patch info + dyldCache->header.patchInfoAddr = dyldCache->header.dylibsTrieAddr + dyldCache->header.dylibsTrieSize; + dyldCache->header.patchInfoSize = patchInfoSize; + dyld_cache_patch_info patchInfo; + patchInfo.patchTableArrayAddr = dyldCache->header.patchInfoAddr + sizeof(dyld_cache_patch_info); + patchInfo.patchTableArrayCount = patchImages.size(); + patchInfo.patchExportArrayAddr = patchInfo.patchTableArrayAddr + (patchInfo.patchTableArrayCount * sizeof(dyld_cache_image_patches)); + patchInfo.patchExportArrayCount = patchExports.size(); + patchInfo.patchLocationArrayAddr = patchInfo.patchExportArrayAddr + (patchInfo.patchExportArrayCount * sizeof(dyld_cache_patchable_export)); + patchInfo.patchLocationArrayCount = patchLocations.size(); + patchInfo.patchExportNamesAddr = patchInfo.patchLocationArrayAddr + (patchInfo.patchLocationArrayCount * sizeof(dyld_cache_patchable_location)); + patchInfo.patchExportNamesSize = patchExportNames.size(); + ::memcpy(_readOnlyRegion.buffer + dyldCache->header.patchInfoAddr - _readOnlyRegion.unslidLoadAddress, + &patchInfo, sizeof(dyld_cache_patch_info)); + ::memcpy(_readOnlyRegion.buffer + patchInfo.patchTableArrayAddr - _readOnlyRegion.unslidLoadAddress, + &patchImages[0], sizeof(patchImages[0]) * patchImages.size()); + ::memcpy(_readOnlyRegion.buffer + patchInfo.patchExportArrayAddr - _readOnlyRegion.unslidLoadAddress, + &patchExports[0], sizeof(patchExports[0]) * patchExports.size()); + ::memcpy(_readOnlyRegion.buffer + patchInfo.patchLocationArrayAddr - _readOnlyRegion.unslidLoadAddress, + &patchLocations[0], sizeof(patchLocations[0]) * patchLocations.size()); + ::memcpy(_readOnlyRegion.buffer + patchInfo.patchExportNamesAddr - _readOnlyRegion.unslidLoadAddress, + &patchExportNames[0], patchExportNames.size()); + + _readOnlyRegion.sizeInUse += align(imageArraySize+trieBytes.size()+patchInfoSize,14); + + // Free the underlying image array buffer + _imageArray->deallocate(); +} + +void SharedCacheBuilder::addOtherImageArray(const std::vector& otherDylibsAndBundles, std::vector& overflowDylibs) +{ + DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + dyld3::closure::PathOverrides pathOverrides; + dyld3::closure::FileSystemNull nullFileSystem; + dyld3::closure::ClosureBuilder cb(dyld3::closure::kFirstOtherOSImageNum, nullFileSystem, cache, false, *_options.archs, pathOverrides, + dyld3::closure::ClosureBuilder::AtPath::none, false, nullptr, _options.platform); + + // make ImageArray for other dylibs and bundles + STACK_ALLOC_ARRAY(dyld3::closure::LoadedFileInfo, others, otherDylibsAndBundles.size() + overflowDylibs.size()); + for (const LoadedMachO& other : otherDylibsAndBundles) { + if ( !contains(other.loadedFileInfo.path, "staged_system_apps/") ) + others.push_back(other.loadedFileInfo); + } + + for (const LoadedMachO* dylib : overflowDylibs) { + if (dylib->mappedFile.mh->canHavePrecomputedDlopenClosure(dylib->mappedFile.runtimePath.c_str(), ^(const char*) {}) ) + others.push_back(dylib->loadedFileInfo); + } + + // Sort the others array by name so that it is deterministic + std::sort(others.begin(), others.end(), + [](const dyld3::closure::LoadedFileInfo& a, const dyld3::closure::LoadedFileInfo& b) { + // Sort mac before iOSMac + bool isIOSMacA = strncmp(a.path, "/System/iOSSupport/", 19) == 0; + bool isIOSMacB = strncmp(b.path, "/System/iOSSupport/", 19) == 0; + if (isIOSMacA != isIOSMacB) + return !isIOSMacA; + return strcmp(a.path, b.path) < 0; + }); + + const dyld3::closure::ImageArray* otherImageArray = cb.makeOtherDylibsImageArray(others, (uint32_t)_sortedDylibs.size()); + + // build trie of paths + __block std::vector otherEntrys; + otherImageArray->forEachImage(^(const dyld3::closure::Image* image, bool& stop) { + if ( !image->isInvalid() ) + otherEntrys.push_back(DylibIndexTrie::Entry(image->path(), DylibIndex(image->imageNum()))); + }); + DylibIndexTrie dylibsTrie(otherEntrys); + std::vector trieBytes; + dylibsTrie.emit(trieBytes); + while ( (trieBytes.size() % 4) != 0 ) + trieBytes.push_back(0); + + // check for fit + uint64_t imageArraySize = otherImageArray->size(); + size_t freeSpace = _readOnlyRegion.bufferSize - _readOnlyRegion.sizeInUse; + if ( imageArraySize+trieBytes.size() > freeSpace ) { + _diagnostics.error("cache buffer too small to hold ImageArray and Trie (buffer size=%lldMB, imageArray size=%lldMB, trie size=%luKB, free space=%ldMB)", + _allocatedBufferSize/1024/1024, imageArraySize/1024/1024, trieBytes.size()/1024, freeSpace/1024/1024); + return; + } + + // copy into cache and update header + DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; + dyldCache->header.otherImageArrayAddr = _readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse; + dyldCache->header.otherImageArraySize = imageArraySize; + dyldCache->header.otherTrieAddr = dyldCache->header.otherImageArrayAddr + imageArraySize; + dyldCache->header.otherTrieSize = trieBytes.size(); + ::memcpy(_readOnlyRegion.buffer + _readOnlyRegion.sizeInUse, otherImageArray, imageArraySize); + ::memcpy(_readOnlyRegion.buffer + _readOnlyRegion.sizeInUse + imageArraySize, &trieBytes[0], trieBytes.size()); + _readOnlyRegion.sizeInUse += align(imageArraySize+trieBytes.size(),14); + + // Free the underlying buffer + otherImageArray->deallocate(); +} + + +void SharedCacheBuilder::addClosures(const std::vector& osExecutables) +{ + const DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; + + __block std::vector osExecutablesDiags; + __block std::vector osExecutablesClosures; + osExecutablesDiags.resize(osExecutables.size()); + osExecutablesClosures.resize(osExecutables.size()); + + dispatch_apply(osExecutables.size(), DISPATCH_APPLY_AUTO, ^(size_t index) { + const LoadedMachO& loadedMachO = osExecutables[index]; + // don't pre-build closures for staged apps into dyld cache, since they won't run from that location + if ( startsWith(loadedMachO.mappedFile.runtimePath, "/private/var/staged_system_apps/") ) { + return; + } + dyld3::closure::PathOverrides pathOverrides; + dyld3::closure::ClosureBuilder builder(dyld3::closure::kFirstLaunchClosureImageNum, _fileSystem, dyldCache, false, *_options.archs, pathOverrides, + dyld3::closure::ClosureBuilder::AtPath::all, false, nullptr, _options.platform, nullptr); + bool issetuid = false; + if ( this->_options.platform == dyld3::Platform::macOS || dyld3::MachOFile::isSimulatorPlatform(this->_options.platform) ) + _fileSystem.fileExists(loadedMachO.loadedFileInfo.path, nullptr, nullptr, &issetuid, nullptr); + const dyld3::closure::LaunchClosure* mainClosure = builder.makeLaunchClosure(loadedMachO.loadedFileInfo, issetuid); + if ( builder.diagnostics().hasError() ) { + osExecutablesDiags[index].error("%s", builder.diagnostics().errorMessage().c_str()); + } + else { + assert(mainClosure != nullptr); + osExecutablesClosures[index] = mainClosure; + } + }); + + std::map closures; + for (uint64_t i = 0, e = osExecutables.size(); i != e; ++i) { + const LoadedMachO& loadedMachO = osExecutables[i]; + const Diagnostics& diag = osExecutablesDiags[i]; + if (diag.hasError()) { + if ( _options.verbose ) { + _diagnostics.warning("building closure for '%s': %s", loadedMachO.mappedFile.runtimePath.c_str(), diag.errorMessage().c_str()); + for (const std::string& warn : diag.warnings() ) + _diagnostics.warning("%s", warn.c_str()); + } + if ( loadedMachO.inputFile && (loadedMachO.inputFile->mustBeIncluded()) ) { + loadedMachO.inputFile->diag.error("%s", diag.errorMessage().c_str()); + } + } else { + // Note, a closure could be null here if it has a path we skip. + if (osExecutablesClosures[i] != nullptr) + closures[loadedMachO.mappedFile.runtimePath] = osExecutablesClosures[i]; + } + } + + osExecutablesDiags.clear(); + osExecutablesClosures.clear(); + + // preflight space needed + size_t closuresSpace = 0; + for (const auto& entry : closures) { + closuresSpace += entry.second->size(); + } + size_t freeSpace = _readOnlyRegion.bufferSize - _readOnlyRegion.sizeInUse; + if ( closuresSpace > freeSpace ) { + _diagnostics.error("cache buffer too small to hold all closures (buffer size=%lldMB, closures size=%ldMB, free space=%ldMB)", + _allocatedBufferSize/1024/1024, closuresSpace/1024/1024, freeSpace/1024/1024); + return; + } + DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + cache->header.progClosuresAddr = _readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse; + uint8_t* closuresBase = _readOnlyRegion.buffer + _readOnlyRegion.sizeInUse; + std::vector closureEntrys; + uint32_t currentClosureOffset = 0; + for (const auto& entry : closures) { + const dyld3::closure::LaunchClosure* closure = entry.second; + closureEntrys.push_back(DylibIndexTrie::Entry(entry.first, DylibIndex(currentClosureOffset))); + size_t size = closure->size(); + assert((size % 4) == 0); + memcpy(closuresBase+currentClosureOffset, closure, size); + currentClosureOffset += size; + freeSpace -= size; + closure->deallocate(); + } + cache->header.progClosuresSize = currentClosureOffset; + _readOnlyRegion.sizeInUse += currentClosureOffset; + freeSpace = _readOnlyRegion.bufferSize - _readOnlyRegion.sizeInUse; + // build trie of indexes into closures list + DylibIndexTrie closureTrie(closureEntrys); + std::vector trieBytes; + closureTrie.emit(trieBytes); + while ( (trieBytes.size() % 8) != 0 ) + trieBytes.push_back(0); + if ( trieBytes.size() > freeSpace ) { + _diagnostics.error("cache buffer too small to hold all closures trie (buffer size=%lldMB, trie size=%ldMB, free space=%ldMB)", + _allocatedBufferSize/1024/1024, trieBytes.size()/1024/1024, freeSpace/1024/1024); + return; + } + memcpy(_readOnlyRegion.buffer + _readOnlyRegion.sizeInUse, &trieBytes[0], trieBytes.size()); + cache->header.progClosuresTrieAddr = _readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse; + cache->header.progClosuresTrieSize = trieBytes.size(); + _readOnlyRegion.sizeInUse += trieBytes.size(); + _readOnlyRegion.sizeInUse = align(_readOnlyRegion.sizeInUse, 14); +} + + +bool SharedCacheBuilder::writeCache(void (^cacheSizeCallback)(uint64_t size), bool (^copyCallback)(const uint8_t* src, uint64_t size, uint64_t dstOffset)) +{ + const dyld_cache_header* cacheHeader = (dyld_cache_header*)_readExecuteRegion.buffer; + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(_readExecuteRegion.buffer + cacheHeader->mappingOffset); + assert(_readExecuteRegion.sizeInUse == mappings[0].size); + assert(_readWriteRegion.sizeInUse == mappings[1].size); + assert(_readOnlyRegion.sizeInUse == mappings[2].size); + assert(_readExecuteRegion.cacheFileOffset == mappings[0].fileOffset); + assert(_readWriteRegion.cacheFileOffset == mappings[1].fileOffset); + assert(_readOnlyRegion.cacheFileOffset == mappings[2].fileOffset); + assert(_codeSignatureRegion.sizeInUse == cacheHeader->codeSignatureSize); + assert(cacheHeader->codeSignatureOffset == mappings[2].fileOffset+_readOnlyRegion.sizeInUse+_localSymbolsRegion.sizeInUse); + cacheSizeCallback(_readExecuteRegion.sizeInUse+_readWriteRegion.sizeInUse+_readOnlyRegion.sizeInUse+_localSymbolsRegion.sizeInUse+_codeSignatureRegion.sizeInUse); + bool fullyWritten = copyCallback(_readExecuteRegion.buffer, _readExecuteRegion.sizeInUse, mappings[0].fileOffset); + fullyWritten &= copyCallback(_readWriteRegion.buffer, _readWriteRegion.sizeInUse, mappings[1].fileOffset); + fullyWritten &= copyCallback(_readOnlyRegion.buffer, _readOnlyRegion.sizeInUse, mappings[2].fileOffset); + if ( _localSymbolsRegion.sizeInUse != 0 ) { + assert(cacheHeader->localSymbolsOffset == mappings[2].fileOffset+_readOnlyRegion.sizeInUse); + fullyWritten &= copyCallback(_localSymbolsRegion.buffer, _localSymbolsRegion.sizeInUse, cacheHeader->localSymbolsOffset); + } + fullyWritten &= copyCallback(_codeSignatureRegion.buffer, _codeSignatureRegion.sizeInUse, cacheHeader->codeSignatureOffset); + return fullyWritten; +} + + +void SharedCacheBuilder::writeFile(const std::string& path) +{ + std::string pathTemplate = path + "-XXXXXX"; + size_t templateLen = strlen(pathTemplate.c_str())+2; + BLOCK_ACCCESSIBLE_ARRAY(char, pathTemplateSpace, templateLen); + strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen); + int fd = mkstemp(pathTemplateSpace); + if ( fd != -1 ) { + auto cacheSizeCallback = ^(uint64_t size) { + // if making macOS dyld cache for current OS into standard location + if ( (_options.platform == dyld3::Platform::macOS) && startsWith(path, MACOSX_DYLD_SHARED_CACHE_DIR) ) { + // pin cache file to SSD on fusion drives + apfs_data_pin_location_t where = APFS_PIN_DATA_TO_MAIN; + ::fsctl(pathTemplateSpace, APFSIOC_PIN_DATA, &where, 0); + } + // set final cache file size (may help defragment file) + ::ftruncate(fd, size); + }; + auto copyCallback = ^(const uint8_t* src, uint64_t size, uint64_t dstOffset) { + uint64_t writtenSize = pwrite(fd, src, size, dstOffset); + return writtenSize == size; + }; + // TOCTOU: verify path is still a realpath (not changed) + char tempPath[MAXPATHLEN]; + if ( ::fcntl(fd, F_GETPATH, tempPath) == 0 ) { + size_t tempPathLen = strlen(tempPath); + if ( tempPathLen > 7 ) + tempPath[tempPathLen-7] = '\0'; // remove trailing -xxxxxx + if ( path != tempPath ) { + _diagnostics.error("output file path changed from: '%s' to: '%s'", path.c_str(), tempPath); + ::close(fd); + return; + } + } + else { + _diagnostics.error("unable to fcntl(fd, F_GETPATH) on output file"); + ::close(fd); + return; + } + bool fullyWritten = writeCache(cacheSizeCallback, copyCallback); + if ( fullyWritten ) { + ::fchmod(fd, S_IRUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "r--r--r--" + // TOCTOU: verify path is still a realpath (not changed) + char resolvedPath[PATH_MAX]; + ::realpath(path.c_str(), resolvedPath); + // Note: if the target cache file does not already exist, realpath() will return NULL, but still fill in the path buffer + if ( path != resolvedPath ) { + _diagnostics.error("output file path changed from: '%s' to: '%s'", path.c_str(), resolvedPath); + return; + } + if ( ::rename(pathTemplateSpace, path.c_str()) == 0) { + ::close(fd); + return; // success + } + } + else { + _diagnostics.error("could not write file %s", pathTemplateSpace); + } + ::close(fd); + ::unlink(pathTemplateSpace); + } + else { + _diagnostics.error("could not open file %s", pathTemplateSpace); + } +} + +void SharedCacheBuilder::writeBuffer(uint8_t*& buffer, uint64_t& bufferSize) { + auto cacheSizeCallback = ^(uint64_t size) { + buffer = (uint8_t*)malloc(size); + bufferSize = size; + }; + auto copyCallback = ^(const uint8_t* src, uint64_t size, uint64_t dstOffset) { + memcpy(buffer + dstOffset, src, size); + return true; + }; + bool fullyWritten = writeCache(cacheSizeCallback, copyCallback); + assert(fullyWritten); +} + +void SharedCacheBuilder::writeMapFile(const std::string& path) +{ + const DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + std::string mapContent = cache->mapFile(); + safeSave(mapContent.c_str(), mapContent.size(), path); +} + +std::string SharedCacheBuilder::getMapFileBuffer(const std::string& cacheDisposition) const +{ + const DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + return cache->generateJSONMap(cacheDisposition.c_str()); +} + +void SharedCacheBuilder::markPaddingInaccessible() +{ + // region between RX and RW + uint8_t* startPad1 = _readExecuteRegion.buffer+_readExecuteRegion.sizeInUse; + uint8_t* endPad1 = _readWriteRegion.buffer; + ::vm_protect(mach_task_self(), (vm_address_t)startPad1, endPad1-startPad1, false, 0); + + // region between RW and RO + uint8_t* startPad2 = _readWriteRegion.buffer+_readWriteRegion.sizeInUse; + uint8_t* endPad2 = _readOnlyRegion.buffer; + ::vm_protect(mach_task_self(), (vm_address_t)startPad2, endPad2-startPad2, false, 0); +} + + +void SharedCacheBuilder::forEachCacheDylib(void (^callback)(const std::string& path)) { + for (const DylibInfo& dylibInfo : _sortedDylibs) + callback(dylibInfo.runtimePath); +} + + +uint64_t SharedCacheBuilder::pathHash(const char* path) +{ + uint64_t sum = 0; + for (const char* s=path; *s != '\0'; ++s) + sum += sum*4 + *s; + return sum; +} + + +void SharedCacheBuilder::findDylibAndSegment(const void* contentPtr, std::string& foundDylibName, std::string& foundSegName) +{ + foundDylibName = "???"; + foundSegName = "???"; + uint64_t unslidVmAddr = ((uint8_t*)contentPtr - _readExecuteRegion.buffer) + _readExecuteRegion.unslidLoadAddress; + const DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + cache->forEachImage(^(const mach_header* mh, const char* installName) { + ((dyld3::MachOLoaded*)mh)->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool &stop) { + if ( (unslidVmAddr >= info.vmAddr) && (unslidVmAddr < (info.vmAddr+info.vmSize)) ) { + foundDylibName = installName; + foundSegName = info.segName; + stop = true; + } + }); + }); +} + + +void SharedCacheBuilder::fipsSign() +{ + // find libcorecrypto.dylib in cache being built + DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; + __block const dyld3::MachOLoaded* ml = nullptr; + dyldCache->forEachImage(^(const mach_header* mh, const char* installName) { + if ( strcmp(installName, "/usr/lib/system/libcorecrypto.dylib") == 0 ) + ml = (dyld3::MachOLoaded*)mh; + }); + if ( ml == nullptr ) { + _diagnostics.warning("Could not find libcorecrypto.dylib, skipping FIPS sealing"); + return; + } + + // find location in libcorecrypto.dylib to store hash of __text section + uint64_t hashStoreSize; + const void* hashStoreLocation = ml->findSectionContent("__TEXT", "__fips_hmacs", hashStoreSize); + if ( hashStoreLocation == nullptr ) { + _diagnostics.warning("Could not find __TEXT/__fips_hmacs section in libcorecrypto.dylib, skipping FIPS sealing"); + return; + } + if ( hashStoreSize != 32 ) { + _diagnostics.warning("__TEXT/__fips_hmacs section in libcorecrypto.dylib is not 32 bytes in size, skipping FIPS sealing"); + return; + } + + // compute hmac hash of __text section + uint64_t textSize; + const void* textLocation = ml->findSectionContent("__TEXT", "__text", textSize); + if ( textLocation == nullptr ) { + _diagnostics.warning("Could not find __TEXT/__text section in libcorecrypto.dylib, skipping FIPS sealing"); + return; + } + unsigned char hmac_key = 0; + CCHmac(kCCHmacAlgSHA256, &hmac_key, 1, textLocation, textSize, (void*)hashStoreLocation); // store hash directly into hashStoreLocation +} + +void SharedCacheBuilder::codeSign() +{ + uint8_t dscHashType; + uint8_t dscHashSize; + uint32_t dscDigestFormat; + bool agile = false; + + // select which codesigning hash + switch (_options.codeSigningDigestMode) { + case DyldSharedCache::Agile: + agile = true; + // Fall through to SHA1, because the main code directory remains SHA1 for compatibility. + [[clang::fallthrough]]; + case DyldSharedCache::SHA1only: +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + dscHashType = CS_HASHTYPE_SHA1; + dscHashSize = CS_HASH_SIZE_SHA1; + dscDigestFormat = kCCDigestSHA1; +#pragma clang diagnostic pop + break; + case DyldSharedCache::SHA256only: + dscHashType = CS_HASHTYPE_SHA256; + dscHashSize = CS_HASH_SIZE_SHA256; + dscDigestFormat = kCCDigestSHA256; + break; + default: + _diagnostics.error("codeSigningDigestMode has unknown, unexpected value %d, bailing out.", + _options.codeSigningDigestMode); + return; + } + + std::string cacheIdentifier = "com.apple.dyld.cache."; + cacheIdentifier += _options.archs->name(); + if ( _options.dylibsRemovedDuringMastering ) { + if ( _options.optimizeStubs ) + cacheIdentifier += ".release"; + else + cacheIdentifier += ".development"; + } + // get pointers into shared cache buffer + size_t inBbufferSize = _readExecuteRegion.sizeInUse+_readWriteRegion.sizeInUse+_readOnlyRegion.sizeInUse+_localSymbolsRegion.sizeInUse; + + const uint16_t pageSize = _archLayout->csPageSize; + + // layout code signature contents + uint32_t blobCount = agile ? 4 : 3; + size_t idSize = cacheIdentifier.size()+1; // +1 for terminating 0 + uint32_t slotCount = (uint32_t)((inBbufferSize + pageSize - 1) / pageSize); + uint32_t xSlotCount = CSSLOT_REQUIREMENTS; + size_t idOffset = offsetof(CS_CodeDirectory, end_withExecSeg); + size_t hashOffset = idOffset+idSize + dscHashSize*xSlotCount; + size_t hash256Offset = idOffset+idSize + CS_HASH_SIZE_SHA256*xSlotCount; + size_t cdSize = hashOffset + (slotCount * dscHashSize); + size_t cd256Size = agile ? hash256Offset + (slotCount * CS_HASH_SIZE_SHA256) : 0; + size_t reqsSize = 12; + size_t cmsSize = sizeof(CS_Blob); + size_t cdOffset = sizeof(CS_SuperBlob) + blobCount*sizeof(CS_BlobIndex); + size_t cd256Offset = cdOffset + cdSize; + size_t reqsOffset = cd256Offset + cd256Size; // equals cdOffset + cdSize if not agile + size_t cmsOffset = reqsOffset + reqsSize; + size_t sbSize = cmsOffset + cmsSize; + size_t sigSize = align(sbSize, 14); // keep whole cache 16KB aligned + + // allocate space for blob + vm_address_t codeSigAlloc; + if ( vm_allocate(mach_task_self(), &codeSigAlloc, sigSize, VM_FLAGS_ANYWHERE) != 0 ) { + _diagnostics.error("could not allocate code signature buffer"); + return; + } + _codeSignatureRegion.buffer = (uint8_t*)codeSigAlloc; + _codeSignatureRegion.bufferSize = sigSize; + _codeSignatureRegion.sizeInUse = sigSize; + + // create overall code signature which is a superblob + CS_SuperBlob* sb = reinterpret_cast(_codeSignatureRegion.buffer); + sb->magic = htonl(CSMAGIC_EMBEDDED_SIGNATURE); + sb->length = htonl(sbSize); + sb->count = htonl(blobCount); + sb->index[0].type = htonl(CSSLOT_CODEDIRECTORY); + sb->index[0].offset = htonl(cdOffset); + sb->index[1].type = htonl(CSSLOT_REQUIREMENTS); + sb->index[1].offset = htonl(reqsOffset); + sb->index[2].type = htonl(CSSLOT_CMS_SIGNATURE); + sb->index[2].offset = htonl(cmsOffset); + if ( agile ) { + sb->index[3].type = htonl(CSSLOT_ALTERNATE_CODEDIRECTORIES + 0); + sb->index[3].offset = htonl(cd256Offset); + } + + // fill in empty requirements + CS_RequirementsBlob* reqs = (CS_RequirementsBlob*)(((char*)sb)+reqsOffset); + reqs->magic = htonl(CSMAGIC_REQUIREMENTS); + reqs->length = htonl(sizeof(CS_RequirementsBlob)); + reqs->data = 0; + + // initialize fixed fields of Code Directory + CS_CodeDirectory* cd = (CS_CodeDirectory*)(((char*)sb)+cdOffset); + cd->magic = htonl(CSMAGIC_CODEDIRECTORY); + cd->length = htonl(cdSize); + cd->version = htonl(0x20400); // supports exec segment + cd->flags = htonl(kSecCodeSignatureAdhoc); + cd->hashOffset = htonl(hashOffset); + cd->identOffset = htonl(idOffset); + cd->nSpecialSlots = htonl(xSlotCount); + cd->nCodeSlots = htonl(slotCount); + cd->codeLimit = htonl(inBbufferSize); + cd->hashSize = dscHashSize; + cd->hashType = dscHashType; + cd->platform = 0; // not platform binary + cd->pageSize = __builtin_ctz(pageSize); // log2(CS_PAGE_SIZE); + cd->spare2 = 0; // unused (must be zero) + cd->scatterOffset = 0; // not supported anymore + cd->teamOffset = 0; // no team ID + cd->spare3 = 0; // unused (must be zero) + cd->codeLimit64 = 0; // falls back to codeLimit + + // executable segment info + cd->execSegBase = htonll(_readExecuteRegion.cacheFileOffset); // base of TEXT segment + cd->execSegLimit = htonll(_readExecuteRegion.sizeInUse); // size of TEXT segment + cd->execSegFlags = 0; // not a main binary + + // initialize dynamic fields of Code Directory + strcpy((char*)cd + idOffset, cacheIdentifier.c_str()); + + // add special slot hashes + uint8_t* hashSlot = (uint8_t*)cd + hashOffset; + uint8_t* reqsHashSlot = &hashSlot[-CSSLOT_REQUIREMENTS*dscHashSize]; + CCDigest(dscDigestFormat, (uint8_t*)reqs, sizeof(CS_RequirementsBlob), reqsHashSlot); + + CS_CodeDirectory* cd256; + uint8_t* hash256Slot; + uint8_t* reqsHash256Slot; + if ( agile ) { + // Note that the assumption here is that the size up to the hashes is the same as for + // sha1 code directory, and that they come last, after everything else. + + cd256 = (CS_CodeDirectory*)(((char*)sb)+cd256Offset); + cd256->magic = htonl(CSMAGIC_CODEDIRECTORY); + cd256->length = htonl(cd256Size); + cd256->version = htonl(0x20400); // supports exec segment + cd256->flags = htonl(kSecCodeSignatureAdhoc); + cd256->hashOffset = htonl(hash256Offset); + cd256->identOffset = htonl(idOffset); + cd256->nSpecialSlots = htonl(xSlotCount); + cd256->nCodeSlots = htonl(slotCount); + cd256->codeLimit = htonl(inBbufferSize); + cd256->hashSize = CS_HASH_SIZE_SHA256; + cd256->hashType = CS_HASHTYPE_SHA256; + cd256->platform = 0; // not platform binary + cd256->pageSize = __builtin_ctz(pageSize); // log2(CS_PAGE_SIZE); + cd256->spare2 = 0; // unused (must be zero) + cd256->scatterOffset = 0; // not supported anymore + cd256->teamOffset = 0; // no team ID + cd256->spare3 = 0; // unused (must be zero) + cd256->codeLimit64 = 0; // falls back to codeLimit + + // executable segment info + cd256->execSegBase = cd->execSegBase; + cd256->execSegLimit = cd->execSegLimit; + cd256->execSegFlags = cd->execSegFlags; + + // initialize dynamic fields of Code Directory + strcpy((char*)cd256 + idOffset, cacheIdentifier.c_str()); + + // add special slot hashes + hash256Slot = (uint8_t*)cd256 + hash256Offset; + reqsHash256Slot = &hash256Slot[-CSSLOT_REQUIREMENTS*CS_HASH_SIZE_SHA256]; + CCDigest(kCCDigestSHA256, (uint8_t*)reqs, sizeof(CS_RequirementsBlob), reqsHash256Slot); + } + else { + cd256 = NULL; + hash256Slot = NULL; + reqsHash256Slot = NULL; + } + + // fill in empty CMS blob for ad-hoc signing + CS_Blob* cms = (CS_Blob*)(((char*)sb)+cmsOffset); + cms->magic = htonl(CSMAGIC_BLOBWRAPPER); + cms->length = htonl(sizeof(CS_Blob)); + + + // alter header of cache to record size and location of code signature + // do this *before* hashing each page + dyld_cache_header* cache = (dyld_cache_header*)_readExecuteRegion.buffer; + cache->codeSignatureOffset = inBbufferSize; + cache->codeSignatureSize = sigSize; + + const uint32_t rwSlotStart = (uint32_t)(_readExecuteRegion.sizeInUse / pageSize); + const uint32_t roSlotStart = (uint32_t)(rwSlotStart + _readWriteRegion.sizeInUse / pageSize); + const uint32_t localsSlotStart = (uint32_t)(roSlotStart + _readOnlyRegion.sizeInUse / pageSize); + auto codeSignPage = ^(size_t i) { + const uint8_t* code = nullptr; + // move to correct region + if ( i < rwSlotStart ) + code = _readExecuteRegion.buffer + (i * pageSize); + else if ( i >= rwSlotStart && i < roSlotStart ) + code = _readWriteRegion.buffer + ((i - rwSlotStart) * pageSize); + else if ( i >= roSlotStart && i < localsSlotStart ) + code = _readOnlyRegion.buffer + ((i - roSlotStart) * pageSize); + else + code = _localSymbolsRegion.buffer + ((i - localsSlotStart) * pageSize); + + CCDigest(dscDigestFormat, code, pageSize, hashSlot + (i * dscHashSize)); + + if ( agile ) { + CCDigest(kCCDigestSHA256, code, pageSize, hash256Slot + (i * CS_HASH_SIZE_SHA256)); + } + }; + + // compute hashes + dispatch_apply(slotCount, DISPATCH_APPLY_AUTO, ^(size_t i) { + codeSignPage(i); + }); + + // Now that we have a code signature, compute a cache UUID by hashing the code signature blob + { + uint8_t* uuidLoc = cache->uuid; + assert(uuid_is_null(uuidLoc)); + static_assert(offsetof(dyld_cache_header, uuid) / CS_PAGE_SIZE_4K == 0, "uuid is expected in the first page of the cache"); + uint8_t fullDigest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256((const void*)cd, (unsigned)cdSize, fullDigest); + memcpy(uuidLoc, fullDigest, 16); + // uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats + uuidLoc[6] = ( uuidLoc[6] & 0x0F ) | ( 3 << 4 ); + uuidLoc[8] = ( uuidLoc[8] & 0x3F ) | 0x80; + + // Now codesign page 0 again, because we modified it by setting uuid in header + codeSignPage(0); + } + + // hash of entire code directory (cdHash) uses same hash as each page + uint8_t fullCdHash[dscHashSize]; + CCDigest(dscDigestFormat, (const uint8_t*)cd, cdSize, fullCdHash); + // Note: cdHash is defined as first 20 bytes of hash + memcpy(_cdHashFirst, fullCdHash, 20); + if ( agile ) { + uint8_t fullCdHash256[CS_HASH_SIZE_SHA256]; + CCDigest(kCCDigestSHA256, (const uint8_t*)cd256, cd256Size, fullCdHash256); + // Note: cdHash is defined as first 20 bytes of hash, even for sha256 + memcpy(_cdHashSecond, fullCdHash256, 20); + } + else { + memset(_cdHashSecond, 0, 20); + } +} + +const bool SharedCacheBuilder::agileSignature() +{ + return _options.codeSigningDigestMode == DyldSharedCache::Agile; +} + +static const std::string cdHash(uint8_t hash[20]) +{ + char buff[48]; + for (int i = 0; i < 20; ++i) + sprintf(&buff[2*i], "%2.2x", hash[i]); + return buff; +} + +const std::string SharedCacheBuilder::cdHashFirst() +{ + return cdHash(_cdHashFirst); +} + +const std::string SharedCacheBuilder::cdHashSecond() +{ + return cdHash(_cdHashSecond); +} + +const std::string SharedCacheBuilder::uuid() const +{ + dyld_cache_header* cache = (dyld_cache_header*)_readExecuteRegion.buffer; + uuid_string_t uuidStr; + uuid_unparse(cache->uuid, uuidStr); + return uuidStr; +} + + + +template +bool SharedCacheBuilder::makeRebaseChainV2(uint8_t* pageContent, uint16_t lastLocationOffset, uint16_t offset, const dyld_cache_slide_info2* info) +{ + typedef typename P::uint_t pint_t; + + const pint_t deltaMask = (pint_t)(info->delta_mask); + const pint_t valueMask = ~deltaMask; + const pint_t valueAdd = (pint_t)(info->value_add); + const unsigned deltaShift = __builtin_ctzll(deltaMask) - 2; + const uint32_t maxDelta = (uint32_t)(deltaMask >> deltaShift); + + pint_t* lastLoc = (pint_t*)&pageContent[lastLocationOffset+0]; + pint_t lastValue = (pint_t)P::getP(*lastLoc); + if ( (lastValue - valueAdd) & deltaMask ) { + std::string dylibName; + std::string segName; + findDylibAndSegment((void*)pageContent, dylibName, segName); + _diagnostics.error("rebase pointer (0x%0lX) does not point within cache. lastOffset=0x%04X, seg=%s, dylib=%s\n", + (long)lastValue, lastLocationOffset, segName.c_str(), dylibName.c_str()); + return false; + } + if ( offset <= (lastLocationOffset+maxDelta) ) { + // previous location in range, make link from it + // encode this location into last value + pint_t delta = offset - lastLocationOffset; + pint_t newLastValue = ((lastValue - valueAdd) & valueMask) | (delta << deltaShift); + //warning(" add chain: delta = %d, lastOffset=0x%03X, offset=0x%03X, org value=0x%08lX, new value=0x%08lX", + // offset - lastLocationOffset, lastLocationOffset, offset, (long)lastValue, (long)newLastValue); + uint8_t highByte; + if ( _aslrTracker.hasHigh8(lastLoc, &highByte) ) { + uint64_t tbi = (uint64_t)highByte << 56; + newLastValue |= tbi; + } + P::setP(*lastLoc, newLastValue); + return true; + } + //fprintf(stderr, " too big delta = %d, lastOffset=0x%03X, offset=0x%03X\n", offset - lastLocationOffset, lastLocationOffset, offset); + + // distance between rebase locations is too far + // see if we can make a chain from non-rebase locations + uint16_t nonRebaseLocationOffsets[1024]; + unsigned nrIndex = 0; + for (uint16_t i = lastLocationOffset; i < offset-maxDelta; ) { + nonRebaseLocationOffsets[nrIndex] = 0; + for (int j=maxDelta; j > 0; j -= 4) { + pint_t value = (pint_t)P::getP(*(pint_t*)&pageContent[i+j]); + if ( value == 0 ) { + // Steal values of 0 to be used in the rebase chain + nonRebaseLocationOffsets[nrIndex] = i+j; + break; + } + } + if ( nonRebaseLocationOffsets[nrIndex] == 0 ) { + lastValue = (pint_t)P::getP(*lastLoc); + pint_t newValue = ((lastValue - valueAdd) & valueMask); + //warning(" no way to make non-rebase delta chain, terminate off=0x%03X, old value=0x%08lX, new value=0x%08lX", lastLocationOffset, (long)value, (long)newValue); + P::setP(*lastLoc, newValue); + return false; + } + i = nonRebaseLocationOffsets[nrIndex]; + ++nrIndex; + } + + // we can make chain. go back and add each non-rebase location to chain + uint16_t prevOffset = lastLocationOffset; + pint_t* prevLoc = (pint_t*)&pageContent[prevOffset]; + for (unsigned n=0; n < nrIndex; ++n) { + uint16_t nOffset = nonRebaseLocationOffsets[n]; + assert(nOffset != 0); + pint_t* nLoc = (pint_t*)&pageContent[nOffset]; + uint32_t delta2 = nOffset - prevOffset; + pint_t value = (pint_t)P::getP(*prevLoc); + pint_t newValue; + if ( value == 0 ) + newValue = (delta2 << deltaShift); + else + newValue = ((value - valueAdd) & valueMask) | (delta2 << deltaShift); + //warning(" non-rebase delta = %d, to off=0x%03X, old value=0x%08lX, new value=0x%08lX", delta2, nOffset, (long)value, (long)newValue); + P::setP(*prevLoc, newValue); + prevOffset = nOffset; + prevLoc = nLoc; + } + uint32_t delta3 = offset - prevOffset; + pint_t value = (pint_t)P::getP(*prevLoc); + pint_t newValue; + if ( value == 0 ) + newValue = (delta3 << deltaShift); + else + newValue = ((value - valueAdd) & valueMask) | (delta3 << deltaShift); + //warning(" non-rebase delta = %d, to off=0x%03X, old value=0x%08lX, new value=0x%08lX", delta3, offset, (long)value, (long)newValue); + P::setP(*prevLoc, newValue); + + return true; +} + + +template +void SharedCacheBuilder::addPageStartsV2(uint8_t* pageContent, const bool bitmap[], const dyld_cache_slide_info2* info, + std::vector& pageStarts, std::vector& pageExtras) +{ + typedef typename P::uint_t pint_t; + + const pint_t deltaMask = (pint_t)(info->delta_mask); + const pint_t valueMask = ~deltaMask; + const uint32_t pageSize = info->page_size; + const pint_t valueAdd = (pint_t)(info->value_add); + + uint16_t startValue = DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE; + uint16_t lastLocationOffset = 0xFFFF; + for(uint32_t i=0; i < pageSize/4; ++i) { + unsigned offset = i*4; + if ( bitmap[i] ) { + if ( startValue == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE ) { + // found first rebase location in page + startValue = i; + } + else if ( !makeRebaseChainV2

(pageContent, lastLocationOffset, offset, info) ) { + // can't record all rebasings in one chain + if ( (startValue & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) == 0 ) { + // switch page_start to "extras" which is a list of chain starts + unsigned indexInExtras = (unsigned)pageExtras.size(); + if ( indexInExtras > 0x3FFF ) { + _diagnostics.error("rebase overflow in v2 page extras"); + return; + } + pageExtras.push_back(startValue); + startValue = indexInExtras | DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA; + } + pageExtras.push_back(i); + } + lastLocationOffset = offset; + } + } + if ( lastLocationOffset != 0xFFFF ) { + // mark end of chain + pint_t* lastLoc = (pint_t*)&pageContent[lastLocationOffset]; + pint_t lastValue = (pint_t)P::getP(*lastLoc); + pint_t newValue = ((lastValue - valueAdd) & valueMask); + P::setP(*lastLoc, newValue); + } + if ( startValue & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA ) { + // add end bit to extras + pageExtras.back() |= DYLD_CACHE_SLIDE_PAGE_ATTR_END; + } + pageStarts.push_back(startValue); +} + +template +void SharedCacheBuilder::writeSlideInfoV2(const bool bitmap[], unsigned dataPageCount) +{ + typedef typename P::uint_t pint_t; + typedef typename P::E E; + const uint32_t pageSize = 4096; + + // fill in fixed info + assert(_slideInfoFileOffset != 0); + dyld_cache_slide_info2* info = (dyld_cache_slide_info2*)_readOnlyRegion.buffer; + info->version = 2; + info->page_size = pageSize; + info->delta_mask = _archLayout->pointerDeltaMask; + info->value_add = _archLayout->useValueAdd ? _archLayout->sharedMemoryStart : 0; + + // set page starts and extras for each page + std::vector pageStarts; + std::vector pageExtras; + pageStarts.reserve(dataPageCount); + uint8_t* pageContent = _readWriteRegion.buffer; + const bool* bitmapForPage = bitmap; + for (unsigned i=0; i < dataPageCount; ++i) { + //warning("page[%d]", i); + addPageStartsV2

(pageContent, bitmapForPage, info, pageStarts, pageExtras); + if ( _diagnostics.hasError() ) { + return; + } + pageContent += pageSize; + bitmapForPage += (sizeof(bool)*(pageSize/4)); + } + + // fill in computed info + info->page_starts_offset = sizeof(dyld_cache_slide_info2); + info->page_starts_count = (unsigned)pageStarts.size(); + info->page_extras_offset = (unsigned)(sizeof(dyld_cache_slide_info2)+pageStarts.size()*sizeof(uint16_t)); + info->page_extras_count = (unsigned)pageExtras.size(); + uint16_t* pageStartsBuffer = (uint16_t*)((char*)info + info->page_starts_offset); + uint16_t* pageExtrasBuffer = (uint16_t*)((char*)info + info->page_extras_offset); + for (unsigned i=0; i < pageStarts.size(); ++i) + pageStartsBuffer[i] = pageStarts[i]; + for (unsigned i=0; i < pageExtras.size(); ++i) + pageExtrasBuffer[i] = pageExtras[i]; + // update header with final size + uint64_t slideInfoSize = align(info->page_extras_offset + pageExtras.size()*sizeof(uint16_t), _archLayout->sharedRegionAlignP2); + if ( slideInfoSize > _slideInfoBufferSizeAllocated ) { + _diagnostics.error("kernel slide info overflow buffer"); + } + ((dyld_cache_header*)_readExecuteRegion.buffer)->slideInfoSize = slideInfoSize; + //fprintf(stderr, "pageCount=%u, page_starts_count=%lu, page_extras_count=%lu\n", dataPageCount, pageStarts.size(), pageExtras.size()); +} + +#if SUPPORT_ARCH_arm64_32 || SUPPORT_ARCH_armv7k +// fits in to int16_t +static bool smallValue(uint64_t value) +{ + uint32_t high = (value & 0xFFFF8000); + return (high == 0) || (high == 0xFFFF8000); +} + +template +bool SharedCacheBuilder::makeRebaseChainV4(uint8_t* pageContent, uint16_t lastLocationOffset, uint16_t offset, const dyld_cache_slide_info4* info) +{ + typedef typename P::uint_t pint_t; + + const pint_t deltaMask = (pint_t)(info->delta_mask); + const pint_t valueMask = ~deltaMask; + const pint_t valueAdd = (pint_t)(info->value_add); + const unsigned deltaShift = __builtin_ctzll(deltaMask) - 2; + const uint32_t maxDelta = (uint32_t)(deltaMask >> deltaShift); + + pint_t* lastLoc = (pint_t*)&pageContent[lastLocationOffset+0]; + pint_t lastValue = (pint_t)P::getP(*lastLoc); + if ( (lastValue - valueAdd) & deltaMask ) { + std::string dylibName; + std::string segName; + findDylibAndSegment((void*)pageContent, dylibName, segName); + _diagnostics.error("rebase pointer does not point within cache. lastOffset=0x%04X, seg=%s, dylib=%s\n", + lastLocationOffset, segName.c_str(), dylibName.c_str()); + return false; + } + if ( offset <= (lastLocationOffset+maxDelta) ) { + // previous location in range, make link from it + // encode this location into last value + pint_t delta = offset - lastLocationOffset; + pint_t newLastValue = ((lastValue - valueAdd) & valueMask) | (delta << deltaShift); + //warning(" add chain: delta = %d, lastOffset=0x%03X, offset=0x%03X, org value=0x%08lX, new value=0x%08lX", + // offset - lastLocationOffset, lastLocationOffset, offset, (long)lastValue, (long)newLastValue); + P::setP(*lastLoc, newLastValue); + return true; + } + //fprintf(stderr, " too big delta = %d, lastOffset=0x%03X, offset=0x%03X\n", offset - lastLocationOffset, lastLocationOffset, offset); + + // distance between rebase locations is too far + // see if we can make a chain from non-rebase locations + uint16_t nonRebaseLocationOffsets[1024]; + unsigned nrIndex = 0; + for (uint16_t i = lastLocationOffset; i < offset-maxDelta; ) { + nonRebaseLocationOffsets[nrIndex] = 0; + for (int j=maxDelta; j > 0; j -= 4) { + pint_t value = (pint_t)P::getP(*(pint_t*)&pageContent[i+j]); + if ( smallValue(value) ) { + // Steal values of 0 to be used in the rebase chain + nonRebaseLocationOffsets[nrIndex] = i+j; + break; + } + } + if ( nonRebaseLocationOffsets[nrIndex] == 0 ) { + lastValue = (pint_t)P::getP(*lastLoc); + pint_t newValue = ((lastValue - valueAdd) & valueMask); + //fprintf(stderr, " no way to make non-rebase delta chain, terminate off=0x%03X, old value=0x%08lX, new value=0x%08lX\n", + // lastLocationOffset, (long)lastValue, (long)newValue); + P::setP(*lastLoc, newValue); + return false; + } + i = nonRebaseLocationOffsets[nrIndex]; + ++nrIndex; + } + + // we can make chain. go back and add each non-rebase location to chain + uint16_t prevOffset = lastLocationOffset; + pint_t* prevLoc = (pint_t*)&pageContent[prevOffset]; + for (unsigned n=0; n < nrIndex; ++n) { + uint16_t nOffset = nonRebaseLocationOffsets[n]; + assert(nOffset != 0); + pint_t* nLoc = (pint_t*)&pageContent[nOffset]; + uint32_t delta2 = nOffset - prevOffset; + pint_t value = (pint_t)P::getP(*prevLoc); + pint_t newValue; + if ( smallValue(value) ) + newValue = (value & valueMask) | (delta2 << deltaShift); + else + newValue = ((value - valueAdd) & valueMask) | (delta2 << deltaShift); + //warning(" non-rebase delta = %d, to off=0x%03X, old value=0x%08lX, new value=0x%08lX", delta2, nOffset, (long)value, (long)newValue); + P::setP(*prevLoc, newValue); + prevOffset = nOffset; + prevLoc = nLoc; + } + uint32_t delta3 = offset - prevOffset; + pint_t value = (pint_t)P::getP(*prevLoc); + pint_t newValue; + if ( smallValue(value) ) + newValue = (value & valueMask) | (delta3 << deltaShift); + else + newValue = ((value - valueAdd) & valueMask) | (delta3 << deltaShift); + //warning(" non-rebase delta = %d, to off=0x%03X, old value=0x%08lX, new value=0x%08lX", delta3, offset, (long)value, (long)newValue); + P::setP(*prevLoc, newValue); + + return true; +} + + +template +void SharedCacheBuilder::addPageStartsV4(uint8_t* pageContent, const bool bitmap[], const dyld_cache_slide_info4* info, + std::vector& pageStarts, std::vector& pageExtras) +{ + typedef typename P::uint_t pint_t; + + const pint_t deltaMask = (pint_t)(info->delta_mask); + const pint_t valueMask = ~deltaMask; + const uint32_t pageSize = info->page_size; + const pint_t valueAdd = (pint_t)(info->value_add); + + uint16_t startValue = DYLD_CACHE_SLIDE4_PAGE_NO_REBASE; + uint16_t lastLocationOffset = 0xFFFF; + for(uint32_t i=0; i < pageSize/4; ++i) { + unsigned offset = i*4; + if ( bitmap[i] ) { + if ( startValue == DYLD_CACHE_SLIDE4_PAGE_NO_REBASE ) { + // found first rebase location in page + startValue = i; + } + else if ( !makeRebaseChainV4

(pageContent, lastLocationOffset, offset, info) ) { + // can't record all rebasings in one chain + if ( (startValue & DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA) == 0 ) { + // switch page_start to "extras" which is a list of chain starts + unsigned indexInExtras = (unsigned)pageExtras.size(); + if ( indexInExtras >= DYLD_CACHE_SLIDE4_PAGE_INDEX ) { + _diagnostics.error("rebase overflow in v4 page extras"); + return; + } + pageExtras.push_back(startValue); + startValue = indexInExtras | DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA; + } + pageExtras.push_back(i); + } + lastLocationOffset = offset; + } + } + if ( lastLocationOffset != 0xFFFF ) { + // mark end of chain + pint_t* lastLoc = (pint_t*)&pageContent[lastLocationOffset]; + pint_t lastValue = (pint_t)P::getP(*lastLoc); + pint_t newValue = ((lastValue - valueAdd) & valueMask); + P::setP(*lastLoc, newValue); + } + if ( startValue & DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA ) { + // add end bit to extras + pageExtras.back() |= DYLD_CACHE_SLIDE4_PAGE_EXTRA_END; + } + pageStarts.push_back(startValue); +} + + + +template +void SharedCacheBuilder::writeSlideInfoV4(const bool bitmap[], unsigned dataPageCount) +{ + typedef typename P::uint_t pint_t; + typedef typename P::E E; + const uint32_t pageSize = 4096; + + // fill in fixed info + assert(_slideInfoFileOffset != 0); + dyld_cache_slide_info4* info = (dyld_cache_slide_info4*)_readOnlyRegion.buffer; + info->version = 4; + info->page_size = pageSize; + info->delta_mask = _archLayout->pointerDeltaMask; + info->value_add = info->value_add = _archLayout->useValueAdd ? _archLayout->sharedMemoryStart : 0; + + // set page starts and extras for each page + std::vector pageStarts; + std::vector pageExtras; + pageStarts.reserve(dataPageCount); + uint8_t* pageContent = _readWriteRegion.buffer; + const bool* bitmapForPage = bitmap; + for (unsigned i=0; i < dataPageCount; ++i) { + addPageStartsV4

(pageContent, bitmapForPage, info, pageStarts, pageExtras); + if ( _diagnostics.hasError() ) { + return; + } + pageContent += pageSize; + bitmapForPage += (sizeof(bool)*(pageSize/4)); + } + // fill in computed info + info->page_starts_offset = sizeof(dyld_cache_slide_info4); + info->page_starts_count = (unsigned)pageStarts.size(); + info->page_extras_offset = (unsigned)(sizeof(dyld_cache_slide_info4)+pageStarts.size()*sizeof(uint16_t)); + info->page_extras_count = (unsigned)pageExtras.size(); + uint16_t* pageStartsBuffer = (uint16_t*)((char*)info + info->page_starts_offset); + uint16_t* pageExtrasBuffer = (uint16_t*)((char*)info + info->page_extras_offset); + for (unsigned i=0; i < pageStarts.size(); ++i) + pageStartsBuffer[i] = pageStarts[i]; + for (unsigned i=0; i < pageExtras.size(); ++i) + pageExtrasBuffer[i] = pageExtras[i]; + // update header with final size + uint64_t slideInfoSize = align(info->page_extras_offset + pageExtras.size()*sizeof(uint16_t), _archLayout->sharedRegionAlignP2); + if ( slideInfoSize > _slideInfoBufferSizeAllocated ) { + _diagnostics.error("kernel slide info v4 overflow buffer, need %lldKB, have room for %lldKB", slideInfoSize, _slideInfoBufferSizeAllocated); + } + ((dyld_cache_header*)_readExecuteRegion.buffer)->slideInfoSize = slideInfoSize; + //fprintf(stderr, "pageCount=%u, page_starts_count=%lu, page_extras_count=%lu\n", dataPageCount, pageStarts.size(), pageExtras.size()); +} +#endif + +/* +void CacheBuilder::writeSlideInfoV1() +{ + // build one 128-byte bitmap per page (4096) of DATA + uint8_t* const dataStart = (uint8_t*)_buffer.get() + regions[1].fileOffset; + uint8_t* const dataEnd = dataStart + regions[1].size; + const long bitmapSize = (dataEnd - dataStart)/(4*8); + uint8_t* bitmap = (uint8_t*)calloc(bitmapSize, 1); + for (void* p : _pointersForASLR) { + if ( (p < dataStart) || ( p > dataEnd) ) + terminate("DATA pointer for sliding, out of range\n"); + long offset = (long)((uint8_t*)p - dataStart); + if ( (offset % 4) != 0 ) + terminate("pointer not 4-byte aligned in DATA offset 0x%08lX\n", offset); + long byteIndex = offset / (4*8); + long bitInByte = (offset % 32) >> 2; + bitmap[byteIndex] |= (1 << bitInByte); + } + + // allocate worst case size block of all slide info + const unsigned entry_size = 4096/(8*4); // 8 bits per byte, possible pointer every 4 bytes. + const unsigned toc_count = (unsigned)bitmapSize/entry_size; + dyld_cache_slide_info* slideInfo = (dyld_cache_slide_info*)((uint8_t*)_buffer + _slideInfoFileOffset); + slideInfo->version = 1; + slideInfo->toc_offset = sizeof(dyld_cache_slide_info); + slideInfo->toc_count = toc_count; + slideInfo->entries_offset = (slideInfo->toc_offset+2*toc_count+127)&(-128); + slideInfo->entries_count = 0; + slideInfo->entries_size = entry_size; + // append each unique entry + const dyldCacheSlideInfoEntry* bitmapAsEntries = (dyldCacheSlideInfoEntry*)bitmap; + dyldCacheSlideInfoEntry* const entriesInSlidInfo = (dyldCacheSlideInfoEntry*)((char*)slideInfo+slideInfo->entries_offset()); + int entry_count = 0; + for (int i=0; i < toc_count; ++i) { + const dyldCacheSlideInfoEntry* thisEntry = &bitmapAsEntries[i]; + // see if it is same as one already added + bool found = false; + for (int j=0; j < entry_count; ++j) { + if ( memcmp(thisEntry, &entriesInSlidInfo[j], entry_size) == 0 ) { + slideInfo->set_toc(i, j); + found = true; + break; + } + } + if ( !found ) { + // append to end + memcpy(&entriesInSlidInfo[entry_count], thisEntry, entry_size); + slideInfo->set_toc(i, entry_count++); + } + } + slideInfo->entries_count = entry_count; + ::free((void*)bitmap); + + _buffer.header->slideInfoSize = align(slideInfo->entries_offset + entry_count*entry_size, _archLayout->sharedRegionAlignP2); +} + +*/ + + +void SharedCacheBuilder::setPointerContentV3(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* loc, uint64_t targetVMAddr, size_t next) +{ + assert(targetVMAddr > _readExecuteRegion.unslidLoadAddress); + assert(targetVMAddr < _readOnlyRegion.unslidLoadAddress+_readOnlyRegion.sizeInUse); + dyld3::MachOLoaded::ChainedFixupPointerOnDisk tmp; + uint16_t diversity; + bool hasAddrDiv; + uint8_t key; + if ( _aslrTracker.hasAuthData(loc, &diversity, &hasAddrDiv, &key) ) { + // if base cache address cannot fit into target, then use offset + tmp.arm64e.authRebase.target = _readExecuteRegion.unslidLoadAddress; + if ( tmp.arm64e.authRebase.target != _readExecuteRegion.unslidLoadAddress ) + targetVMAddr -= _readExecuteRegion.unslidLoadAddress; + loc->arm64e.authRebase.target = targetVMAddr; + loc->arm64e.authRebase.diversity = diversity; + loc->arm64e.authRebase.addrDiv = hasAddrDiv; + loc->arm64e.authRebase.key = key; + loc->arm64e.authRebase.next = next; + loc->arm64e.authRebase.bind = 0; + loc->arm64e.authRebase.auth = 1; + assert(loc->arm64e.authRebase.target == targetVMAddr && "target truncated"); + assert(loc->arm64e.authRebase.next == next && "next location truncated"); + } + else { + uint8_t highByte = 0; + _aslrTracker.hasHigh8(loc, &highByte); + // if base cache address cannot fit into target, then use offset + tmp.arm64e.rebase.target = _readExecuteRegion.unslidLoadAddress; + if ( tmp.arm64e.rebase.target != _readExecuteRegion.unslidLoadAddress ) + targetVMAddr -= _readExecuteRegion.unslidLoadAddress; + loc->arm64e.rebase.target = targetVMAddr; + loc->arm64e.rebase.high8 = highByte; + loc->arm64e.rebase.next = next; + loc->arm64e.rebase.bind = 0; + loc->arm64e.rebase.auth = 0; + assert(loc->arm64e.rebase.target == targetVMAddr && "target truncated"); + assert(loc->arm64e.rebase.next == next && "next location truncated"); + } +} + +uint16_t SharedCacheBuilder::pageStartV3(uint8_t* pageContent, uint32_t pageSize, const bool bitmap[]) +{ + const int maxPerPage = pageSize / 4; + uint16_t result = DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE; + dyld3::MachOLoaded::ChainedFixupPointerOnDisk* lastLoc = nullptr; + for (int i=0; i < maxPerPage; ++i) { + if ( bitmap[i] ) { + if ( result == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE ) { + // found first rebase location in page + result = i * 4; + } + dyld3::MachOLoaded::ChainedFixupPointerOnDisk* loc = (dyld3::MachOLoaded::ChainedFixupPointerOnDisk*)(pageContent + i*4);; + if ( lastLoc != nullptr ) { + // convert vmaddr based pointers to arm64e dyld cache chains + setPointerContentV3(lastLoc, lastLoc->raw64, loc - lastLoc); + } + lastLoc = loc; + } + } + if ( lastLoc != nullptr ) { + // convert vmaddr based pointers to arm64e dyld cache chain, and mark end of chain + setPointerContentV3(lastLoc, lastLoc->raw64, 0); + } + return result; +} + + +void SharedCacheBuilder::writeSlideInfoV3(const bool bitmap[], unsigned dataPageCount) +{ + const uint32_t pageSize = 4096; + + // fill in fixed info + assert(_slideInfoFileOffset != 0); + dyld_cache_slide_info3* info = (dyld_cache_slide_info3*)_readOnlyRegion.buffer; + info->version = 3; + info->page_size = pageSize; + info->page_starts_count = dataPageCount; + info->auth_value_add = _archLayout->sharedMemoryStart; + + // fill in per-page starts + uint8_t* pageContent = _readWriteRegion.buffer; + const bool* bitmapForPage = bitmap; + for (unsigned i=0; i < dataPageCount; ++i) { + info->page_starts[i] = pageStartV3(pageContent, pageSize, bitmapForPage); + pageContent += pageSize; + bitmapForPage += (sizeof(bool)*(pageSize/4)); + } + + // update header with final size + dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)_readExecuteRegion.buffer; + dyldCacheHeader->slideInfoSize = align(__offsetof(dyld_cache_slide_info3, page_starts[dataPageCount]), _archLayout->sharedRegionAlignP2); + if ( dyldCacheHeader->slideInfoSize > _slideInfoBufferSizeAllocated ) { + _diagnostics.error("kernel slide info overflow buffer"); + } +}