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 "LaunchCache.h"
#include "LaunchCacheWriter.h"
#include "DyldSharedCache.h"
#include "FileUtils.h"
#include "ImageProxy.h"
#include "StringUtils.h"
#include "ClosureBuffer.h"
extern "C" {
#include "closuredProtocol.h"
}
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;
}
void* mapped_cache = mmap(NULL, (size_t)statbuf.st_size, PROT_READ, MAP_PRIVATE, cache_fd, 0);
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*)mapped_cache;
}
struct CachedSections
{
uint32_t mappedOffsetStart;
uint32_t mappedOffsetEnd;
uint64_t vmAddress;
const mach_header* mh;
std::string segmentName;
std::string sectionName;
const char* dylibPath;
};
static const CachedSections& find(uint32_t mappedOffset, const std::vector<CachedSections>& sections)
{
for (const CachedSections& entry : sections) {
if ( (entry.mappedOffsetStart <= mappedOffset) && (mappedOffset < entry.mappedOffsetEnd) )
return entry;
}
assert(0 && "invalid offset");
}
static void usage()
{
printf("dyld_closure_util program to create of view dyld3 closures\n");
printf(" mode:\n");
printf(" -create_closure <prog-path> # create a closure for the specified main executable\n");
printf(" -create_image_group <dylib-path> # create an ImageGroup for the specified dylib/bundle\n");
printf(" -list_dyld_cache_closures # list all closures in the dyld shared cache with size\n");
printf(" -list_dyld_cache_other_dylibs # list all group-1 (non-cached dylibs/bundles)\n");
printf(" -print_image_group <closure-path> # print specified ImageGroup file as JSON\n");
printf(" -print_closure_file <closure-path> # print specified closure file as JSON\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_dylibs # print group-0 (cached dylibs) as JSON\n");
printf(" -print_dyld_cache_other_dylibs # print group-1 (non-cached dylibs/bundles) as JSON\n");
printf(" -print_dyld_cache_other <path> # print just one group-1 (non-cached dylib/bundle) as JSON\n");
printf(" -print_dyld_cache_patch_table # print locations in shared cache that may need patching\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(" -o <output-file> # when building a closure, the file to write the (binary) closure to\n");
printf(" -include_all_dylibs_in_dir # when building a closure, add other mach-o files found in directory\n");
printf(" -env <var=value> # when building a closure, DYLD_* env vars to assume\n");
printf(" -dlopen <path> # for use with -create_closure to append ImageGroup if target had called dlopen\n");
printf(" -verbose_fixups # for use with -print* options to force printing fixups\n");
}
int main(int argc, const char* argv[])
{
const char* cacheFilePath = nullptr;
const char* inputMainExecutablePath = nullptr;
const char* inputTopImagePath = nullptr;
const char* outPath = nullptr;
const char* printPath = nullptr;
const char* printGroupPath = nullptr;
const char* printCacheClosure = nullptr;
const char* printCachedDylib = nullptr;
const char* printOtherDylib = nullptr;
bool listCacheClosures = false;
bool listOtherDylibs = false;
bool includeAllDylibs = false;
bool printClosures = false;
bool printCachedDylibs = false;
bool printOtherDylibs = false;
bool printPatchTable = false;
bool useClosured = false;
bool verboseFixups = false;
std::vector<std::string> buildtimePrefixes;
std::vector<std::string> envArgs;
std::vector<const char*> dlopens;
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, "-create_image_group") == 0 ) {
inputTopImagePath = argv[++i];
if ( inputTopImagePath == nullptr ) {
fprintf(stderr, "-create_image_group option requires a path to a dylib or bundle\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, "-build_root") == 0 ) {
const char* buildRootPath = argv[++i];
if ( buildRootPath == nullptr ) {
fprintf(stderr, "-build_root option requires a path \n");
return 1;
}
buildtimePrefixes.push_back(buildRootPath);
}
else if ( strcmp(arg, "-o") == 0 ) {
outPath = argv[++i];
if ( outPath == nullptr ) {
fprintf(stderr, "-o option requires a path \n");
return 1;
}
}
else if ( strcmp(arg, "-print_closure_file") == 0 ) {
printPath = argv[++i];
if ( printPath == nullptr ) {
fprintf(stderr, "-print_closure_file option requires a path \n");
return 1;
}
}
else if ( strcmp(arg, "-print_image_group") == 0 ) {
printGroupPath = argv[++i];
if ( printGroupPath == nullptr ) {
fprintf(stderr, "-print_image_group option requires a path \n");
return 1;
}
}
else if ( strcmp(arg, "-list_dyld_cache_closures") == 0 ) {
listCacheClosures = true;
}
else if ( strcmp(arg, "-list_dyld_cache_other_dylibs") == 0 ) {
listOtherDylibs = 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_closures") == 0 ) {
printClosures = true;
}
else if ( strcmp(arg, "-print_dyld_cache_dylibs") == 0 ) {
printCachedDylibs = true;
}
else if ( strcmp(arg, "-print_dyld_cache_other_dylibs") == 0 ) {
printOtherDylibs = 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_other") == 0 ) {
printOtherDylib = argv[++i];
if ( printOtherDylib == nullptr ) {
fprintf(stderr, "-print_dyld_cache_other option requires a path \n");
return 1;
}
}
else if ( strcmp(arg, "-print_dyld_cache_patch_table") == 0 ) {
printPatchTable = true;
}
else if ( strcmp(arg, "-include_all_dylibs_in_dir") == 0 ) {
includeAllDylibs = true;
}
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 if ( strcmp(arg, "-use_closured") == 0 ) {
useClosured = true;
}
else {
fprintf(stderr, "unknown option %s\n", arg);
return 1;
}
}
if ( (inputMainExecutablePath || inputTopImagePath) && printPath ) {
fprintf(stderr, "-create_closure and -print_closure_file are mutually exclusive");
return 1;
}
const DyldSharedCache* dyldCache = nullptr;
bool dyldCacheIsRaw = false;
if ( cacheFilePath != nullptr ) {
dyldCache = mapCacheFile(cacheFilePath);
dyldCacheIsRaw = true;
}
else {
#if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300)
size_t cacheLength;
dyldCache = (DyldSharedCache*)_dyld_get_shared_cache_range(&cacheLength);
dyldCacheIsRaw = false;
#endif
}
dyld3::ClosureBuffer::CacheIdent cacheIdent;
dyldCache->getUUID(cacheIdent.cacheUUID);
cacheIdent.cacheAddress = (unsigned long)dyldCache;
cacheIdent.cacheMappedSize = dyldCache->mappedSize();
dyld3::DyldCacheParser cacheParser(dyldCache, dyldCacheIsRaw);
if ( buildtimePrefixes.empty() )
buildtimePrefixes.push_back("");
std::vector<const dyld3::launch_cache::binary_format::ImageGroup*> existingGroups;
const dyld3::launch_cache::BinaryClosureData* mainClosure = nullptr;
if ( inputMainExecutablePath != nullptr ) {
dyld3::PathOverrides pathStuff(envArgs);
STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 3+dlopens.size(), theGroups);
theGroups[0] = cacheParser.cachedDylibsGroup();
theGroups[1] = cacheParser.otherDylibsGroup();
dyld3::launch_cache::DynArray<const dyld3::launch_cache::binary_format::ImageGroup*> groupList(2, &theGroups[0]);
dyld3::ClosureBuffer clsBuffer(cacheIdent, inputMainExecutablePath, groupList, pathStuff);
std::string mainPath = inputMainExecutablePath;
for (const std::string& prefix : buildtimePrefixes) {
if ( startsWith(mainPath, prefix) ) {
mainPath = mainPath.substr(prefix.size());
if ( mainPath[0] != '/' )
mainPath = "/" + mainPath;
break;
}
}
Diagnostics closureDiag;
mainClosure = dyld3::ImageProxyGroup::makeClosure(closureDiag, clsBuffer, mach_task_self(), buildtimePrefixes);
if ( closureDiag.hasError() ) {
fprintf(stderr, "dyld_closure_util: %s\n", closureDiag.errorMessage().c_str());
return 1;
}
for (const std::string& warn : closureDiag.warnings() )
fprintf(stderr, "dyld_closure_util: warning: %s\n", warn.c_str());
dyld3::launch_cache::Closure closure(mainClosure);
if ( outPath != nullptr ) {
safeSave(mainClosure, closure.size(), outPath);
}
else {
dyld3::launch_cache::Closure theClosure(mainClosure);
theGroups[2] = theClosure.group().binaryData();
if ( !dlopens.empty() )
printf("[\n");
closure.printAsJSON(dyld3::launch_cache::ImageGroupList(3, &theGroups[0]), true);
int groupIndex = 3;
for (const char* path : dlopens) {
printf(",\n");
dyld3::launch_cache::DynArray<const dyld3::launch_cache::binary_format::ImageGroup*> groupList2(groupIndex-2, &theGroups[2]);
dyld3::ClosureBuffer dlopenBuffer(cacheIdent, path, groupList2, pathStuff);
Diagnostics dlopenDiag;
theGroups[groupIndex] = dyld3::ImageProxyGroup::makeDlopenGroup(dlopenDiag, dlopenBuffer, mach_task_self(), buildtimePrefixes);
if ( dlopenDiag.hasError() ) {
fprintf(stderr, "dyld_closure_util: %s\n", dlopenDiag.errorMessage().c_str());
return 1;
}
for (const std::string& warn : dlopenDiag.warnings() )
fprintf(stderr, "dyld_closure_util: warning: %s\n", warn.c_str());
dyld3::launch_cache::ImageGroup dlopenGroup(theGroups[groupIndex]);
dlopenGroup.printAsJSON(dyld3::launch_cache::ImageGroupList(groupIndex+1, &theGroups[0]), true);
++groupIndex;
}
if ( !dlopens.empty() )
printf("]\n");
}
}
#if 0
else if ( inputTopImagePath != nullptr ) {
std::string imagePath = inputTopImagePath;
for (const std::string& prefix : buildtimePrefixes) {
if ( startsWith(imagePath, prefix) ) {
imagePath = imagePath.substr(prefix.size());
if ( imagePath[0] != '/' )
imagePath = "/" + imagePath;
break;
}
}
Diagnostics igDiag;
existingGroups.push_back(dyldCache->cachedDylibsGroup());
existingGroups.push_back(dyldCache->otherDylibsGroup());
if ( existingClosuresPath != nullptr ) {
size_t mappedSize;
const void* imageGroups = mapFileReadOnly(existingClosuresPath, mappedSize);
if ( imageGroups == nullptr ) {
fprintf(stderr, "dyld_closure_util: could not read file %s\n", printPath);
return 1;
}
uint32_t sentGroups = *(uint32_t*)imageGroups;
uint16_t lastGroupNum = 2;
existingGroups.resize(sentGroups+2);
const uint8_t* p = (uint8_t*)(imageGroups)+4;
for (uint32_t i=0; i < sentGroups; ++i) {
const dyld3::launch_cache::binary_format::ImageGroup* aGroup = (const dyld3::launch_cache::binary_format::ImageGroup*)p;
existingGroups[2+i] = aGroup;
dyld3::launch_cache::ImageGroup imgrp(aGroup);
lastGroupNum = imgrp.groupNum();
p += imgrp.size();
}
}
const dyld3::launch_cache::binary_format::ImageGroup* ig = dyld3::ImageProxyGroup::makeDlopenGroup(igDiag, dyldCache, existingGroups.size(), existingGroups, imagePath, envArgs);
if ( igDiag.hasError() ) {
fprintf(stderr, "dyld_closure_util: %s\n", igDiag.errorMessage().c_str());
return 1;
}
dyld3::launch_cache::ImageGroup group(ig);
group.printAsJSON(dyldCache, true);
}
#endif
else if ( printPath != nullptr ) {
size_t mappedSize;
const void* buff = mapFileReadOnly(printPath, mappedSize);
if ( buff == nullptr ) {
fprintf(stderr, "dyld_closure_util: could not read file %s\n", printPath);
return 1;
}
dyld3::launch_cache::Closure theClosure((dyld3::launch_cache::binary_format::Closure*)buff);
STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 3, theGroups);
theGroups[0] = cacheParser.cachedDylibsGroup();
theGroups[1] = cacheParser.otherDylibsGroup();
theGroups[2] = theClosure.group().binaryData();
theClosure.printAsJSON(theGroups, verboseFixups);
munmap((void*)buff, mappedSize);
}
else if ( printGroupPath != nullptr ) {
size_t mappedSize;
const void* buff = mapFileReadOnly(printGroupPath, mappedSize);
if ( buff == nullptr ) {
fprintf(stderr, "dyld_closure_util: could not read file %s\n", printPath);
return 1;
}
dyld3::launch_cache::ImageGroup group((dyld3::launch_cache::binary_format::ImageGroup*)buff);
munmap((void*)buff, mappedSize);
}
else if ( listCacheClosures ) {
cacheParser.forEachClosure(^(const char* runtimePath, const dyld3::launch_cache::binary_format::Closure* closureBinary) {
dyld3::launch_cache::Closure closure(closureBinary);
printf("%6lu %s\n", closure.size(), runtimePath);
});
}
else if ( listOtherDylibs ) {
dyld3::launch_cache::ImageGroup dylibGroup(cacheParser.otherDylibsGroup());
for (uint32_t i=0; i < dylibGroup.imageCount(); ++i) {
dyld3::launch_cache::Image image = dylibGroup.image(i);
printf("%s\n", image.path());
}
}
else if ( printCacheClosure ) {
const dyld3::launch_cache::BinaryClosureData* cls = cacheParser.findClosure(printCacheClosure);
if ( cls != nullptr ) {
dyld3::launch_cache::Closure theClosure(cls);
STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 3, theGroups);
theGroups[0] = cacheParser.cachedDylibsGroup();
theGroups[1] = cacheParser.otherDylibsGroup();
theGroups[2] = theClosure.group().binaryData();
theClosure.printAsJSON(theGroups, verboseFixups);
}
else {
fprintf(stderr, "no closure in cache for %s\n", printCacheClosure);
}
}
else if ( printClosures ) {
cacheParser.forEachClosure(^(const char* runtimePath, const dyld3::launch_cache::binary_format::Closure* closureBinary) {
dyld3::launch_cache::Closure theClosure(closureBinary);
STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 3, theGroups);
theGroups[0] = cacheParser.cachedDylibsGroup();
theGroups[1] = cacheParser.otherDylibsGroup();
theGroups[2] = theClosure.group().binaryData();
theClosure.printAsJSON(theGroups, verboseFixups);
});
}
else if ( printCachedDylibs ) {
STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 2, theGroups);
theGroups[0] = cacheParser.cachedDylibsGroup();
theGroups[1] = cacheParser.otherDylibsGroup();
dyld3::launch_cache::ImageGroup dylibGroup(theGroups[0]);
dylibGroup.printAsJSON(theGroups, verboseFixups);
}
else if ( printCachedDylib != nullptr ) {
STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 2, theGroups);
theGroups[0] = cacheParser.cachedDylibsGroup();
theGroups[1] = cacheParser.otherDylibsGroup();
dyld3::launch_cache::ImageGroup dylibGroup(cacheParser.cachedDylibsGroup());
uint32_t imageIndex;
const dyld3::launch_cache::binary_format::Image* binImage = dylibGroup.findImageByPath(printCachedDylib, imageIndex);
if ( binImage != nullptr ) {
dyld3::launch_cache::Image image(binImage);
image.printAsJSON(theGroups, true);
}
else {
fprintf(stderr, "no such other image found\n");
}
}
else if ( printOtherDylibs ) {
STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 2, theGroups);
theGroups[0] = cacheParser.cachedDylibsGroup();
theGroups[1] = cacheParser.otherDylibsGroup();
dyld3::launch_cache::ImageGroup dylibGroup(theGroups[1]);
dylibGroup.printAsJSON(theGroups, verboseFixups);
}
else if ( printOtherDylib != nullptr ) {
STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 2, theGroups);
theGroups[0] = cacheParser.cachedDylibsGroup();
theGroups[1] = cacheParser.otherDylibsGroup();
dyld3::launch_cache::ImageGroup dylibGroup(cacheParser.otherDylibsGroup());
uint32_t imageIndex;
const dyld3::launch_cache::binary_format::Image* binImage = dylibGroup.findImageByPath(printOtherDylib, imageIndex);
if ( binImage != nullptr ) {
dyld3::launch_cache::Image image(binImage);
image.printAsJSON(theGroups, true);
}
else {
fprintf(stderr, "no such other image found\n");
}
}
else if ( printPatchTable ) {
__block uint64_t cacheBaseAddress = 0;
dyldCache->forEachRegion(^(const void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) {
if ( cacheBaseAddress == 0 )
cacheBaseAddress = vmAddr;
});
__block std::vector<CachedSections> sections;
__block bool hasError = false;
dyldCache->forEachImage(^(const mach_header* mh, const char* installName) {
dyld3::MachOParser parser(mh, dyldCacheIsRaw);
parser.forEachSection(^(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content,
uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop) {
if ( illegalSectionSize ) {
fprintf(stderr, "dyld_closure_util: section size extends beyond the end of the segment %s/%s\n", segName, sectionName);
stop = true;
return;
}
uint32_t offsetStart = (uint32_t)(addr - cacheBaseAddress);
uint32_t offsetEnd = (uint32_t)(offsetStart + size);
sections.push_back({offsetStart, offsetEnd, addr, mh, segName, sectionName, installName});
});
});
if (hasError)
return 1;
dyld3::launch_cache::ImageGroup dylibGroup(cacheParser.cachedDylibsGroup());
dylibGroup.forEachDyldCachePatchLocation(cacheParser, ^(uint32_t targetCacheVmOffset, const std::vector<uint32_t>& usesPointersCacheVmOffsets, bool& stop) {
const CachedSections& targetSection = find(targetCacheVmOffset, sections);
dyld3::MachOParser targetParser(targetSection.mh, dyldCacheIsRaw);
const char* symbolName;
uint64_t symbolAddress;
if ( targetParser.findClosestSymbol(targetSection.vmAddress + targetCacheVmOffset - targetSection.mappedOffsetStart, &symbolName, &symbolAddress) ) {
printf("%s: [cache offset = 0x%08X]\n", symbolName, targetCacheVmOffset);
}
else {
printf("0x%08X from %40s %10s %16s + 0x%06X\n", targetCacheVmOffset, strrchr(targetSection.dylibPath, '/')+1, targetSection.segmentName.c_str(), targetSection.sectionName.c_str(), targetCacheVmOffset - targetSection.mappedOffsetStart);
}
for (uint32_t offset : usesPointersCacheVmOffsets) {
const CachedSections& usedInSection = find(offset, sections);
printf("%40s %10s %16s + 0x%06X\n", strrchr(usedInSection.dylibPath, '/')+1, usedInSection.segmentName.c_str(), usedInSection.sectionName.c_str(), offset - usedInSection.mappedOffsetStart);
}
});
}
return 0;
}