Cider Isn't Darwin Emulation, Really
Something went wrong. Try again.
18 kB · 412 lines
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413/* * Copyright (c) 2017 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */
#include <stdio.h>#include <unistd.h>#include <sys/stat.h>#include <string.h>#include <dlfcn.h>#include <fcntl.h>#include <stdlib.h>#include <errno.h>#include <sys/mman.h>#include <sys/syslimits.h>#include <mach-o/arch.h>#include <mach-o/loader.h>#include <mach-o/dyld_priv.h>#include <bootstrap.h>#include <mach/mach.h>#include <dispatch/dispatch.h>
#include <map>#include <vector>
#include "DyldSharedCache.h"#include "FileUtils.h"#include "StringUtils.h"#include "ClosureBuilder.h"#include "ClosurePrinter.h"#include "ClosureFileSystemPhysical.h"
using dyld3::closure::ImageArray;using dyld3::closure::Image;using dyld3::closure::ImageNum;using dyld3::closure::ClosureBuilder;using dyld3::closure::LaunchClosure;using dyld3::closure::DlopenClosure;using dyld3::closure::PathOverrides;using dyld3::Array;
// mmap() an shared cache file read/only but laid out like it would be at runtimestatic const DyldSharedCache* mapCacheFile(const char* path){ struct stat statbuf; if ( ::stat(path, &statbuf) ) { fprintf(stderr, "Error: stat failed for dyld shared cache at %s\n", path); return nullptr; } int cache_fd = ::open(path, O_RDONLY); if (cache_fd < 0) { fprintf(stderr, "Error: failed to open shared cache file at %s\n", path); return nullptr; }
uint8_t firstPage[4096]; if ( ::pread(cache_fd, firstPage, 4096, 0) != 4096 ) { fprintf(stderr, "Error: failed to read shared cache file at %s\n", path); return nullptr; } const dyld_cache_header* header = (dyld_cache_header*)firstPage; const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(firstPage + header->mappingOffset);
size_t vmSize = (size_t)(mappings[2].address + mappings[2].size - mappings[0].address); vm_address_t result; kern_return_t r = ::vm_allocate(mach_task_self(), &result, vmSize, VM_FLAGS_ANYWHERE); if ( r != KERN_SUCCESS ) { fprintf(stderr, "Error: failed to allocate space to load shared cache file at %s\n", path); return nullptr; } for (int i=0; i < 3; ++i) { void* mapped_cache = ::mmap((void*)(result + mappings[i].address - mappings[0].address), (size_t)mappings[i].size, PROT_READ, MAP_FIXED | MAP_PRIVATE, cache_fd, mappings[i].fileOffset); if (mapped_cache == MAP_FAILED) { fprintf(stderr, "Error: mmap() for shared cache at %s failed, errno=%d\n", path, errno); return nullptr; } } ::close(cache_fd);
return (DyldSharedCache*)result;}
static void usage(){ printf("dyld_closure_util program to create or view dyld3 closures\n"); printf(" mode:\n"); printf(" -create_closure <prog-path> # create a closure for the specified main executable\n"); printf(" -list_dyld_cache_closures # list all launch closures in the dyld shared cache with size\n"); printf(" -list_dyld_cache_dlopen_closures # list all dlopen closures in the dyld shared cache with size\n"); printf(" -print_dyld_cache_closure <prog-path> # find closure for specified program in dyld cache and print as JSON\n"); printf(" -print_dyld_cache_dylib <dylib-path> # print specified cached dylib as JSON\n"); printf(" -print_dyld_cache_dylibs # print all cached dylibs as JSON\n"); printf(" -print_dyld_cache_dlopen <path> # print specified dlopen closure as JSON\n"); printf(" options:\n"); printf(" -cache_file <cache-path> # path to cache file to use (default is current cache)\n"); printf(" -build_root <path-prefix> # when building a closure, the path prefix when runtime volume is not current boot volume\n"); printf(" -env <var=value> # when building a closure, DYLD_* env vars to assume\n"); printf(" -dlopen <path> # for use with -create_closure to simulate that program calling dlopen\n"); printf(" -verbose_fixups # for use with -print* options to force printing fixups\n"); printf(" -no_at_paths # when building a closure, simulate security not allowing @path expansion\n"); printf(" -no_fallback_paths # when building a closure, simulate security not allowing default fallback paths\n"); printf(" -allow_insertion_failures # when building a closure, simulate security allowing unloadable DYLD_INSERT_LIBRARIES to be ignored\n"); printf(" -force_invalid_cache_version # when building a closure, simulate security the cache version mismatching the builder\n");}
int main(int argc, const char* argv[]){ const char* cacheFilePath = nullptr; const char* inputMainExecutablePath = nullptr; const char* printCacheClosure = nullptr; const char* printCachedDylib = nullptr; const char* printOtherDylib = nullptr; const char* fsRootPath = nullptr; const char* fsOverlayPath = nullptr; bool listCacheClosures = false; bool listCacheDlopenClosures = false; bool printCachedDylibs = false; bool verboseFixups = false; bool allowAtPaths = true; bool allowFallbackPaths = true; bool allowInsertionFailures = false; bool forceInvalidFormatVersion = false; bool printRaw = false; std::vector<const char*> envArgs; std::vector<const char*> dlopens; char fsRootRealPath[PATH_MAX]; char fsOverlayRealPath[PATH_MAX];
if ( argc == 1 ) { usage(); return 0; }
for (int i = 1; i < argc; ++i) { const char* arg = argv[i]; if ( strcmp(arg, "-cache_file") == 0 ) { cacheFilePath = argv[++i]; if ( cacheFilePath == nullptr ) { fprintf(stderr, "-cache_file option requires path to cache file\n"); return 1; } } else if ( strcmp(arg, "-create_closure") == 0 ) { inputMainExecutablePath = argv[++i]; if ( inputMainExecutablePath == nullptr ) { fprintf(stderr, "-create_closure option requires a path to an executable\n"); return 1; } } else if ( strcmp(arg, "-dlopen") == 0 ) { const char* path = argv[++i]; if ( path == nullptr ) { fprintf(stderr, "-dlopen option requires a path to a packed closure list\n"); return 1; } dlopens.push_back(path); } else if ( strcmp(arg, "-verbose_fixups") == 0 ) { verboseFixups = true; } else if ( strcmp(arg, "-no_at_paths") == 0 ) { allowAtPaths = false; } else if ( strcmp(arg, "-no_fallback_paths") == 0 ) { allowFallbackPaths = false; } else if ( strcmp(arg, "-allow_insertion_failures") == 0 ) { allowInsertionFailures = true; } else if ( strcmp(arg, "-raw") == 0 ) { printRaw = true; } else if ( strcmp(arg, "-fs_root") == 0 ) { fsRootPath = argv[++i]; if ( fsRootPath == nullptr ) { fprintf(stderr, "-fs_root option requires a path\n"); return 1; } if ( realpath(fsRootPath, fsRootRealPath) == nullptr ) { fprintf(stderr, "-fs_root option requires a real path\n"); return 1; } fsRootPath = fsRootRealPath; } else if ( strcmp(arg, "-fs_overlay") == 0 ) { fsOverlayPath = argv[++i]; if ( fsOverlayPath == nullptr ) { fprintf(stderr, "-fs_overlay option requires a path\n"); return 1; } if ( realpath(fsOverlayPath, fsOverlayRealPath) == nullptr ) { fprintf(stderr, "-fs_root option requires a real path\n"); return 1; } fsOverlayPath = fsOverlayRealPath; } else if ( strcmp(arg, "-force_invalid_cache_version") == 0 ) { forceInvalidFormatVersion = true; } else if ( strcmp(arg, "-list_dyld_cache_closures") == 0 ) { listCacheClosures = true; } else if ( strcmp(arg, "-list_dyld_cache_dlopen_closures") == 0 ) { listCacheDlopenClosures = true; } else if ( strcmp(arg, "-print_dyld_cache_closure") == 0 ) { printCacheClosure = argv[++i]; if ( printCacheClosure == nullptr ) { fprintf(stderr, "-print_dyld_cache_closure option requires a path \n"); return 1; } } else if ( strcmp(arg, "-print_dyld_cache_dylibs") == 0 ) { printCachedDylibs = true; } else if ( strcmp(arg, "-print_dyld_cache_dylib") == 0 ) { printCachedDylib = argv[++i]; if ( printCachedDylib == nullptr ) { fprintf(stderr, "-print_dyld_cache_dylib option requires a path \n"); return 1; } } else if ( strcmp(arg, "-print_dyld_cache_dlopen") == 0 ) { printOtherDylib = argv[++i]; if ( printOtherDylib == nullptr ) { fprintf(stderr, "-print_dyld_cache_dlopen option requires a path \n"); return 1; } } else if ( strcmp(arg, "-env") == 0 ) { const char* envArg = argv[++i]; if ( (envArg == nullptr) || (strchr(envArg, '=') == nullptr) ) { fprintf(stderr, "-env option requires KEY=VALUE\n"); return 1; } envArgs.push_back(envArg); } else { fprintf(stderr, "unknown option %s\n", arg); return 1; } }
envArgs.push_back(nullptr);
const DyldSharedCache* dyldCache = nullptr; bool dyldCacheIsLive = true; if ( cacheFilePath != nullptr ) { dyldCache = mapCacheFile(cacheFilePath); dyldCacheIsLive = false; } else {#if __MAC_OS_X_VERSION_MIN_REQUIRED && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101300) fprintf(stderr, "this tool needs to run on macOS 10.13 or later\n"); return 1;#else size_t cacheLength; dyldCache = (DyldSharedCache*)_dyld_get_shared_cache_range(&cacheLength);#endif } dyld3::Platform platform = dyldCache->platform(); const dyld3::GradedArchs& archs = dyld3::GradedArchs::forName(dyldCache->archName(), true);
if ( inputMainExecutablePath != nullptr ) { PathOverrides pathOverrides; pathOverrides.setFallbackPathHandling(allowFallbackPaths ? dyld3::closure::PathOverrides::FallbackPathMode::classic : dyld3::closure::PathOverrides::FallbackPathMode::none); pathOverrides.setEnvVars(&envArgs[0], nullptr, nullptr); STACK_ALLOC_ARRAY(const ImageArray*, imagesArrays, 3+dlopens.size()); STACK_ALLOC_ARRAY(dyld3::LoadedImage, loadedArray, 1024); imagesArrays.push_back(dyldCache->cachedDylibsImageArray()); imagesArrays.push_back(dyldCache->otherOSImageArray());
dyld3::closure::FileSystemPhysical fileSystem(fsRootPath, fsOverlayPath); ClosureBuilder::AtPath atPathHanding = allowAtPaths ? ClosureBuilder::AtPath::all : ClosureBuilder::AtPath::none; ClosureBuilder builder(dyld3::closure::kFirstLaunchClosureImageNum, fileSystem, dyldCache, dyldCacheIsLive, archs, pathOverrides, atPathHanding, true, nullptr, platform, nullptr); if (forceInvalidFormatVersion) builder.setDyldCacheInvalidFormatVersion();
const LaunchClosure* mainClosure = builder.makeLaunchClosure(inputMainExecutablePath, allowInsertionFailures); if ( builder.diagnostics().hasError() ) { fprintf(stderr, "dyld_closure_util: %s\n", builder.diagnostics().errorMessage()); return 1; } ImageNum nextNum = builder.nextFreeImageNum();
if ( !dlopens.empty() ) printf("[\n"); imagesArrays.push_back(mainClosure->images()); dyld3::closure::printClosureAsJSON(mainClosure, imagesArrays, verboseFixups, printRaw, dyldCache); ClosureBuilder::buildLoadOrder(loadedArray, imagesArrays, mainClosure);
for (const char* path : dlopens) { printf(",\n");
// map unloaded mach-o files for use during closure building for (dyld3::LoadedImage& li : loadedArray) { if ( li.loadedAddress() != nullptr ) continue; if ( li.image()->inDyldCache() ) { li.setLoadedAddress((dyld3::MachOLoaded*)((uint8_t*)dyldCache + li.image()->cacheOffset())); } else { Diagnostics diag; char realerPath[MAXPATHLEN]; dyld3::closure::LoadedFileInfo loadedFileInfo = dyld3::MachOAnalyzer::load(diag, fileSystem, li.image()->path(), archs, platform, realerPath); li.setLoadedAddress((const dyld3::MachOAnalyzer*)loadedFileInfo.fileContent); } }
ClosureBuilder::AtPath atPathHandingDlopen = allowAtPaths ? ClosureBuilder::AtPath::all : ClosureBuilder::AtPath::onlyInRPaths; ClosureBuilder dlopenBuilder(nextNum, fileSystem, dyldCache, dyldCacheIsLive, archs, pathOverrides, atPathHandingDlopen, true, nullptr, platform, nullptr); if (forceInvalidFormatVersion) dlopenBuilder.setDyldCacheInvalidFormatVersion();
ImageNum topImageNum; const DlopenClosure* dlopenClosure = dlopenBuilder.makeDlopenClosure(path, mainClosure, loadedArray, 0, false, false, false, &topImageNum); if ( dlopenBuilder.diagnostics().hasError() ) { fprintf(stderr, "dyld_closure_util: %s\n", dlopenBuilder.diagnostics().errorMessage()); return 1; } if ( dlopenClosure == nullptr ) { if ( topImageNum == 0 ) { fprintf(stderr, "dyld_closure_util: failed to dlopen %s\n", path); return 1; } printf("{\n \"dyld-cache-image-num\": \"0x%04X\"\n}\n", topImageNum); } else { nextNum += dlopenClosure->images()->imageCount(); imagesArrays.push_back(dlopenClosure->images()); dyld3::closure::printClosureAsJSON(dlopenClosure, imagesArrays, verboseFixups, printRaw); ClosureBuilder::buildLoadOrder(loadedArray, imagesArrays, dlopenClosure); } } if ( !dlopens.empty() ) printf("]\n"); } else if ( listCacheClosures ) { dyldCache->forEachLaunchClosure(^(const char* runtimePath, const dyld3::closure::LaunchClosure* closure) { printf("%6lu %s\n", closure->size(), runtimePath); }); } else if ( listCacheDlopenClosures ) { dyldCache->forEachDlopenImage(^(const char* runtimePath, const dyld3::closure::Image* image) { printf("%6lu %s\n", image->size(), runtimePath); }); } else if ( printCacheClosure ) { const dyld3::closure::LaunchClosure* closure = dyldCache->findClosure(printCacheClosure); if ( closure != nullptr ) { STACK_ALLOC_ARRAY(const ImageArray*, imagesArrays, 3); imagesArrays.push_back(dyldCache->cachedDylibsImageArray()); imagesArrays.push_back(dyldCache->otherOSImageArray()); imagesArrays.push_back(closure->images()); dyld3::closure::printClosureAsJSON(closure, imagesArrays, verboseFixups, printRaw, dyldCache); } else { fprintf(stderr, "no closure in cache for %s\n", printCacheClosure); } } else if ( printCachedDylibs ) { dyld3::closure::printDyldCacheImagesAsJSON(dyldCache, verboseFixups, printRaw); } else if ( printCachedDylib != nullptr ) { const dyld3::closure::ImageArray* dylibs = dyldCache->cachedDylibsImageArray(); STACK_ALLOC_ARRAY(const ImageArray*, imagesArrays, 2); imagesArrays.push_back(dylibs); ImageNum num; if ( dylibs->hasPath(printCachedDylib, num) ) { dyld3::closure::printImageAsJSON(dylibs->imageForNum(num), imagesArrays, verboseFixups, printRaw, dyldCache); } else { fprintf(stderr, "no such image found\n"); } } else if ( printOtherDylib != nullptr ) { if ( const dyld3::closure::Image* image = dyldCache->findDlopenOtherImage(printOtherDylib) ) { STACK_ALLOC_ARRAY(const ImageArray*, imagesArrays, 2); imagesArrays.push_back(dyldCache->cachedDylibsImageArray()); imagesArrays.push_back(dyldCache->otherOSImageArray()); dyld3::closure::printImageAsJSON(image, imagesArrays, verboseFixups); } else { fprintf(stderr, "no such image found\n"); } }
return 0;}