dyld_closure_util.cpp [plain text]
#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"
#include "RootsChecker.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;
static 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);
const dyld_cache_mapping_info* lastMapping = &mappings[header->mappingCount - 1];
size_t vmSize = (size_t)(lastMapping->address + lastMapping->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 < header->mappingCount; ++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 {
size_t cacheLength;
dyldCache = (DyldSharedCache*)_dyld_get_shared_cache_range(&cacheLength);
}
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());
if ( auto others = dyldCache->otherOSImageArray() )
imagesArrays.push_back(others);
dyld3::closure::FileSystemPhysical fileSystem(fsRootPath, fsOverlayPath);
dyld3::RootsChecker rootsChecker;
ClosureBuilder::AtPath atPathHanding = allowAtPaths ? ClosureBuilder::AtPath::all : ClosureBuilder::AtPath::none;
ClosureBuilder builder(dyld3::closure::kFirstLaunchClosureImageNum, fileSystem, rootsChecker, 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");
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, builder.platform(), realerPath);
li.setLoadedAddress((const dyld3::MachOAnalyzer*)loadedFileInfo.fileContent);
}
}
ClosureBuilder::AtPath atPathHandingDlopen = allowAtPaths ? ClosureBuilder::AtPath::all : ClosureBuilder::AtPath::onlyInRPaths;
ClosureBuilder dlopenBuilder(nextNum, fileSystem, rootsChecker, dyldCache, dyldCacheIsLive, archs, pathOverrides, atPathHandingDlopen, true, nullptr, builder.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());
if ( auto others = dyldCache->otherOSImageArray() )
imagesArrays.push_back(others);
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());
if ( auto others = dyldCache->otherOSImageArray() )
imagesArrays.push_back(others);
dyld3::closure::printImageAsJSON(image, imagesArrays, verboseFixups);
}
else {
fprintf(stderr, "no such image found\n");
}
}
return 0;
}