#include <string.h>
#include <stdint.h>
#include <_simple.h>
#include <sys/errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <TargetConditionals.h>
#include <CommonCrypto/CommonDigest.h>
#include <dispatch/dispatch.h>
#include <algorithm>
#include "dlfcn.h"
#include "dyld_priv.h"
#include "AllImages.h"
#include "MachOParser.h"
#include "Loading.h"
#include "Logging.h"
#include "Diagnostics.h"
#include "DyldSharedCache.h"
#include "PathOverrides.h"
#include "APIs.h"
#include "StringUtils.h"
extern "C" {
#include "closuredProtocol.h"
}
namespace dyld {
extern dyld_all_image_infos dyld_all_image_infos;
}
namespace dyld3 {
uint32_t _dyld_image_count(void)
{
log_apis("_dyld_image_count()\n");
return gAllImages.count();
}
const mach_header* _dyld_get_image_header(uint32_t imageIndex)
{
log_apis("_dyld_get_image_header(%d)\n", imageIndex);
const mach_header* loadAddress;
launch_cache::Image image = gAllImages.findByLoadOrder(imageIndex, &loadAddress);
if ( image.valid() )
return loadAddress;
return nullptr;
}
intptr_t _dyld_get_image_slide(const mach_header* mh)
{
log_apis("_dyld_get_image_slide(%p)\n", mh);
MachOParser parser(mh);
return parser.getSlide();
}
intptr_t _dyld_get_image_vmaddr_slide(uint32_t imageIndex)
{
log_apis("_dyld_get_image_vmaddr_slide(%d)\n", imageIndex);
const mach_header* mh = _dyld_get_image_header(imageIndex);
if ( mh != nullptr )
return dyld3::_dyld_get_image_slide(mh);
return 0;
}
const char* _dyld_get_image_name(uint32_t imageIndex)
{
log_apis("_dyld_get_image_name(%d)\n", imageIndex);
const mach_header* loadAddress;
launch_cache::Image image = gAllImages.findByLoadOrder(imageIndex, &loadAddress);
if ( image.valid() )
return gAllImages.imagePath(image.binaryData());
return nullptr;
}
static bool nameMatch(const char* installName, const char* libraryName)
{
const char* leafName = strrchr(installName, '/');
if ( leafName == NULL )
leafName = installName;
else
leafName++;
if ( strcmp(leafName, libraryName) == 0 )
return true;
size_t leafNameLen = strlen(leafName);
size_t libraryNameLen = strlen(libraryName);
if ( leafNameLen < (libraryNameLen+9) )
return false;
if ( strncmp(leafName, "lib", 3) != 0 )
return false;
if ( strcmp(&leafName[leafNameLen-6], ".dylib") != 0 )
return false;
if ( strncmp(&leafName[3], libraryName, libraryNameLen) != 0 )
return false;
return (leafName[libraryNameLen+3] == '.');
}
int32_t NSVersionOfLinkTimeLibrary(const char* libraryName)
{
log_apis("NSVersionOfLinkTimeLibrary(\"%s\")\n", libraryName);
__block int32_t result = -1;
MachOParser parser(gAllImages.mainExecutable());
parser.forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t compatVersion, uint32_t currentVersion, bool& stop) {
if ( nameMatch(loadPath, libraryName) )
result = currentVersion;
});
log_apis(" NSVersionOfLinkTimeLibrary() => 0x%08X\n", result);
return result;
}
int32_t NSVersionOfRunTimeLibrary(const char* libraryName)
{
log_apis("NSVersionOfRunTimeLibrary(\"%s\")\n", libraryName);
uint32_t count = gAllImages.count();
for (uint32_t i=0; i < count; ++i) {
const mach_header* loadAddress;
launch_cache::Image image = gAllImages.findByLoadOrder(i, &loadAddress);
if ( image.valid() ) {
MachOParser parser(loadAddress);
const char* installName;
uint32_t currentVersion;
uint32_t compatVersion;
if ( parser.getDylibInstallName(&installName, &compatVersion, ¤tVersion) && nameMatch(installName, libraryName) ) {
log_apis(" NSVersionOfRunTimeLibrary() => 0x%08X\n", currentVersion);
return currentVersion;
}
}
}
log_apis(" NSVersionOfRunTimeLibrary() => -1\n");
return -1;
}
#if __WATCH_OS_VERSION_MIN_REQUIRED
static uint32_t watchVersToIOSVers(uint32_t vers)
{
return vers + 0x00070000;
}
uint32_t dyld_get_program_sdk_watch_os_version()
{
log_apis("dyld_get_program_sdk_watch_os_version()\n");
Platform platform;
uint32_t minOS;
uint32_t sdk;
MachOParser parser(gAllImages.mainExecutable());
if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) {
if ( platform == Platform::watchOS )
return sdk;
}
return 0;
}
uint32_t dyld_get_program_min_watch_os_version()
{
log_apis("dyld_get_program_min_watch_os_version()\n");
Platform platform;
uint32_t minOS;
uint32_t sdk;
MachOParser parser(gAllImages.mainExecutable());
if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) {
if ( platform == Platform::watchOS )
return minOS; }
return 0;
}
#endif
#if TARGET_OS_BRIDGE
static uint32_t bridgeVersToIOSVers(uint32_t vers)
{
return vers + 0x00090000;
}
uint32_t dyld_get_program_sdk_bridge_os_version()
{
log_apis("dyld_get_program_sdk_bridge_os_version()\n");
Platform platform;
uint32_t minOS;
uint32_t sdk;
MachOParser parser(gAllImages.mainExecutable());
if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) {
if ( platform == Platform::bridgeOS )
return sdk;
}
return 0;
}
uint32_t dyld_get_program_min_bridge_os_version()
{
log_apis("dyld_get_program_min_bridge_os_version()\n");
Platform platform;
uint32_t minOS;
uint32_t sdk;
MachOParser parser(gAllImages.mainExecutable());
if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) {
if ( platform == Platform::bridgeOS )
return minOS; }
return 0;
}
#endif
#if !__WATCH_OS_VERSION_MIN_REQUIRED && !__TV_OS_VERSION_MIN_REQUIRED && !TARGET_OS_BRIDGE
#define PACKED_VERSION(major, minor, tiny) ((((major) & 0xffff) << 16) | (((minor) & 0xff) << 8) | ((tiny) & 0xff))
static uint32_t deriveSDKVersFromDylibs(const mach_header* mh)
{
__block uint32_t foundationVers = 0;
__block uint32_t libSystemVers = 0;
MachOParser parser(mh);
parser.forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t compatVersion, uint32_t currentVersion, bool& stop) {
if ( strcmp(loadPath, "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation") == 0 )
foundationVers = currentVersion;
else if ( strcmp(loadPath, "/usr/lib/libSystem.B.dylib") == 0 )
libSystemVers = currentVersion;
});
struct DylibToOSMapping {
uint32_t dylibVersion;
uint32_t osVersion;
};
#if __IPHONE_OS_VERSION_MIN_REQUIRED
static const DylibToOSMapping foundationMapping[] = {
{ PACKED_VERSION(678,24,0), 0x00020000 },
{ PACKED_VERSION(678,26,0), 0x00020100 },
{ PACKED_VERSION(678,29,0), 0x00020200 },
{ PACKED_VERSION(678,47,0), 0x00030000 },
{ PACKED_VERSION(678,51,0), 0x00030100 },
{ PACKED_VERSION(678,60,0), 0x00030200 },
{ PACKED_VERSION(751,32,0), 0x00040000 },
{ PACKED_VERSION(751,37,0), 0x00040100 },
{ PACKED_VERSION(751,49,0), 0x00040200 },
{ PACKED_VERSION(751,58,0), 0x00040300 },
{ PACKED_VERSION(881,0,0), 0x00050000 },
{ PACKED_VERSION(890,1,0), 0x00050100 },
{ PACKED_VERSION(992,0,0), 0x00060000 },
{ PACKED_VERSION(993,0,0), 0x00060100 },
{ PACKED_VERSION(1038,14,0),0x00070000 },
{ PACKED_VERSION(0,0,0), 0x00070000 }
};
if ( foundationVers != 0 ) {
uint32_t lastOsVersion = 0;
for (const DylibToOSMapping* p=foundationMapping; ; ++p) {
if ( p->dylibVersion == 0 )
return p->osVersion;
if ( foundationVers < p->dylibVersion )
return lastOsVersion;
lastOsVersion = p->osVersion;
}
}
#else
static const DylibToOSMapping libSystemMapping[] = {
{ PACKED_VERSION(88,1,3), 0x000A0400 },
{ PACKED_VERSION(111,0,0), 0x000A0500 },
{ PACKED_VERSION(123,0,0), 0x000A0600 },
{ PACKED_VERSION(159,0,0), 0x000A0700 },
{ PACKED_VERSION(169,3,0), 0x000A0800 },
{ PACKED_VERSION(1197,0,0), 0x000A0900 },
{ PACKED_VERSION(0,0,0), 0x000A0900 }
};
if ( libSystemVers != 0 ) {
uint32_t lastOsVersion = 0;
for (const DylibToOSMapping* p=libSystemMapping; ; ++p) {
if ( p->dylibVersion == 0 )
return p->osVersion;
if ( libSystemVers < p->dylibVersion )
return lastOsVersion;
lastOsVersion = p->osVersion;
}
}
#endif
return 0;
}
#endif
uint32_t dyld_get_sdk_version(const mach_header* mh)
{
log_apis("dyld_get_sdk_version(%p)\n", mh);
Platform platform;
uint32_t minOS;
uint32_t sdk;
if ( !MachOParser::wellFormedMachHeaderAndLoadCommands(mh) )
return 0;
MachOParser parser(mh);
if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) {
switch (platform) {
#if TARGET_OS_BRIDGE
case Platform::bridgeOS:
return bridgeVersToIOSVers(sdk);
case Platform::iOS:
return sdk;
#elif __WATCH_OS_VERSION_MIN_REQUIRED
case Platform::watchOS:
return watchVersToIOSVers(sdk);
case Platform::iOS:
return sdk;
#elif __TV_OS_VERSION_MIN_REQUIRED
case Platform::tvOS:
case Platform::iOS:
return sdk;
#elif __IPHONE_OS_VERSION_MIN_REQUIRED
case Platform::iOS:
if ( sdk != 0 ) return sdk;
break;
#else
case Platform::macOS:
if ( sdk != 0 ) return sdk;
break;
#endif
default:
break;
}
}
#if __WATCH_OS_VERSION_MIN_REQUIRED ||__TV_OS_VERSION_MIN_REQUIRED || TARGET_OS_BRIDGE
return 0;
#else
return deriveSDKVersFromDylibs(mh);
#endif
}
uint32_t dyld_get_program_sdk_version()
{
log_apis("dyld_get_program_sdk_version()\n");
return dyld3::dyld_get_sdk_version(gAllImages.mainExecutable());
}
uint32_t dyld_get_min_os_version(const mach_header* mh)
{
log_apis("dyld_get_min_os_version(%p)\n", mh);
Platform platform;
uint32_t minOS;
uint32_t sdk;
if ( !MachOParser::wellFormedMachHeaderAndLoadCommands(mh) )
return 0;
MachOParser parser(mh);
if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) {
switch (platform) {
#if TARGET_OS_BRIDGE
case Platform::bridgeOS:
return bridgeVersToIOSVers(minOS);
case Platform::iOS:
return minOS;
#elif __WATCH_OS_VERSION_MIN_REQUIRED
case Platform::watchOS:
return watchVersToIOSVers(minOS);
case Platform::iOS:
return minOS;
#elif __TV_OS_VERSION_MIN_REQUIRED
case Platform::tvOS:
case Platform::iOS:
return minOS;
#elif __IPHONE_OS_VERSION_MIN_REQUIRED
case Platform::iOS:
return minOS;
#else
case Platform::macOS:
return minOS;
#endif
default:
break;
}
}
return 0;
}
uint32_t dyld_get_program_min_os_version()
{
log_apis("dyld_get_program_min_os_version()\n");
return dyld3::dyld_get_min_os_version(gAllImages.mainExecutable());
}
bool _dyld_get_image_uuid(const mach_header* mh, uuid_t uuid)
{
log_apis("_dyld_get_image_uuid(%p, %p)\n", mh, uuid);
if ( !MachOParser::wellFormedMachHeaderAndLoadCommands(mh) )
return false;
MachOParser parser(mh);
return parser.getUuid(uuid);
}
int _NSGetExecutablePath(char* buf, uint32_t* bufsize)
{
log_apis("_NSGetExecutablePath(%p, %p)\n", buf, bufsize);
launch_cache::Image image = gAllImages.mainExecutableImage();
if ( image.valid() ) {
const char* path = gAllImages.imagePath(image.binaryData());
size_t pathSize = strlen(path) + 1;
if ( *bufsize >= pathSize ) {
strcpy(buf, path);
return 0;
}
*bufsize = (uint32_t)pathSize;
}
return -1;
}
void _dyld_register_func_for_add_image(void (*func)(const mach_header *mh, intptr_t vmaddr_slide))
{
log_apis("_dyld_register_func_for_add_image(%p)\n", func);
gAllImages.addLoadNotifier(func);
}
void _dyld_register_func_for_remove_image(void (*func)(const mach_header *mh, intptr_t vmaddr_slide))
{
log_apis("_dyld_register_func_for_remove_image(%p)\n", func);
gAllImages.addUnloadNotifier(func);
}
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
log_apis("_dyld_objc_notify_register(%p, %p, %p)\n", mapped, init, unmapped);
gAllImages.setObjCNotifiers(mapped, init, unmapped);
}
const mach_header* dyld_image_header_containing_address(const void* addr)
{
log_apis("dyld_image_header_containing_address(%p)\n", addr);
const mach_header* loadAddress;
launch_cache::Image image = gAllImages.findByOwnedAddress(addr, &loadAddress);
if ( image.valid() )
return loadAddress;
return nullptr;
}
const char* dyld_image_path_containing_address(const void* addr)
{
log_apis("dyld_image_path_containing_address(%p)\n", addr);
const mach_header* loadAddress;
launch_cache::Image image = gAllImages.findByOwnedAddress(addr, &loadAddress);
if ( image.valid() ) {
const char* path = gAllImages.imagePath(image.binaryData());
log_apis(" dyld_image_path_containing_address() => %s\n", path);
return path;
}
log_apis(" dyld_image_path_containing_address() => NULL\n");
return nullptr;
}
bool _dyld_is_memory_immutable(const void* addr, size_t length)
{
uintptr_t checkStart = (uintptr_t)addr;
uintptr_t checkEnd = checkStart + length;
const DyldSharedCache* cache = (DyldSharedCache*)gAllImages.cacheLoadAddress();
if ( cache != nullptr ) {
__block bool firstVMAddr = 0;
__block bool isReadOnlyInCache = false;
__block bool isInCache = false;
cache->forEachRegion(^(const void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) {
if ( firstVMAddr == 0 )
firstVMAddr = vmAddr;
uintptr_t regionStart = (uintptr_t)cache + (uintptr_t)(vmAddr - firstVMAddr);
uintptr_t regionEnd = regionStart + (uintptr_t)size;
if ( (regionStart < checkStart) && (checkEnd < regionEnd) ) {
isInCache = true;
isReadOnlyInCache = ((permissions & VM_PROT_WRITE) != 0);
}
});
if ( isInCache )
return isReadOnlyInCache;
}
const mach_header* loadAddress;
uint8_t permissions;
launch_cache::Image image = gAllImages.findByOwnedAddress(addr, &loadAddress, &permissions);
if ( !image.valid() )
return false;
if ( (permissions & VM_PROT_WRITE) != 0 )
return false;
return !gAllImages.imageUnloadable(image, loadAddress);
}
int dladdr(const void* addr, Dl_info* info)
{
log_apis("dladdr(%p, %p)\n", addr, info);
const mach_header* loadAddress;
launch_cache::Image image = gAllImages.findByOwnedAddress(addr, &loadAddress);
if ( !image.valid() ) {
log_apis(" dladdr() => 0\n");
return 0;
}
MachOParser parser(loadAddress);
info->dli_fname = gAllImages.imagePath(image.binaryData());
info->dli_fbase = (void*)(loadAddress);
if ( addr == info->dli_fbase ) {
info->dli_sname = "__dso_handle";
info->dli_saddr = info->dli_fbase;
}
else if ( parser.findClosestSymbol(addr, &(info->dli_sname), (const void**)&(info->dli_saddr)) ) {
if ( info->dli_saddr == info->dli_fbase ) {
info->dli_sname = nullptr;
info->dli_saddr = nullptr;
}
else if ( (info->dli_sname != nullptr) && (info->dli_sname[0] == '_') ) {
info->dli_sname = info->dli_sname + 1;
}
}
else {
info->dli_sname = nullptr;
info->dli_saddr = nullptr;
}
log_apis(" dladdr() => 1, { \"%s\", %p, \"%s\", %p }\n", info->dli_fname, info->dli_fbase, info->dli_sname, info->dli_saddr);
return 1;
}
struct PerThreadErrorMessage
{
size_t sizeAllocated;
bool valid;
char message[1];
};
static pthread_key_t dlerror_perThreadKey()
{
static dispatch_once_t onceToken;
static pthread_key_t dlerrorPThreadKey;
dispatch_once(&onceToken, ^{
pthread_key_create(&dlerrorPThreadKey, &free);
});
return dlerrorPThreadKey;
}
static void clearErrorString()
{
PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)pthread_getspecific(dlerror_perThreadKey());
if ( errorBuffer != nullptr )
errorBuffer->valid = false;
}
__attribute__((format(printf, 1, 2)))
static void setErrorString(const char* format, ...)
{
_SIMPLE_STRING buf = _simple_salloc();
if ( buf != nullptr ) {
va_list list;
va_start(list, format);
_simple_vsprintf(buf, format, list);
va_end(list);
size_t strLen = strlen(_simple_string(buf)) + 1;
size_t sizeNeeded = sizeof(PerThreadErrorMessage) + strLen;
PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)pthread_getspecific(dlerror_perThreadKey());
if ( errorBuffer != nullptr ) {
if ( errorBuffer->sizeAllocated < sizeNeeded ) {
free(errorBuffer);
errorBuffer = nullptr;
}
}
if ( errorBuffer == nullptr ) {
size_t allocSize = std::max(sizeNeeded, (size_t)256);
PerThreadErrorMessage* p = (PerThreadErrorMessage*)malloc(allocSize);
p->sizeAllocated = allocSize;
p->valid = false;
pthread_setspecific(dlerror_perThreadKey(), p);
errorBuffer = p;
}
strcpy(errorBuffer->message, _simple_string(buf));
errorBuffer->valid = true;
_simple_sfree(buf);
}
}
char* dlerror()
{
log_apis("dlerror()\n");
PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)pthread_getspecific(dlerror_perThreadKey());
if ( errorBuffer != nullptr ) {
if ( errorBuffer->valid ) {
errorBuffer->valid = false;
return errorBuffer->message;
}
}
return nullptr;
}
#if __arm64__
#define CURRENT_CPU_TYPE CPU_TYPE_ARM64
#elif __arm__
#define CURRENT_CPU_TYPE CPU_TYPE_ARM
#endif
class VIS_HIDDEN RecursiveAutoLock
{
public:
RecursiveAutoLock() {
pthread_mutex_lock(&_sMutex);
}
~RecursiveAutoLock() {
pthread_mutex_unlock(&_sMutex);
}
private:
static pthread_mutex_t _sMutex;
};
pthread_mutex_t RecursiveAutoLock::_sMutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
static void* makeDlHandle(const mach_header* mh, bool dontContinue)
{
uintptr_t flags = (dontContinue ? 1 : 0);
return (void*)((((uintptr_t)mh) >> 5) | flags);
}
VIS_HIDDEN
void parseDlHandle(void* h, const mach_header** mh, bool* dontContinue)
{
*dontContinue = (((uintptr_t)h) & 1);
*mh = (const mach_header*)((((uintptr_t)h) & (-2)) << 5);
}
int dlclose(void* handle)
{
log_apis("dlclose(%p)\n", handle);
if ( handle == RTLD_MAIN_ONLY )
return 0;
if ( handle == RTLD_DEFAULT )
return 0;
RecursiveAutoLock dlopenSerializer;
const mach_header* mh;
bool dontContinue;
parseDlHandle(handle, &mh, &dontContinue);
launch_cache::Image image = gAllImages.findByLoadAddress(mh);
if ( image.valid() ) {
if ( !image.neverUnload() )
gAllImages.decRefCount(mh);
clearErrorString();
return 0;
}
else {
setErrorString("invalid handle passed to dlclose()");
return -1;
}
}
VIS_HIDDEN
const mach_header* loadImageAndDependents(Diagnostics& diag, const launch_cache::binary_format::Image* imageToLoad, bool bumpDlopenCount)
{
launch_cache::Image topImage(imageToLoad);
uint32_t maxLoad = topImage.maxLoadCount();
const dyld3::launch_cache::binary_format::Image* fullImageList[maxLoad];
dyld3::launch_cache::SlowLoadSet imageSet(&fullImageList[0], &fullImageList[maxLoad]);
imageSet.add(imageToLoad);
STACK_ALLOC_DYNARRAY(const launch_cache::BinaryImageGroupData*, gAllImages.currentGroupsCount(), currentGroupsList);
gAllImages.copyCurrentGroups(currentGroupsList);
if ( !topImage.recurseAllDependentImages(currentGroupsList, imageSet, nullptr) ) {
diag.error("unexpected > %d images loaded", maxLoad);
return nullptr;
}
const dyld3::launch_cache::binary_format::Image* toLoadImageList[maxLoad];
const dyld3::launch_cache::binary_format::Image** toLoadImageArray = toLoadImageList;
__block int needToLoadCount = 0;
imageSet.forEach(^(const dyld3::launch_cache::binary_format::Image* aBinImage) {
if ( gAllImages.findLoadAddressByImage(aBinImage) == nullptr )
toLoadImageArray[needToLoadCount++] = aBinImage;
});
assert(needToLoadCount > 0);
uint32_t alreadyLoadImageCount = gAllImages.count();
STACK_ALLOC_DYNARRAY(loader::ImageInfo, alreadyLoadImageCount + needToLoadCount, allImages);
loader::ImageInfo* allImagesArray = &allImages[0];
gAllImages.forEachImage(^(uint32_t imageIndex, const mach_header* loadAddress, const launch_cache::Image image, bool& stop) {
launch_cache::ImageGroup grp = image.group();
loader::ImageInfo& info= allImagesArray[imageIndex];
info.imageData = image.binaryData();
info.loadAddress = loadAddress;
info.groupNum = grp.groupNum();
info.indexInGroup = grp.indexInGroup(info.imageData);
info.previouslyFixedUp = true;
info.justMapped = false;
info.justUsedFromDyldCache = false;
info.neverUnload = false;
});
for (int i=0; i < needToLoadCount; ++i) {
launch_cache::Image img(toLoadImageArray[i]);
launch_cache::ImageGroup grp = img.group();
loader::ImageInfo& info= allImages[alreadyLoadImageCount+i];
info.imageData = toLoadImageArray[i];
info.loadAddress = nullptr;
info.groupNum = grp.groupNum();
info.indexInGroup = grp.indexInGroup(img.binaryData());
info.previouslyFixedUp = false;
info.justMapped = false;
info.justUsedFromDyldCache = false;
info.neverUnload = false;
}
mapAndFixupImages(diag, allImages, (const uint8_t*)gAllImages.cacheLoadAddress(), &dyld3::log_loads, &dyld3::log_segments, &dyld3::log_fixups, &dyld3::log_dofs);
if ( diag.hasError() )
return nullptr;
const mach_header* topLoadAddress = allImages[alreadyLoadImageCount].loadAddress;
if ( bumpDlopenCount )
gAllImages.incRefCount(topLoadAddress);
dyld3::launch_cache::DynArray<loader::ImageInfo> newImages(needToLoadCount, &allImages[alreadyLoadImageCount]);
gAllImages.addImages(newImages);
for (int i=0; i < alreadyLoadImageCount; ++i) {
if (allImages[i].neverUnload && !allImages[i].imageData->neverUnload)
gAllImages.setNeverUnload(allImages[i]);
}
gAllImages.runInitialzersBottomUp(topLoadAddress);
return topLoadAddress;
}
void* dlopen(const char* path, int mode)
{
log_apis("dlopen(\"%s\", 0x%08X)\n", ((path==NULL) ? "NULL" : path), mode);
clearErrorString();
if ( path == NULL ) {
if ( (mode & RTLD_FIRST) != 0 )
return RTLD_MAIN_ONLY;
else
return RTLD_DEFAULT;
}
RecursiveAutoLock dlopenSerializer;
const char* leafName = strrchr(path, '/');
if ( leafName != nullptr )
++leafName;
else
leafName = path;
bool dontContinue = (mode & RTLD_FIRST);
bool bumpRefCount = true;
__block const mach_header* alreadyLoadMH = nullptr;
struct stat statBuf;
if ( stat(path, &statBuf) == 0 ) {
alreadyLoadMH = gAllImages.alreadyLoaded(statBuf.st_ino, statBuf.st_mtime, bumpRefCount);
if ( alreadyLoadMH != nullptr) {
log_apis(" dlopen: path inode/mtime matches already loaded image\n");
void* result = makeDlHandle(alreadyLoadMH, dontContinue);
log_apis(" dlopen(%s) => %p\n", leafName, result);
return result;
}
}
gPathOverrides.forEachPathVariant(path, ^(const char* possiblePath, bool& stop) {
alreadyLoadMH = gAllImages.alreadyLoaded(possiblePath, bumpRefCount);
if ( alreadyLoadMH != nullptr ) {
log_apis(" dlopen: matches already loaded image %s\n", possiblePath);
stop = true;
}
});
if ( alreadyLoadMH != nullptr) {
void* result = makeDlHandle(alreadyLoadMH, dontContinue);
log_apis(" dlopen(%s) => %p\n", leafName, result);
return result;
}
char resolvedPath[PATH_MAX];
const char* realPathResult = realpath(path, resolvedPath);
bool checkRealPathToo = ((realPathResult != nullptr) || (errno == ENOENT)) && (strcmp(path, resolvedPath) != 0);
if ( checkRealPathToo ) {
alreadyLoadMH = gAllImages.alreadyLoaded(resolvedPath, bumpRefCount);
log_apis(" dlopen: real path=%s\n", resolvedPath);
if ( alreadyLoadMH != nullptr) {
void* result = makeDlHandle(alreadyLoadMH, dontContinue);
log_apis(" dlopen(%s) => %p\n", leafName, result);
return result;
}
}
__block const launch_cache::binary_format::Image* imageToLoad = nullptr;
gPathOverrides.forEachPathVariant(path, ^(const char* possiblePath, bool& stop) {
log_apis(" dlopen: checking for pre-built closure for path: %s\n", possiblePath);
imageToLoad = gAllImages.findImageInKnownGroups(possiblePath);
if ( imageToLoad != nullptr )
stop = true;
});
if ( (imageToLoad == nullptr) && checkRealPathToo ) {
gPathOverrides.forEachPathVariant(resolvedPath, ^(const char* possiblePath, bool& stop) {
log_apis(" dlopen: checking for pre-built closure for real path: %s\n", possiblePath);
imageToLoad = gAllImages.findImageInKnownGroups(possiblePath);
if ( imageToLoad != nullptr )
stop = true;
});
}
if ( imageToLoad != nullptr ) {
alreadyLoadMH = gAllImages.alreadyLoaded(imageToLoad, bumpRefCount);
if ( alreadyLoadMH != nullptr) {
void* result = makeDlHandle(alreadyLoadMH, dontContinue);
log_apis(" dlopen(%s) => %p\n", leafName, result);
return result;
}
}
if ( mode & RTLD_NOLOAD ) {
log_apis(" dlopen(%s) => NULL\n", leafName);
return nullptr;
}
if ( imageToLoad != nullptr ) {
log_apis(" dlopen: trying existing closure image=%p\n", imageToLoad);
Diagnostics diag;
const mach_header* topLoadAddress = loadImageAndDependents(diag, imageToLoad, true);
if ( diag.noError() ) {
void* result = makeDlHandle(topLoadAddress, dontContinue);
log_apis(" dlopen(%s) => %p\n", leafName, result);
return result;
}
imageToLoad = nullptr;
log_apis(" dlopen: existing closure no longer valid\n");
}
const char* closuredErrorMessages[3];
int closuredErrorMessagesCount = 0;
if ( imageToLoad == nullptr ) {
imageToLoad = gAllImages.messageClosured(path, "dlopen", closuredErrorMessages, closuredErrorMessagesCount);
}
if ( imageToLoad != nullptr ) {
log_apis(" dlopen: using closured built image=%p\n", imageToLoad);
Diagnostics diag;
const mach_header* topLoadAddress = loadImageAndDependents(diag, imageToLoad, true);
if ( diag.noError() ) {
void* result = makeDlHandle(topLoadAddress, dontContinue);
log_apis(" dlopen(%s) => %p\n", leafName, result);
return result;
}
if ( closuredErrorMessagesCount < 3 ) {
closuredErrorMessages[closuredErrorMessagesCount++] = strdup(diag.errorMessage());
}
}
switch ( closuredErrorMessagesCount ) {
case 0:
setErrorString("dlopen(%s, 0x%04X): closured error", path, mode);
log_apis(" dlopen: closured error\n");
break;
case 1:
setErrorString("dlopen(%s, 0x%04X): %s", path, mode, closuredErrorMessages[0]);
log_apis(" dlopen: closured error: %s\n", closuredErrorMessages[0]);
break;
case 2:
setErrorString("dlopen(%s, 0x%04X): %s %s", path, mode, closuredErrorMessages[0], closuredErrorMessages[1]);
log_apis(" dlopen: closured error: %s %s\n", closuredErrorMessages[0], closuredErrorMessages[1]);
break;
case 3:
setErrorString("dlopen(%s, 0x%04X): %s %s %s", path, mode, closuredErrorMessages[0], closuredErrorMessages[1], closuredErrorMessages[2]);
log_apis(" dlopen: closured error: %s %s %s\n", closuredErrorMessages[0], closuredErrorMessages[1], closuredErrorMessages[2]);
break;
}
for (int i=0; i < closuredErrorMessagesCount;++i)
free((void*)closuredErrorMessages[i]);
log_apis(" dlopen(%s) => NULL\n", leafName);
return nullptr;
}
bool dlopen_preflight(const char* path)
{
log_apis("dlopen_preflight(%s)\n", path);
if ( gAllImages.alreadyLoaded(path, false) != nullptr )
return true;
if ( gAllImages.findImageInKnownGroups(path) != nullptr )
return true;
struct stat statBuf;
if ( ::stat(path, &statBuf) != 0 )
return false;
int fd = ::open(path, O_RDONLY);
if ( fd < 0 )
return false;
const void* fileBuffer = ::mmap(NULL, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
::close(fd);
if ( fileBuffer == MAP_FAILED )
return false;
size_t mappedSize = (size_t)statBuf.st_size;
__block bool result = false;
__block Diagnostics diag;
if ( MachOParser::isMachO(diag, fileBuffer, mappedSize) ) {
result = true;
}
else {
if ( FatUtil::isFatFile(fileBuffer) ) {
FatUtil::forEachSlice(diag, fileBuffer, mappedSize, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, size_t sliceSz, bool& stop) {
if ( MachOParser::isMachO(diag, sliceStart, sliceSz) ) {
result = true;
stop = true;
}
});
}
}
::munmap((void*)fileBuffer, mappedSize);
return result;
}
static void* dlsym_search(const char* symName, const mach_header* startImageLoadAddress, const launch_cache::Image& startImage, bool searchStartImage, MachOParser::DependentFinder reExportFollower)
{
uint32_t maxLoad = startImage.maxLoadCount();
const dyld3::launch_cache::binary_format::Image* fullImageList[maxLoad];
dyld3::launch_cache::SlowLoadSet imageSet(&fullImageList[0], &fullImageList[maxLoad]);
imageSet.add(startImage.binaryData());
STACK_ALLOC_DYNARRAY(const launch_cache::BinaryImageGroupData*, gAllImages.currentGroupsCount(), currentGroupsList);
gAllImages.copyCurrentGroups(currentGroupsList);
__block void* result = nullptr;
auto handler = ^(const dyld3::launch_cache::binary_format::Image* aBinImage, bool& stop) {
const mach_header* loadAddress = gAllImages.findLoadAddressByImage(aBinImage);
if ( !searchStartImage && (loadAddress == startImageLoadAddress) )
return;
if ( loadAddress != nullptr ) {
MachOParser parser(loadAddress);
if ( parser.hasExportedSymbol(symName, reExportFollower, &result) ) {
stop = true;
}
}
};
bool stop = false;
handler(startImage.binaryData(), stop);
if (stop)
return result;
if ( !startImage.recurseAllDependentImages(currentGroupsList, imageSet, handler) ) {
setErrorString("unexpected > %d images loaded", maxLoad);
return nullptr;
}
return result;
}
void* dlsym(void* handle, const char* symbolName)
{
log_apis("dlsym(%p, \"%s\")\n", handle, symbolName);
clearErrorString();
char underscoredName[strlen(symbolName)+2];
underscoredName[0] = '_';
strcpy(&underscoredName[1], symbolName);
MachOParser::DependentFinder reExportFollower = ^(uint32_t targetDepIndex, const char* depLoadPath, void* extra, const mach_header** foundMH, void** foundExtra) {
if ( (strncmp(depLoadPath, "@rpath/", 7) == 0) && (extra != nullptr) ) {
const mach_header* parentMH = (mach_header*)extra;
launch_cache::Image parentImage = gAllImages.findByLoadAddress(parentMH);
if ( parentImage.valid() ) {
STACK_ALLOC_DYNARRAY(const launch_cache::BinaryImageGroupData*, gAllImages.currentGroupsCount(), currentGroupsList);
gAllImages.copyCurrentGroups(currentGroupsList);
parentImage.forEachDependentImage(currentGroupsList, ^(uint32_t parentDepIndex, dyld3::launch_cache::Image parentDepImage, dyld3::launch_cache::Image::LinkKind kind, bool &stop) {
if ( parentDepIndex != targetDepIndex )
return;
const mach_header* parentDepMH = gAllImages.findLoadAddressByImage(parentDepImage.binaryData());
if ( parentDepMH != nullptr ) {
*foundMH = parentDepMH;
stop = true;
}
});
}
}
else {
*foundMH = gAllImages.alreadyLoaded(depLoadPath, false);
}
return (*foundMH != nullptr);
};
if ( handle == RTLD_DEFAULT ) {
for (uint32_t index=0; index < gAllImages.count(); ++index) {
const mach_header* loadAddress;
launch_cache::Image image = gAllImages.findByLoadOrder(index, &loadAddress);
if ( image.valid() ) {
MachOParser parser(loadAddress);
void* result;
if ( parser.hasExportedSymbol(underscoredName, reExportFollower, &result) ) {
log_apis(" dlsym() => %p\n", result);
return result;
}
}
}
setErrorString("dlsym(RTLD_DEFAULT, %s): symbol not found", symbolName);
log_apis(" dlsym() => NULL\n");
return nullptr;
}
else if ( handle == RTLD_MAIN_ONLY ) {
MachOParser parser(gAllImages.mainExecutable());
void* result;
if ( parser.hasExportedSymbol(underscoredName, reExportFollower, &result) ) {
log_apis(" dlsym() => %p\n", result);
return result;
}
setErrorString("dlsym(RTLD_MAIN_ONLY, %s): symbol not found", symbolName);
log_apis(" dlsym() => NULL\n");
return nullptr;
}
const mach_header* startImageLoadAddress;
launch_cache::Image startImage(nullptr);
void* result = nullptr;
if ( handle == RTLD_NEXT ) {
void* callerAddress = __builtin_return_address(0);
startImage = gAllImages.findByOwnedAddress(callerAddress, &startImageLoadAddress);
if ( ! startImage.valid() ) {
setErrorString("dlsym(RTLD_NEXT, %s): called by unknown image (caller=%p)", symbolName, callerAddress);
return nullptr;
}
result = dlsym_search(underscoredName, startImageLoadAddress, startImage, false, reExportFollower);
}
else if ( handle == RTLD_SELF ) {
void* callerAddress = __builtin_return_address(0);
startImage = gAllImages.findByOwnedAddress(callerAddress, &startImageLoadAddress);
if ( ! startImage.valid() ) {
setErrorString("dlsym(RTLD_SELF, %s): called by unknown image (caller=%p)", symbolName, callerAddress);
return nullptr;
}
result = dlsym_search(underscoredName, startImageLoadAddress, startImage, true, reExportFollower);
}
else {
bool dontContinue;
parseDlHandle(handle, &startImageLoadAddress, &dontContinue);
startImage = gAllImages.findByLoadAddress(startImageLoadAddress);
if ( !startImage.valid() ) {
setErrorString("dlsym(%p, %s): invalid handle", handle, symbolName);
log_apis(" dlsym() => NULL\n");
return nullptr;
}
if ( dontContinue ) {
MachOParser parser(startImageLoadAddress);
parser.hasExportedSymbol(underscoredName, reExportFollower, &result);
}
else {
result = dlsym_search(underscoredName, startImageLoadAddress, startImage, true, reExportFollower);
}
}
if ( result != nullptr ) {
log_apis(" dlsym() => %p\n", result);
return result;
}
setErrorString("dlsym(%p, %s): symbol not found", handle, symbolName);
log_apis(" dlsym() => NULL\n");
return nullptr;
}
const struct dyld_all_image_infos* _dyld_get_all_image_infos()
{
return gAllImages.oldAllImageInfo();
}
bool dyld_shared_cache_some_image_overridden()
{
log_apis("dyld_shared_cache_some_image_overridden()\n");
assert(0 && "not implemented yet");
}
bool _dyld_get_shared_cache_uuid(uuid_t uuid)
{
log_apis("_dyld_get_shared_cache_uuid()\n");
if ( gAllImages.oldAllImageInfo() != nullptr ) {
memcpy(uuid, gAllImages.oldAllImageInfo()->sharedCacheUUID, sizeof(uuid_t));
return true;
}
return false;
}
const void* _dyld_get_shared_cache_range(size_t* mappedSize)
{
log_apis("_dyld_get_shared_cache_range()\n");
const DyldSharedCache* sharedCache = (DyldSharedCache*)gAllImages.cacheLoadAddress();
if ( sharedCache != nullptr ) {
*mappedSize = (size_t)sharedCache->mappedSize();
return sharedCache;
}
*mappedSize = 0;
return NULL;
}
bool _dyld_find_unwind_sections(void* addr, dyld_unwind_sections* info)
{
log_apis("_dyld_find_unwind_sections(%p, %p)\n", addr, info);
const mach_header* mh = dyld_image_header_containing_address(addr);
if ( mh == nullptr )
return false;
info->mh = mh;
info->dwarf_section = nullptr;
info->dwarf_section_length = 0;
info->compact_unwind_section = nullptr;
info->compact_unwind_section_length = 0;
MachOParser parser(mh);
parser.forEachSection(^(const char* segName, const char* sectName, uint32_t flags, const void* content, size_t sectSize, bool illegalSectionSize, bool& stop) {
if ( strcmp(segName, "__TEXT") == 0 ) {
if ( strcmp(sectName, "__eh_frame") == 0 ) {
info->dwarf_section = content;
info->dwarf_section_length = sectSize;
}
else if ( strcmp(sectName, "__unwind_info") == 0 ) {
info->compact_unwind_section = content;
info->compact_unwind_section_length = sectSize;
}
}
});
return true;
}
bool dyld_process_is_restricted()
{
log_apis("dyld_process_is_restricted()\n");
launch_cache::Closure closure(gAllImages.mainClosure());
return closure.isRestricted();
}
const char* dyld_shared_cache_file_path()
{
log_apis("dyld_shared_cache_file_path()\n");
return gAllImages.dyldCachePath();
}
void dyld_dynamic_interpose(const mach_header* mh, const dyld_interpose_tuple array[], size_t count)
{
log_apis("dyld_dynamic_interpose(%p, %p, %lu)\n", mh, array, count);
}
static void* mapStartOfCache(const char* path, size_t length)
{
struct stat statbuf;
if ( ::stat(path, &statbuf) == -1 )
return NULL;
if ( statbuf.st_size < length )
return NULL;
int cache_fd = ::open(path, O_RDONLY);
if ( cache_fd < 0 )
return NULL;
void* result = ::mmap(NULL, length, PROT_READ, MAP_PRIVATE, cache_fd, 0);
close(cache_fd);
if ( result == MAP_FAILED )
return NULL;
return result;
}
static const DyldSharedCache* findCacheInDirAndMap(const uuid_t cacheUuid, const char* dirPath, size_t& sizeMapped)
{
DIR* dirp = ::opendir(dirPath);
if ( dirp != NULL) {
dirent entry;
dirent* entp = NULL;
char cachePath[PATH_MAX];
while ( ::readdir_r(dirp, &entry, &entp) == 0 ) {
if ( entp == NULL )
break;
if ( entp->d_type != DT_REG )
continue;
if ( strlcpy(cachePath, dirPath, PATH_MAX) >= PATH_MAX )
continue;
if ( strlcat(cachePath, "/", PATH_MAX) >= PATH_MAX )
continue;
if ( strlcat(cachePath, entp->d_name, PATH_MAX) >= PATH_MAX )
continue;
if ( const DyldSharedCache* cache = (DyldSharedCache*)mapStartOfCache(cachePath, 0x00100000) ) {
uuid_t foundUuid;
cache->getUUID(foundUuid);
if ( ::memcmp(foundUuid, cacheUuid, 16) != 0 ) {
::munmap((void*)cache, 0x00100000);
}
else {
closedir(dirp);
sizeMapped = 0x00100000;
return cache;
}
}
}
closedir(dirp);
}
return nullptr;
}
int dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extraSearchDirs[], void (^callback)(const dyld_shared_cache_dylib_text_info* info))
{
log_apis("dyld_shared_cache_find_iterate_text()\n");
size_t sizeMapped = 0;
const DyldSharedCache* sharedCache = (DyldSharedCache*)gAllImages.cacheLoadAddress();
if ( sharedCache != nullptr ) {
uuid_t runningUuid;
sharedCache->getUUID(runningUuid);
if ( ::memcmp(runningUuid, cacheUuid, 16) != 0 )
sharedCache = nullptr;
}
if ( sharedCache == nullptr ) {
#if __IPHONE_OS_VERSION_MIN_REQUIRED
const char* defaultSearchDir = IPHONE_DYLD_SHARED_CACHE_DIR;
#else
const char* defaultSearchDir = MACOSX_DYLD_SHARED_CACHE_DIR;
#endif
sharedCache = findCacheInDirAndMap(cacheUuid, defaultSearchDir, sizeMapped);
if ( sharedCache == nullptr ) {
for (const char** p = extraSearchDirs; *p != nullptr; ++p) {
sharedCache = findCacheInDirAndMap(cacheUuid, *p, sizeMapped);
if ( sharedCache != nullptr )
break;
}
}
}
if ( sharedCache == nullptr )
return -1;
__block uint64_t cacheUnslidBaseAddress = 0;
sharedCache->forEachRegion(^(const void *content, uint64_t vmAddr, uint64_t size, uint32_t permissions) {
if ( cacheUnslidBaseAddress == 0 )
cacheUnslidBaseAddress = vmAddr;
});
sharedCache->forEachImageTextSegment(^(uint64_t loadAddressUnslid, uint64_t textSegmentSize, const uuid_t dylibUUID, const char* installName) {
dyld_shared_cache_dylib_text_info dylibTextInfo;
dylibTextInfo.version = 2;
dylibTextInfo.loadAddressUnslid = loadAddressUnslid;
dylibTextInfo.textSegmentSize = textSegmentSize;
dylibTextInfo.path = installName;
::memcpy(dylibTextInfo.dylibUuid, dylibUUID, 16);
dylibTextInfo.textSegmentOffset = loadAddressUnslid - cacheUnslidBaseAddress;
callback(&dylibTextInfo);
});
if ( sizeMapped != 0 )
::munmap((void*)sharedCache, sizeMapped);
return 0;
}
int dyld_shared_cache_iterate_text(const uuid_t cacheUuid, void (^callback)(const dyld_shared_cache_dylib_text_info* info))
{
log_apis("dyld_shared_cache_iterate_text()\n");
const char* extraSearchDirs[] = { NULL };
return dyld3::dyld_shared_cache_find_iterate_text(cacheUuid, extraSearchDirs, callback);
}
}