Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
dsc_extractor_badly/src/dyld.cpp
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
6053 lines (5486 sloc)
208 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- | |
| * | |
| * Copyright (c) 2004-2013 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 <stdint.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <dirent.h> | |
| #include <pthread.h> | |
| #include <libproc.h> | |
| #include <sys/param.h> | |
| #include <mach/mach_time.h> // mach_absolute_time() | |
| #include <mach/mach_init.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <sys/syscall.h> | |
| #include <sys/socket.h> | |
| #include <sys/un.h> | |
| #include <sys/syslog.h> | |
| #include <sys/uio.h> | |
| #include <mach-o/fat.h> | |
| #include <mach-o/loader.h> | |
| #include <mach-o/ldsyms.h> | |
| #include <libkern/OSByteOrder.h> | |
| #include <libkern/OSAtomic.h> | |
| #include <mach/mach.h> | |
| #include <sys/sysctl.h> | |
| #include <sys/mman.h> | |
| #include <sys/dtrace.h> | |
| #include <libkern/OSAtomic.h> | |
| #include <Availability.h> | |
| #include <System/sys/codesign.h> | |
| #include <System/sys/csr.h> | |
| #include <_simple.h> | |
| #include <os/lock_private.h> | |
| #include <System/machine/cpu_capabilities.h> | |
| #include <System/sys/reason.h> | |
| #include <kern/kcdata.h> | |
| #include <sandbox.h> | |
| #include <sandbox/private.h> | |
| #include <array> | |
| #ifndef CPU_SUBTYPE_ARM_V5TEJ | |
| #define CPU_SUBTYPE_ARM_V5TEJ ((cpu_subtype_t) 7) | |
| #endif | |
| #ifndef CPU_SUBTYPE_ARM_XSCALE | |
| #define CPU_SUBTYPE_ARM_XSCALE ((cpu_subtype_t) 8) | |
| #endif | |
| #ifndef CPU_SUBTYPE_ARM_V7 | |
| #define CPU_SUBTYPE_ARM_V7 ((cpu_subtype_t) 9) | |
| #endif | |
| #ifndef CPU_SUBTYPE_ARM_V7F | |
| #define CPU_SUBTYPE_ARM_V7F ((cpu_subtype_t) 10) | |
| #endif | |
| #ifndef CPU_SUBTYPE_ARM_V7S | |
| #define CPU_SUBTYPE_ARM_V7S ((cpu_subtype_t) 11) | |
| #endif | |
| #ifndef CPU_SUBTYPE_ARM_V7K | |
| #define CPU_SUBTYPE_ARM_V7K ((cpu_subtype_t) 12) | |
| #endif | |
| #ifndef LC_DYLD_ENVIRONMENT | |
| #define LC_DYLD_ENVIRONMENT 0x27 | |
| #endif | |
| #ifndef CPU_SUBTYPE_X86_64_H | |
| #define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t) 8) | |
| #endif | |
| #ifndef VM_PROT_SLIDE | |
| #define VM_PROT_SLIDE 0x20 | |
| #endif | |
| #include <vector> | |
| #include <algorithm> | |
| #include "mach-o/dyld_gdb.h" | |
| #include "dyld.h" | |
| #include "ImageLoader.h" | |
| #include "ImageLoaderMachO.h" | |
| #include "dyldLibSystemInterface.h" | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| #include "dyld_cache_format.h" | |
| #endif | |
| #include "dyld_process_info_internal.h" | |
| #include <coreSymbolicationDyldSupport.h> | |
| #if TARGET_IPHONE_SIMULATOR | |
| extern "C" void xcoresymbolication_load_notifier(void *connection, uint64_t load_timestamp, const char *image_path, const struct mach_header *mach_header); | |
| extern "C" void xcoresymbolication_unload_notifier(void *connection, uint64_t unload_timestamp, const char *image_path, const struct mach_header *mach_header); | |
| #define coresymbolication_load_notifier(c, t, p, h) xcoresymbolication_load_notifier(c, t, p, h) | |
| #define coresymbolication_unload_notifier(c, t, p, h) xcoresymbolication_unload_notifier(c, t, p, h) | |
| #endif | |
| #if SUPPORT_ACCELERATE_TABLES | |
| #include "ImageLoaderMegaDylib.h" | |
| #endif | |
| #if TARGET_IPHONE_SIMULATOR | |
| extern "C" void* gSyscallHelpers; | |
| #else | |
| #include "dyldSyscallInterface.h" | |
| #endif | |
| // not libc header for send() syscall interface | |
| extern "C" ssize_t __sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); | |
| // ARM and x86_64 are the only architecture that use cpu-sub-types | |
| #define CPU_SUBTYPES_SUPPORTED ((__arm__ || __x86_64__) && !TARGET_IPHONE_SIMULATOR) | |
| #if __LP64__ | |
| #define LC_SEGMENT_COMMAND LC_SEGMENT_64 | |
| #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT | |
| #define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO | |
| #define macho_segment_command segment_command_64 | |
| #define macho_section section_64 | |
| #else | |
| #define LC_SEGMENT_COMMAND LC_SEGMENT | |
| #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64 | |
| #define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO_64 | |
| #define macho_segment_command segment_command | |
| #define macho_section section | |
| #endif | |
| #define CPU_TYPE_MASK 0x00FFFFFF /* complement of CPU_ARCH_MASK */ | |
| /* implemented in dyld_gdb.cpp */ | |
| extern void resetAllImages(); | |
| extern void addImagesToAllImages(uint32_t infoCount, const dyld_image_info info[]); | |
| extern void removeImageFromAllImages(const mach_header* mh); | |
| extern void addNonSharedCacheImageUUID(const dyld_uuid_info& info); | |
| extern const char* notifyGDB(enum dyld_image_states state, uint32_t infoCount, const dyld_image_info info[]); | |
| extern size_t allImagesCount(); | |
| // magic so CrashReporter logs message | |
| extern "C" { | |
| char error_string[1024]; | |
| } | |
| // magic linker symbol for start of dyld binary | |
| extern "C" const macho_header __dso_handle; | |
| // | |
| // The file contains the core of dyld used to get a process to main(). | |
| // The API's that dyld supports are implemented in dyldAPIs.cpp. | |
| // | |
| // | |
| // | |
| // | |
| // | |
| namespace dyld { | |
| struct RegisteredDOF { const mach_header* mh; int registrationID; }; | |
| struct DylibOverride { const char* installName; const char* override; }; | |
| } | |
| VECTOR_NEVER_DESTRUCTED(ImageLoader*); | |
| VECTOR_NEVER_DESTRUCTED(dyld::RegisteredDOF); | |
| VECTOR_NEVER_DESTRUCTED(dyld::ImageCallback); | |
| VECTOR_NEVER_DESTRUCTED(dyld::DylibOverride); | |
| VECTOR_NEVER_DESTRUCTED(ImageLoader::DynamicReference); | |
| VECTOR_NEVER_DESTRUCTED(dyld_image_state_change_handler); | |
| namespace dyld { | |
| // | |
| // state of all environment variables dyld uses | |
| // | |
| struct EnvironmentVariables { | |
| const char* const * DYLD_FRAMEWORK_PATH; | |
| const char* const * DYLD_FALLBACK_FRAMEWORK_PATH; | |
| const char* const * DYLD_LIBRARY_PATH; | |
| const char* const * DYLD_FALLBACK_LIBRARY_PATH; | |
| const char* const * DYLD_INSERT_LIBRARIES; | |
| const char* const * LD_LIBRARY_PATH; // for unix conformance | |
| const char* const * DYLD_VERSIONED_LIBRARY_PATH; | |
| const char* const * DYLD_VERSIONED_FRAMEWORK_PATH; | |
| bool DYLD_PRINT_LIBRARIES_POST_LAUNCH; | |
| bool DYLD_BIND_AT_LAUNCH; | |
| bool DYLD_PRINT_STATISTICS; | |
| bool DYLD_PRINT_STATISTICS_DETAILS; | |
| bool DYLD_PRINT_OPTS; | |
| bool DYLD_PRINT_ENV; | |
| bool DYLD_DISABLE_DOFS; | |
| bool DYLD_PRINT_CS_NOTIFICATIONS; | |
| // DYLD_SHARED_CACHE_DONT_VALIDATE ==> sSharedCacheIgnoreInodeAndTimeStamp | |
| // DYLD_SHARED_CACHE_DIR ==> sSharedCacheDir | |
| // DYLD_ROOT_PATH ==> gLinkContext.rootPaths | |
| // DYLD_IMAGE_SUFFIX ==> gLinkContext.imageSuffix | |
| // DYLD_PRINT_OPTS ==> gLinkContext.verboseOpts | |
| // DYLD_PRINT_ENV ==> gLinkContext.verboseEnv | |
| // DYLD_FORCE_FLAT_NAMESPACE ==> gLinkContext.bindFlat | |
| // DYLD_PRINT_INITIALIZERS ==> gLinkContext.verboseInit | |
| // DYLD_PRINT_SEGMENTS ==> gLinkContext.verboseMapping | |
| // DYLD_PRINT_BINDINGS ==> gLinkContext.verboseBind | |
| // DYLD_PRINT_WEAK_BINDINGS ==> gLinkContext.verboseWeakBind | |
| // DYLD_PRINT_REBASINGS ==> gLinkContext.verboseRebase | |
| // DYLD_PRINT_DOFS ==> gLinkContext.verboseDOF | |
| // DYLD_PRINT_APIS ==> gLogAPIs | |
| // DYLD_IGNORE_PREBINDING ==> gLinkContext.prebindUsage | |
| // DYLD_PREBIND_DEBUG ==> gLinkContext.verbosePrebinding | |
| // DYLD_NEW_LOCAL_SHARED_REGIONS ==> gLinkContext.sharedRegionMode | |
| // DYLD_SHARED_REGION ==> gLinkContext.sharedRegionMode | |
| // DYLD_PRINT_WARNINGS ==> gLinkContext.verboseWarnings | |
| // DYLD_PRINT_RPATHS ==> gLinkContext.verboseRPaths | |
| // DYLD_PRINT_INTERPOSING ==> gLinkContext.verboseInterposing | |
| // DYLD_PRINT_LIBRARIES ==> gLinkContext.verboseLoading | |
| }; | |
| typedef std::vector<dyld_image_state_change_handler> StateHandlers; | |
| enum EnvVarMode { envNone, envPrintOnly, envAll }; | |
| // all global state | |
| static const char* sExecPath = NULL; | |
| static const char* sExecShortName = NULL; | |
| static const macho_header* sMainExecutableMachHeader = NULL; | |
| #if CPU_SUBTYPES_SUPPORTED | |
| static cpu_type_t sHostCPU; | |
| static cpu_subtype_t sHostCPUsubtype; | |
| #endif | |
| static ImageLoaderMachO* sMainExecutable = NULL; | |
| static EnvVarMode sEnvMode = envNone; | |
| static size_t sInsertedDylibCount = 0; | |
| static std::vector<ImageLoader*> sAllImages; | |
| static std::vector<ImageLoader*> sImageRoots; | |
| static std::vector<ImageLoader*> sImageFilesNeedingTermination; | |
| static std::vector<RegisteredDOF> sImageFilesNeedingDOFUnregistration; | |
| static std::vector<ImageCallback> sAddImageCallbacks; | |
| static std::vector<ImageCallback> sRemoveImageCallbacks; | |
| static bool sRemoveImageCallbacksInUse = false; | |
| static void* sSingleHandlers[7][3]; | |
| static void* sBatchHandlers[7][3]; | |
| static ImageLoader* sLastImageByAddressCache; | |
| static EnvironmentVariables sEnv; | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| static const char* sFrameworkFallbackPaths[] = { "$HOME/Library/Frameworks", "/Library/Frameworks", "/Network/Library/Frameworks", "/System/Library/Frameworks", NULL }; | |
| static const char* sLibraryFallbackPaths[] = { "$HOME/lib", "/usr/local/lib", "/usr/lib", NULL }; | |
| #else | |
| static const char* sFrameworkFallbackPaths[] = { "/System/Library/Frameworks", NULL }; | |
| static const char* sLibraryFallbackPaths[] = { "/usr/local/lib", "/usr/lib", NULL }; | |
| #endif | |
| static const char* sRestrictedFrameworkFallbackPaths[] = { "/System/Library/Frameworks", NULL }; | |
| static const char* sRestrictedLibraryFallbackPaths[] = { "/usr/lib", NULL }; | |
| static UndefinedHandler sUndefinedHandler = NULL; | |
| static ImageLoader* sBundleBeingLoaded = NULL; // hack until OFI is reworked | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| static const dyld_cache_header* sSharedCache = NULL; | |
| static long sSharedCacheSlide = 0; | |
| static bool sSharedCacheIgnoreInodeAndTimeStamp = false; | |
| bool gSharedCacheOverridden = false; | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| static const char* sSharedCacheDir = IPHONE_DYLD_SHARED_CACHE_DIR; | |
| #define ENABLE_DYLIBS_TO_OVERRIDE_CACHE_SIZE 1024 | |
| #else | |
| static const char* sSharedCacheDir = MACOSX_DYLD_SHARED_CACHE_DIR; | |
| #endif | |
| #endif | |
| ImageLoader::LinkContext gLinkContext; | |
| bool gLogAPIs = false; | |
| #if SUPPORT_ACCELERATE_TABLES | |
| bool gLogAppAPIs = false; | |
| #endif | |
| const struct LibSystemHelpers* gLibSystemHelpers = NULL; | |
| #if SUPPORT_OLD_CRT_INITIALIZATION | |
| bool gRunInitializersOldWay = false; | |
| #endif | |
| static std::vector<DylibOverride> sDylibOverrides; | |
| #if !TARGET_IPHONE_SIMULATOR | |
| static int sLogSocket = -1; | |
| #endif | |
| static bool sFrameworksFoundAsDylibs = false; | |
| #if __x86_64__ && DYLD_SHARED_CACHE_SUPPORT | |
| static bool sHaswell = false; | |
| #endif | |
| static std::vector<ImageLoader::DynamicReference> sDynamicReferences; | |
| static OSSpinLock sDynamicReferencesLock = 0; | |
| #if !TARGET_IPHONE_SIMULATOR | |
| static bool sLogToFile = false; | |
| #endif | |
| static char sLoadingCrashMessage[1024] = "dyld: launch, loading dependent libraries"; | |
| static _dyld_objc_notify_mapped sNotifyObjCMapped; | |
| static _dyld_objc_notify_init sNotifyObjCInit; | |
| static _dyld_objc_notify_unmapped sNotifyObjCUnmapped; | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR | |
| static bool sForceStderr = false; | |
| #endif | |
| #if SUPPORT_ACCELERATE_TABLES | |
| static ImageLoaderMegaDylib* sAllCacheImagesProxy = NULL; | |
| static bool sDisableAcceleratorTables = false; | |
| #endif | |
| // | |
| // The MappedRanges structure is used for fast address->image lookups. | |
| // The table is only updated when the dyld lock is held, so we don't | |
| // need to worry about multiple writers. But readers may look at this | |
| // data without holding the lock. Therefore, all updates must be done | |
| // in an order that will never cause readers to see inconsistent data. | |
| // The general rule is that if the image field is non-NULL then | |
| // the other fields are valid. | |
| // | |
| struct MappedRanges | |
| { | |
| MappedRanges* next; | |
| unsigned long count; | |
| struct { | |
| ImageLoader* image; | |
| uintptr_t start; | |
| uintptr_t end; | |
| } array[1]; | |
| }; | |
| static MappedRanges* sMappedRangesStart; | |
| void addMappedRange(ImageLoader* image, uintptr_t start, uintptr_t end) | |
| { | |
| //dyld::log("addMappedRange(0x%lX->0x%lX) for %s\n", start, end, image->getShortName()); | |
| for (MappedRanges* p = sMappedRangesStart; p != NULL; p = p->next) { | |
| for (unsigned long i=0; i < p->count; ++i) { | |
| if ( p->array[i].image == NULL ) { | |
| p->array[i].start = start; | |
| p->array[i].end = end; | |
| // add image field last with a barrier so that any reader will see consistent records | |
| OSMemoryBarrier(); | |
| p->array[i].image = image; | |
| return; | |
| } | |
| } | |
| } | |
| // table must be full, chain another | |
| #if SUPPORT_ACCELERATE_TABLES | |
| unsigned count = (sAllCacheImagesProxy != NULL) ? 16 : 400; | |
| #else | |
| unsigned count = 400; | |
| #endif | |
| size_t allocationSize = sizeof(MappedRanges) + (count-1)*3*sizeof(void*); | |
| MappedRanges* newRanges = (MappedRanges*)malloc(allocationSize); | |
| bzero(newRanges, allocationSize); | |
| newRanges->count = count; | |
| newRanges->array[0].start = start; | |
| newRanges->array[0].end = end; | |
| newRanges->array[0].image = image; | |
| OSMemoryBarrier(); | |
| if ( sMappedRangesStart == NULL ) { | |
| sMappedRangesStart = newRanges; | |
| } | |
| else { | |
| for (MappedRanges* p = sMappedRangesStart; p != NULL; p = p->next) { | |
| if ( p->next == NULL ) { | |
| OSMemoryBarrier(); | |
| p->next = newRanges; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| void removedMappedRanges(ImageLoader* image) | |
| { | |
| for (MappedRanges* p = sMappedRangesStart; p != NULL; p = p->next) { | |
| for (unsigned long i=0; i < p->count; ++i) { | |
| if ( p->array[i].image == image ) { | |
| // clear with a barrier so that any reader will see consistent records | |
| OSMemoryBarrier(); | |
| p->array[i].image = NULL; | |
| } | |
| } | |
| } | |
| } | |
| ImageLoader* findMappedRange(uintptr_t target) | |
| { | |
| for (MappedRanges* p = sMappedRangesStart; p != NULL; p = p->next) { | |
| for (unsigned long i=0; i < p->count; ++i) { | |
| if ( p->array[i].image != NULL ) { | |
| if ( (p->array[i].start <= target) && (target < p->array[i].end) ) | |
| return p->array[i].image; | |
| } | |
| } | |
| } | |
| return NULL; | |
| } | |
| const char* mkstringf(const char* format, ...) | |
| { | |
| _SIMPLE_STRING buf = _simple_salloc(); | |
| if ( buf != NULL ) { | |
| va_list list; | |
| va_start(list, format); | |
| _simple_vsprintf(buf, format, list); | |
| va_end(list); | |
| const char* t = strdup(_simple_string(buf)); | |
| _simple_sfree(buf); | |
| if ( t != NULL ) | |
| return t; | |
| } | |
| return "mkstringf, out of memory error"; | |
| } | |
| void throwf(const char* format, ...) | |
| { | |
| _SIMPLE_STRING buf = _simple_salloc(); | |
| if ( buf != NULL ) { | |
| va_list list; | |
| va_start(list, format); | |
| _simple_vsprintf(buf, format, list); | |
| va_end(list); | |
| const char* t = strdup(_simple_string(buf)); | |
| _simple_sfree(buf); | |
| if ( t != NULL ) | |
| throw t; | |
| } | |
| throw "throwf, out of memory error"; | |
| } | |
| #if !TARGET_IPHONE_SIMULATOR | |
| static int sLogfile = STDERR_FILENO; | |
| #endif | |
| #if !TARGET_IPHONE_SIMULATOR | |
| // based on CFUtilities.c: also_do_stderr() | |
| static bool useSyslog() | |
| { | |
| // Use syslog() for processes managed by launchd | |
| static bool launchdChecked = false; | |
| static bool launchdOwned = false; | |
| if ( !launchdChecked && gProcessInfo->libSystemInitialized ) { | |
| if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 11) ) { | |
| // <rdar://problem/23520449> only call isLaunchdOwned() after libSystem is initialized | |
| launchdOwned = (*gLibSystemHelpers->isLaunchdOwned)(); | |
| launchdChecked = true; | |
| } | |
| } | |
| if ( launchdChecked && launchdOwned ) | |
| return true; | |
| // If stderr is not available, use syslog() | |
| struct stat sb; | |
| int result = fstat(STDERR_FILENO, &sb); | |
| if ( result < 0 ) | |
| return true; // file descriptor 2 is closed | |
| return false; | |
| } | |
| static void socket_syslogv(int priority, const char* format, va_list list) | |
| { | |
| // lazily create socket and connection to syslogd | |
| if ( sLogSocket == -1 ) { | |
| sLogSocket = ::socket(AF_UNIX, SOCK_DGRAM, 0); | |
| if (sLogSocket == -1) | |
| return; // cannot log | |
| ::fcntl(sLogSocket, F_SETFD, 1); | |
| struct sockaddr_un addr; | |
| addr.sun_family = AF_UNIX; | |
| strncpy(addr.sun_path, _PATH_LOG, sizeof(addr.sun_path)); | |
| if ( ::connect(sLogSocket, (struct sockaddr *)&addr, sizeof(addr)) == -1 ) { | |
| ::close(sLogSocket); | |
| sLogSocket = -1; | |
| return; | |
| } | |
| } | |
| // format message to syslogd like: "<priority>Process[pid]: message" | |
| _SIMPLE_STRING buf = _simple_salloc(); | |
| if ( buf == NULL ) | |
| return; | |
| if ( _simple_sprintf(buf, "<%d>%s[%d]: ", LOG_USER|LOG_NOTICE, sExecShortName, getpid()) == 0 ) { | |
| if ( _simple_vsprintf(buf, format, list) == 0 ) { | |
| const char* p = _simple_string(buf); | |
| ::__sendto(sLogSocket, p, strlen(p), 0, NULL, 0); | |
| } | |
| } | |
| _simple_sfree(buf); | |
| } | |
| void vlog(const char* format, va_list list) | |
| { | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR | |
| // <rdar://problem/25965832> log to console when running iOS app from Xcode | |
| if ( !sLogToFile && !sForceStderr && useSyslog() ) | |
| #else | |
| if ( !sLogToFile && useSyslog() ) | |
| #endif | |
| socket_syslogv(LOG_ERR, format, list); | |
| else { | |
| _simple_vdprintf(sLogfile, format, list); | |
| } | |
| } | |
| void log(const char* format, ...) | |
| { | |
| va_list list; | |
| va_start(list, format); | |
| vlog(format, list); | |
| va_end(list); | |
| } | |
| void vwarn(const char* format, va_list list) | |
| { | |
| _simple_dprintf(sLogfile, "dyld: warning, "); | |
| _simple_vdprintf(sLogfile, format, list); | |
| } | |
| void warn(const char* format, ...) | |
| { | |
| va_list list; | |
| va_start(list, format); | |
| vwarn(format, list); | |
| va_end(list); | |
| } | |
| #endif // !TARGET_IPHONE_SIMULATOR | |
| // <rdar://problem/8867781> control access to sAllImages through a lock | |
| // because global dyld lock is not held during initialization phase of dlopen() | |
| // <rdar://problem/16145518> Use OSSpinLockLock to allow yielding | |
| static OSSpinLock sAllImagesLock = 0; | |
| static void allImagesLock() | |
| { | |
| OSSpinLockLock(&sAllImagesLock); | |
| } | |
| static void allImagesUnlock() | |
| { | |
| OSSpinLockUnlock(&sAllImagesLock); | |
| } | |
| // utility class to assure files are closed when an exception is thrown | |
| class FileOpener { | |
| public: | |
| FileOpener(const char* path); | |
| ~FileOpener(); | |
| int getFileDescriptor() { return fd; } | |
| private: | |
| int fd; | |
| }; | |
| FileOpener::FileOpener(const char* path) | |
| : fd(-1) | |
| { | |
| fd = my_open(path, O_RDONLY, 0); | |
| } | |
| FileOpener::~FileOpener() | |
| { | |
| if ( fd != -1 ) | |
| close(fd); | |
| } | |
| static void registerDOFs(const std::vector<ImageLoader::DOFInfo>& dofs) | |
| { | |
| const size_t dofSectionCount = dofs.size(); | |
| if ( !sEnv.DYLD_DISABLE_DOFS && (dofSectionCount != 0) ) { | |
| int fd = open("/dev/" DTRACEMNR_HELPER, O_RDWR); | |
| if ( fd < 0 ) { | |
| //dyld::warn("can't open /dev/" DTRACEMNR_HELPER " to register dtrace DOF sections\n"); | |
| } | |
| else { | |
| // allocate a buffer on the stack for the variable length dof_ioctl_data_t type | |
| uint8_t buffer[sizeof(dof_ioctl_data_t) + dofSectionCount*sizeof(dof_helper_t)]; | |
| dof_ioctl_data_t* ioctlData = (dof_ioctl_data_t*)buffer; | |
| // fill in buffer with one dof_helper_t per DOF section | |
| ioctlData->dofiod_count = dofSectionCount; | |
| for (unsigned int i=0; i < dofSectionCount; ++i) { | |
| strlcpy(ioctlData->dofiod_helpers[i].dofhp_mod, dofs[i].imageShortName, DTRACE_MODNAMELEN); | |
| ioctlData->dofiod_helpers[i].dofhp_dof = (uintptr_t)(dofs[i].dof); | |
| ioctlData->dofiod_helpers[i].dofhp_addr = (uintptr_t)(dofs[i].dof); | |
| } | |
| // tell kernel about all DOF sections en mas | |
| // pass pointer to ioctlData because ioctl() only copies a fixed size amount of data into kernel | |
| user_addr_t val = (user_addr_t)(unsigned long)ioctlData; | |
| if ( ioctl(fd, DTRACEHIOC_ADDDOF, &val) != -1 ) { | |
| // kernel returns a unique identifier for each section in the dofiod_helpers[].dofhp_dof field. | |
| for (unsigned int i=0; i < dofSectionCount; ++i) { | |
| RegisteredDOF info; | |
| info.mh = dofs[i].imageHeader; | |
| info.registrationID = (int)(ioctlData->dofiod_helpers[i].dofhp_dof); | |
| sImageFilesNeedingDOFUnregistration.push_back(info); | |
| if ( gLinkContext.verboseDOF ) { | |
| dyld::log("dyld: registering DOF section %p in %s with dtrace, ID=0x%08X\n", | |
| dofs[i].dof, dofs[i].imageShortName, info.registrationID); | |
| } | |
| } | |
| } | |
| else { | |
| //dyld::log( "dyld: ioctl to register dtrace DOF section failed\n"); | |
| } | |
| close(fd); | |
| } | |
| } | |
| } | |
| static void unregisterDOF(int registrationID) | |
| { | |
| int fd = open("/dev/" DTRACEMNR_HELPER, O_RDWR); | |
| if ( fd < 0 ) { | |
| dyld::warn("can't open /dev/" DTRACEMNR_HELPER " to unregister dtrace DOF section\n"); | |
| } | |
| else { | |
| ioctl(fd, DTRACEHIOC_REMOVE, registrationID); | |
| close(fd); | |
| if ( gLinkContext.verboseInit ) | |
| dyld::warn("unregistering DOF section ID=0x%08X with dtrace\n", registrationID); | |
| } | |
| } | |
| // | |
| // _dyld_register_func_for_add_image() is implemented as part of the general image state change notification | |
| // | |
| static void notifyAddImageCallbacks(ImageLoader* image) | |
| { | |
| // use guard so that we cannot notify about the same image twice | |
| if ( ! image->addFuncNotified() ) { | |
| for (std::vector<ImageCallback>::iterator it=sAddImageCallbacks.begin(); it != sAddImageCallbacks.end(); it++) | |
| (*it)(image->machHeader(), image->getSlide()); | |
| image->setAddFuncNotified(); | |
| } | |
| } | |
| // notify gdb about these new images | |
| static const char* updateAllImages(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]) | |
| { | |
| // <rdar://problem/8812589> don't add images without paths to all-image-info-list | |
| if ( info[0].imageFilePath != NULL ) | |
| addImagesToAllImages(infoCount, info); | |
| return NULL; | |
| } | |
| static StateHandlers* stateToHandlers(dyld_image_states state, void* handlersArray[7][3]) | |
| { | |
| switch ( state ) { | |
| case dyld_image_state_mapped: | |
| return reinterpret_cast<StateHandlers*>(&handlersArray[0]); | |
| case dyld_image_state_dependents_mapped: | |
| return reinterpret_cast<StateHandlers*>(&handlersArray[1]); | |
| case dyld_image_state_rebased: | |
| return reinterpret_cast<StateHandlers*>(&handlersArray[2]); | |
| case dyld_image_state_bound: | |
| return reinterpret_cast<StateHandlers*>(&handlersArray[3]); | |
| case dyld_image_state_dependents_initialized: | |
| return reinterpret_cast<StateHandlers*>(&handlersArray[4]); | |
| case dyld_image_state_initialized: | |
| return reinterpret_cast<StateHandlers*>(&handlersArray[5]); | |
| case dyld_image_state_terminated: | |
| return reinterpret_cast<StateHandlers*>(&handlersArray[6]); | |
| } | |
| return NULL; | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| static dyld_image_state_change_handler getPreInitNotifyHandler(unsigned index) | |
| { | |
| std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(dyld_image_state_dependents_initialized, sSingleHandlers); | |
| if ( index >= handlers->size() ) | |
| return NULL; | |
| return (*handlers)[index]; | |
| } | |
| static dyld_image_state_change_handler getBoundBatchHandler(unsigned index) | |
| { | |
| std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(dyld_image_state_bound, sBatchHandlers); | |
| if ( index >= handlers->size() ) | |
| return NULL; | |
| return (*handlers)[index]; | |
| } | |
| static void notifySingleFromCache(dyld_image_states state, const mach_header* mh, const char* path) | |
| { | |
| //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath()); | |
| std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers); | |
| if ( handlers != NULL ) { | |
| dyld_image_info info; | |
| info.imageLoadAddress = mh; | |
| info.imageFilePath = path; | |
| info.imageFileModDate = 0; | |
| for (dyld_image_state_change_handler handler : *handlers) { | |
| const char* result = (*handler)(state, 1, &info); | |
| if ( (result != NULL) && (state == dyld_image_state_mapped) ) { | |
| //fprintf(stderr, " image rejected by handler=%p\n", *it); | |
| // make copy of thrown string so that later catch clauses can free it | |
| const char* str = strdup(result); | |
| throw str; | |
| } | |
| } | |
| } | |
| if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && (mh->flags & MH_HAS_OBJC) ) { | |
| (*sNotifyObjCInit)(path, mh); | |
| } | |
| } | |
| #endif | |
| static mach_port_t sNotifyReplyPorts[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; | |
| static void notifyMonitoringDyld(bool unloading, unsigned portSlot, unsigned imageCount, const dyld_image_info infos[]) | |
| { | |
| unsigned entriesSize = imageCount*sizeof(dyld_process_info_image_entry); | |
| unsigned pathsSize = 0; | |
| for (unsigned j=0; j < imageCount; ++j) { | |
| pathsSize += (strlen(infos[j].imageFilePath) + 1); | |
| } | |
| unsigned totalSize = (sizeof(dyld_process_info_notify_header) + entriesSize + pathsSize + 127) & -128; // align | |
| if ( totalSize > DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE ) { | |
| // Putting all image paths into one message would make buffer too big. | |
| // Instead split into two messages. Recurse as needed until paths fit in buffer. | |
| unsigned imageHalfCount = imageCount/2; | |
| notifyMonitoringDyld(unloading, portSlot, imageHalfCount, infos); | |
| notifyMonitoringDyld(unloading, portSlot, imageCount - imageHalfCount, &infos[imageHalfCount]); | |
| return; | |
| } | |
| uint8_t buffer[totalSize]; | |
| dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)buffer; | |
| header->version = 1; | |
| header->imageCount = imageCount; | |
| header->imagesOffset = sizeof(dyld_process_info_notify_header); | |
| header->stringsOffset = sizeof(dyld_process_info_notify_header) + entriesSize; | |
| header->timestamp = mach_absolute_time(); | |
| dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&buffer[header->imagesOffset]; | |
| char* const pathPoolStart = (char*)&buffer[header->stringsOffset]; | |
| char* pathPool = pathPoolStart; | |
| for (unsigned j=0; j < imageCount; ++j) { | |
| strcpy(pathPool, infos[j].imageFilePath); | |
| uint32_t len = (uint32_t)strlen(pathPool); | |
| bzero(entries->uuid, 16); | |
| const ImageLoader* image = findImageByMachHeader(infos[j].imageLoadAddress); | |
| if ( image != NULL ) { | |
| image->getUUID(entries->uuid); | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| else if ( sAllCacheImagesProxy != NULL ) { | |
| const mach_header* mh; | |
| const char* path; | |
| unsigned index; | |
| if ( sAllCacheImagesProxy->addressInCache(infos[j].imageLoadAddress, &mh, &path, &index) ) { | |
| sAllCacheImagesProxy->getDylibUUID(index, entries->uuid); | |
| } | |
| } | |
| #endif | |
| entries->loadAddress = (uint64_t)infos[j].imageLoadAddress; | |
| entries->pathStringOffset = (uint32_t)(pathPool - pathPoolStart); | |
| entries->pathLength = len; | |
| pathPool += (len +1); | |
| ++entries; | |
| } | |
| if ( sNotifyReplyPorts[portSlot] == 0 ) { | |
| if ( !mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sNotifyReplyPorts[portSlot]) ) | |
| mach_port_insert_right(mach_task_self(), sNotifyReplyPorts[portSlot], sNotifyReplyPorts[portSlot], MACH_MSG_TYPE_MAKE_SEND); | |
| //dyld::log("allocated reply port %d\n", sNotifyReplyPorts[portSlot]); | |
| } | |
| //dyld::log("found port to send to\n"); | |
| mach_msg_header_t* h = (mach_msg_header_t*)buffer; | |
| h->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND); // MACH_MSG_TYPE_MAKE_SEND_ONCE | |
| h->msgh_id = unloading ? DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID : DYLD_PROCESS_INFO_NOTIFY_LOAD_ID; | |
| h->msgh_local_port = sNotifyReplyPorts[portSlot]; | |
| h->msgh_remote_port = dyld::gProcessInfo->notifyPorts[portSlot]; | |
| h->msgh_reserved = 0; | |
| h->msgh_size = (mach_msg_size_t)sizeof(buffer); | |
| //dyld::log("sending to port[%d]=%d, size=%d, reply port=%d, id=0x%X\n", portSlot, dyld::gProcessInfo->notifyPorts[portSlot], h->msgh_size, sNotifyReplyPorts[portSlot], h->msgh_id); | |
| kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_SEND_TIMEOUT, h->msgh_size, h->msgh_size, sNotifyReplyPorts[portSlot], 100, MACH_PORT_NULL); | |
| //dyld::log("send result = 0x%X, msg_id=%d, msg_size=%d\n", sendResult, h->msgh_id, h->msgh_size); | |
| if ( sendResult == MACH_SEND_INVALID_DEST ) { | |
| // sender is not responding, detatch | |
| //dyld::log("process requesting notification gone. deallocation send port %d and receive port %d\n", dyld::gProcessInfo->notifyPorts[portSlot], sNotifyReplyPorts[portSlot]); | |
| mach_port_deallocate(mach_task_self(), dyld::gProcessInfo->notifyPorts[portSlot]); | |
| mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[portSlot]); | |
| dyld::gProcessInfo->notifyPorts[portSlot] = 0; | |
| sNotifyReplyPorts[portSlot] = 0; | |
| } | |
| } | |
| #define MAX_KERNEL_IMAGES_PER_CALL (100) | |
| static void flushKernelNotifications(bool loading, bool force, std::array<dyld_kernel_image_info_t,MAX_KERNEL_IMAGES_PER_CALL>& kernelInfos, uint32_t &kernelInfoCount) { | |
| if ((force && kernelInfoCount != 0) || kernelInfoCount == MAX_KERNEL_IMAGES_PER_CALL) { | |
| if (loading) { | |
| task_register_dyld_image_infos(mach_task_self(), kernelInfos.data(), kernelInfoCount); | |
| } else { | |
| task_unregister_dyld_image_infos(mach_task_self(), kernelInfos.data(), kernelInfoCount); | |
| } | |
| kernelInfoCount = 0; | |
| } | |
| } | |
| static | |
| void queueKernelNotification(const ImageLoader& image, bool loading, std::array<dyld_kernel_image_info_t,MAX_KERNEL_IMAGES_PER_CALL>& kernelInfos, uint32_t &kernelInfoCount) { | |
| if ( !image.inSharedCache() ) { | |
| ino_t inode = image.getInode(); | |
| image.getUUID(kernelInfos[kernelInfoCount].uuid); | |
| memcpy(&kernelInfos[kernelInfoCount].fsobjid, &inode, 8); | |
| kernelInfos[kernelInfoCount].load_addr = (uint64_t)image.machHeader(); | |
| // FIXME we should also be grabbing the device ID, but that is not necessary yet, | |
| // and requires threading it through the ImageLoader | |
| kernelInfos[kernelInfoCount].fsid.val[0] = 0; | |
| kernelInfos[kernelInfoCount].fsid.val[1] = 0; | |
| kernelInfoCount++; | |
| } | |
| flushKernelNotifications(loading, false, kernelInfos, kernelInfoCount); | |
| } | |
| void notifyKernel(const ImageLoader& image, bool loading) { | |
| std::array<dyld_kernel_image_info_t,MAX_KERNEL_IMAGES_PER_CALL> kernelInfos; | |
| uint32_t kernelInfoCount = 0; | |
| queueKernelNotification(image, loading, kernelInfos, kernelInfoCount); | |
| flushKernelNotifications(loading, true, kernelInfos, kernelInfoCount); | |
| } | |
| static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo) | |
| { | |
| //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath()); | |
| std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers); | |
| if ( handlers != NULL ) { | |
| dyld_image_info info; | |
| info.imageLoadAddress = image->machHeader(); | |
| info.imageFilePath = image->getRealPath(); | |
| info.imageFileModDate = image->lastModified(); | |
| for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) { | |
| const char* result = (*it)(state, 1, &info); | |
| if ( (result != NULL) && (state == dyld_image_state_mapped) ) { | |
| //fprintf(stderr, " image rejected by handler=%p\n", *it); | |
| // make copy of thrown string so that later catch clauses can free it | |
| const char* str = strdup(result); | |
| throw str; | |
| } | |
| } | |
| } | |
| if ( state == dyld_image_state_mapped ) { | |
| // <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache | |
| if ( !image->inSharedCache() ) { | |
| dyld_uuid_info info; | |
| if ( image->getUUID(info.imageUUID) ) { | |
| info.imageLoadAddress = image->machHeader(); | |
| addNonSharedCacheImageUUID(info); | |
| } | |
| } | |
| } | |
| if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) { | |
| uint64_t t0 = mach_absolute_time(); | |
| (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); | |
| uint64_t t1 = mach_absolute_time(); | |
| uint64_t t2 = mach_absolute_time(); | |
| uint64_t timeInObjC = t1-t0; | |
| uint64_t emptyTime = (t2-t1)*100; | |
| if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) { | |
| timingInfo->addTime(image->getShortName(), timeInObjC); | |
| } | |
| } | |
| // mach message csdlc about dynamically unloaded images | |
| if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) { | |
| notifyKernel(*image, false); | |
| uint64_t loadTimestamp = mach_absolute_time(); | |
| if ( sEnv.DYLD_PRINT_CS_NOTIFICATIONS ) { | |
| dyld::log("dyld: coresymbolication_unload_notifier(%p, 0x%016llX, %p, %s)\n", | |
| dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, image->machHeader(), image->getPath()); | |
| } | |
| if ( dyld::gProcessInfo->coreSymbolicationShmPage != NULL) { | |
| coresymbolication_unload_notifier(dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, image->getPath(), image->machHeader()); | |
| } | |
| for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { | |
| if ( dyld::gProcessInfo->notifyPorts[slot] != 0 ) { | |
| dyld_image_info info; | |
| info.imageLoadAddress = image->machHeader(); | |
| info.imageFilePath = image->getPath(); | |
| info.imageFileModDate = 0; | |
| notifyMonitoringDyld(true, slot, 1, &info); | |
| } | |
| else if ( sNotifyReplyPorts[slot] != 0 ) { | |
| // monitoring process detached from this process, so release reply port | |
| //dyld::log("deallocated reply port %d\n", sNotifyReplyPorts[slot]); | |
| mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); | |
| sNotifyReplyPorts[slot] = 0; | |
| } | |
| } | |
| } | |
| } | |
| // | |
| // Normally, dyld_all_image_infos is only updated in batches after an entire | |
| // graph is loaded. But if there is an error loading the initial set of | |
| // dylibs needed by the main executable, dyld_all_image_infos is not yet set | |
| // up, leading to usually brief crash logs. | |
| // | |
| // This function manually adds the images loaded so far to dyld::gProcessInfo. | |
| // It should only be called before terminating. | |
| // | |
| void syncAllImages() | |
| { | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); ++it) { | |
| dyld_image_info info; | |
| ImageLoader* image = *it; | |
| info.imageLoadAddress = image->machHeader(); | |
| info.imageFilePath = image->getRealPath(); | |
| info.imageFileModDate = image->lastModified(); | |
| // add to all_image_infos if not already there | |
| bool found = false; | |
| int existingCount = dyld::gProcessInfo->infoArrayCount; | |
| const dyld_image_info* existing = dyld::gProcessInfo->infoArray; | |
| if ( existing != NULL ) { | |
| for (int i=0; i < existingCount; ++i) { | |
| if ( existing[i].imageLoadAddress == info.imageLoadAddress ) { | |
| //dyld::log("not adding %s\n", info.imageFilePath); | |
| found = true; | |
| break; | |
| } | |
| } | |
| } | |
| if ( ! found ) { | |
| //dyld::log("adding %s\n", info.imageFilePath); | |
| addImagesToAllImages(1, &info); | |
| } | |
| } | |
| } | |
| static int imageSorter(const void* l, const void* r) | |
| { | |
| const ImageLoader* left = *((ImageLoader**)l); | |
| const ImageLoader* right= *((ImageLoader**)r); | |
| return left->compare(right); | |
| } | |
| static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification) | |
| { | |
| std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sBatchHandlers); | |
| std::array<dyld_kernel_image_info_t,MAX_KERNEL_IMAGES_PER_CALL> kernelInfos; | |
| uint32_t kernelInfoCount = 0; | |
| if ( (handlers != NULL) || ((state == dyld_image_state_bound) && (sNotifyObjCMapped != NULL)) ) { | |
| // don't use a vector because it will use malloc/free and we want notifcation to be low cost | |
| allImagesLock(); | |
| dyld_image_info infos[allImagesCount()+1]; | |
| ImageLoader* images[allImagesCount()+1]; | |
| ImageLoader** end = images; | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| dyld_image_states imageState = (*it)->getState(); | |
| if ( (imageState == state) || (orLater && (imageState > state)) ) | |
| *end++ = *it; | |
| } | |
| if ( sBundleBeingLoaded != NULL ) { | |
| dyld_image_states imageState = sBundleBeingLoaded->getState(); | |
| if ( (imageState == state) || (orLater && (imageState > state)) ) | |
| *end++ = sBundleBeingLoaded; | |
| } | |
| const char* dontLoadReason = NULL; | |
| uint32_t imageCount = (uint32_t)(end-images); | |
| if ( imageCount != 0 ) { | |
| // sort bottom up | |
| qsort(images, imageCount, sizeof(ImageLoader*), &imageSorter); | |
| // build info array | |
| for (unsigned int i=0; i < imageCount; ++i) { | |
| dyld_image_info* p = &infos[i]; | |
| ImageLoader* image = images[i]; | |
| //dyld::log(" state=%d, name=%s\n", state, image->getPath()); | |
| p->imageLoadAddress = image->machHeader(); | |
| p->imageFilePath = image->getRealPath(); | |
| p->imageFileModDate = image->lastModified(); | |
| // get these registered with the kernel as early as possible | |
| if ( state == dyld_image_state_dependents_mapped) | |
| queueKernelNotification(*image, true, kernelInfos, kernelInfoCount); | |
| // special case for add_image hook | |
| if ( state == dyld_image_state_bound ) | |
| notifyAddImageCallbacks(image); | |
| } | |
| flushKernelNotifications(true, true, kernelInfos, kernelInfoCount); | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( sAllCacheImagesProxy != NULL ) { | |
| unsigned cacheCount = sAllCacheImagesProxy->appendImagesToNotify(state, orLater, &infos[imageCount]); | |
| // support _dyld_register_func_for_add_image() | |
| if ( state == dyld_image_state_bound ) { | |
| for (ImageCallback callback : sAddImageCallbacks) { | |
| for (unsigned i=0; i < cacheCount; ++i) | |
| (*callback)(infos[imageCount+i].imageLoadAddress, sSharedCacheSlide); | |
| } | |
| } | |
| imageCount += cacheCount; | |
| } | |
| #endif | |
| if ( imageCount != 0 ) { | |
| if ( !onlyObjCMappedNotification ) { | |
| if ( onlyHandler != NULL ) { | |
| const char* result = NULL; | |
| if ( result == NULL ) { | |
| result = (*onlyHandler)(state, imageCount, infos); | |
| } | |
| if ( (result != NULL) && (state == dyld_image_state_dependents_mapped) ) { | |
| //fprintf(stderr, " images rejected by handler=%p\n", onlyHandler); | |
| // make copy of thrown string so that later catch clauses can free it | |
| dontLoadReason = strdup(result); | |
| } | |
| } | |
| else { | |
| // call each handler with whole array | |
| if ( handlers != NULL ) { | |
| for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) { | |
| const char* result = (*it)(state, imageCount, infos); | |
| if ( (result != NULL) && (state == dyld_image_state_dependents_mapped) ) { | |
| //fprintf(stderr, " images rejected by handler=%p\n", *it); | |
| // make copy of thrown string so that later catch clauses can free it | |
| dontLoadReason = strdup(result); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // tell objc about new images | |
| if ( (onlyHandler == NULL) && ((state == dyld_image_state_bound) || (orLater && (dyld_image_state_bound > state))) && (sNotifyObjCMapped != NULL) ) { | |
| const char* paths[imageCount]; | |
| const mach_header* mhs[imageCount]; | |
| unsigned objcImageCount = 0; | |
| for (int i=0; i < imageCount; ++i) { | |
| const ImageLoader* image = findImageByMachHeader(infos[i].imageLoadAddress); | |
| bool hasObjC = false; | |
| if ( image != NULL ) { | |
| hasObjC = image->notifyObjC(); | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| else if ( sAllCacheImagesProxy != NULL ) { | |
| const mach_header* mh; | |
| const char* path; | |
| unsigned index; | |
| if ( sAllCacheImagesProxy->addressInCache(infos[i].imageLoadAddress, &mh, &path, &index) ) { | |
| hasObjC = (mh->flags & MH_HAS_OBJC); | |
| } | |
| } | |
| #endif | |
| if ( hasObjC ) { | |
| paths[objcImageCount] = infos[i].imageFilePath; | |
| mhs[objcImageCount] = infos[i].imageLoadAddress; | |
| ++objcImageCount; | |
| } | |
| } | |
| if ( objcImageCount != 0 ) { | |
| uint64_t t0 = mach_absolute_time(); | |
| (*sNotifyObjCMapped)(objcImageCount, paths, mhs); | |
| uint64_t t1 = mach_absolute_time(); | |
| ImageLoader::fgTotalObjCSetupTime += (t1-t0); | |
| } | |
| } | |
| } | |
| allImagesUnlock(); | |
| if ( dontLoadReason != NULL ) | |
| throw dontLoadReason; | |
| if ( !preflightOnly && (state == dyld_image_state_dependents_mapped) ) { | |
| if ( (dyld::gProcessInfo->coreSymbolicationShmPage != NULL) || sEnv.DYLD_PRINT_CS_NOTIFICATIONS ) { | |
| // mach message csdlc about loaded images | |
| uint64_t loadTimestamp = mach_absolute_time(); | |
| for (unsigned j=0; j < imageCount; ++j) { | |
| if ( sEnv.DYLD_PRINT_CS_NOTIFICATIONS ) { | |
| dyld::log("dyld: coresymbolication_load_notifier(%p, 0x%016llX, %p, %s)\n", | |
| dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, infos[j].imageLoadAddress, infos[j].imageFilePath); | |
| } | |
| coresymbolication_load_notifier(dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, infos[j].imageFilePath, infos[j].imageLoadAddress); | |
| } | |
| } | |
| for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { | |
| if ( dyld::gProcessInfo->notifyPorts[slot] ) | |
| notifyMonitoringDyld(false, slot, imageCount, infos); | |
| } | |
| } | |
| } | |
| } | |
| static void notifyBatch(dyld_image_states state, bool preflightOnly) | |
| { | |
| notifyBatchPartial(state, false, NULL, preflightOnly, false); | |
| } | |
| // In order for register_func_for_add_image() callbacks to to be called bottom up, | |
| // we need to maintain a list of root images. The main executable is usally the | |
| // first root. Any images dynamically added are also roots (unless already loaded). | |
| // If DYLD_INSERT_LIBRARIES is used, those libraries are first. | |
| static void addRootImage(ImageLoader* image) | |
| { | |
| //dyld::log("addRootImage(%p, %s)\n", image, image->getPath()); | |
| // add to list of roots | |
| sImageRoots.push_back(image); | |
| } | |
| static void clearAllDepths() | |
| { | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) | |
| (*it)->clearDepth(); | |
| } | |
| static void printAllDepths() | |
| { | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) | |
| dyld::log("%03d %s\n", (*it)->getDepth(), (*it)->getShortName()); | |
| } | |
| static unsigned int imageCount() | |
| { | |
| allImagesLock(); | |
| unsigned int result = (unsigned int)sAllImages.size(); | |
| allImagesUnlock(); | |
| return (result); | |
| } | |
| static void setNewProgramVars(const ProgramVars& newVars) | |
| { | |
| // make a copy of the pointers to program variables | |
| gLinkContext.programVars = newVars; | |
| // now set each program global to their initial value | |
| *gLinkContext.programVars.NXArgcPtr = gLinkContext.argc; | |
| *gLinkContext.programVars.NXArgvPtr = gLinkContext.argv; | |
| *gLinkContext.programVars.environPtr = gLinkContext.envp; | |
| *gLinkContext.programVars.__prognamePtr = gLinkContext.progname; | |
| } | |
| #if SUPPORT_OLD_CRT_INITIALIZATION | |
| static void setRunInitialzersOldWay() | |
| { | |
| gRunInitializersOldWay = true; | |
| } | |
| #endif | |
| static bool sandboxBlocked(const char* path, const char* kind) | |
| { | |
| #if TARGET_IPHONE_SIMULATOR | |
| // sandbox calls not yet supported in simulator runtime | |
| return false; | |
| #else | |
| sandbox_filter_type filter = (sandbox_filter_type)(SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT); | |
| return ( sandbox_check(getpid(), kind, filter, path) > 0 ); | |
| #endif | |
| } | |
| bool sandboxBlockedMmap(const char* path) | |
| { | |
| return sandboxBlocked(path, "file-map-executable"); | |
| } | |
| bool sandboxBlockedOpen(const char* path) | |
| { | |
| return sandboxBlocked(path, "file-read-data"); | |
| } | |
| bool sandboxBlockedStat(const char* path) | |
| { | |
| return sandboxBlocked(path, "file-read-metadata"); | |
| } | |
| static void addDynamicReference(ImageLoader* from, ImageLoader* to) { | |
| // don't add dynamic reference if target is in the shared cache (since it can't be unloaded) | |
| if ( to->inSharedCache() ) | |
| return; | |
| // don't add dynamic reference if there already is a static one | |
| if ( from->dependsOn(to) ) | |
| return; | |
| // don't add if this combination already exists | |
| OSSpinLockLock(&sDynamicReferencesLock); | |
| for (std::vector<ImageLoader::DynamicReference>::iterator it=sDynamicReferences.begin(); it != sDynamicReferences.end(); ++it) { | |
| if ( (it->from == from) && (it->to == to) ) { | |
| OSSpinLockUnlock(&sDynamicReferencesLock); | |
| return; | |
| } | |
| } | |
| //dyld::log("addDynamicReference(%s, %s\n", from->getShortName(), to->getShortName()); | |
| ImageLoader::DynamicReference t; | |
| t.from = from; | |
| t.to = to; | |
| sDynamicReferences.push_back(t); | |
| OSSpinLockUnlock(&sDynamicReferencesLock); | |
| } | |
| static void addImage(ImageLoader* image) | |
| { | |
| // add to master list | |
| allImagesLock(); | |
| sAllImages.push_back(image); | |
| allImagesUnlock(); | |
| // update mapped ranges | |
| uintptr_t lastSegStart = 0; | |
| uintptr_t lastSegEnd = 0; | |
| for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) { | |
| if ( image->segUnaccessible(i) ) | |
| continue; | |
| uintptr_t start = image->segActualLoadAddress(i); | |
| uintptr_t end = image->segActualEndAddress(i); | |
| if ( start == lastSegEnd ) { | |
| // two segments are contiguous, just record combined segments | |
| lastSegEnd = end; | |
| } | |
| else { | |
| // non-contiguous segments, record last (if any) | |
| if ( lastSegEnd != 0 ) | |
| addMappedRange(image, lastSegStart, lastSegEnd); | |
| lastSegStart = start; | |
| lastSegEnd = end; | |
| } | |
| } | |
| if ( lastSegEnd != 0 ) | |
| addMappedRange(image, lastSegStart, lastSegEnd); | |
| if ( gLinkContext.verboseLoading || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable!=NULL) && sMainExecutable->isLinked()) ) { | |
| dyld::log("dyld: loaded: %s\n", image->getPath()); | |
| } | |
| } | |
| // | |
| // Helper for std::remove_if | |
| // | |
| class RefUsesImage { | |
| public: | |
| RefUsesImage(ImageLoader* image) : _image(image) {} | |
| bool operator()(const ImageLoader::DynamicReference& ref) const { | |
| return ( (ref.from == _image) || (ref.to == _image) ); | |
| } | |
| private: | |
| ImageLoader* _image; | |
| }; | |
| void removeImage(ImageLoader* image) | |
| { | |
| // if has dtrace DOF section, tell dtrace it is going away, then remove from sImageFilesNeedingDOFUnregistration | |
| for (std::vector<RegisteredDOF>::iterator it=sImageFilesNeedingDOFUnregistration.begin(); it != sImageFilesNeedingDOFUnregistration.end(); ) { | |
| if ( it->mh == image->machHeader() ) { | |
| unregisterDOF(it->registrationID); | |
| sImageFilesNeedingDOFUnregistration.erase(it); | |
| // don't increment iterator, the erase caused next element to be copied to where this iterator points | |
| } | |
| else { | |
| ++it; | |
| } | |
| } | |
| // tell all registered remove image handlers about this | |
| // do this before removing image from internal data structures so that the callback can query dyld about the image | |
| if ( image->getState() >= dyld_image_state_bound ) { | |
| sRemoveImageCallbacksInUse = true; // This only runs inside dyld's global lock, so ok to use a global for the in-use flag. | |
| for (std::vector<ImageCallback>::iterator it=sRemoveImageCallbacks.begin(); it != sRemoveImageCallbacks.end(); it++) { | |
| (*it)(image->machHeader(), image->getSlide()); | |
| } | |
| sRemoveImageCallbacksInUse = false; | |
| if ( sNotifyObjCUnmapped != NULL && image->notifyObjC() ) | |
| (*sNotifyObjCUnmapped)(image->getRealPath(), image->machHeader()); | |
| } | |
| // notify | |
| notifySingle(dyld_image_state_terminated, image, NULL); | |
| // remove from mapped images table | |
| removedMappedRanges(image); | |
| // remove from master list | |
| allImagesLock(); | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| if ( *it == image ) { | |
| sAllImages.erase(it); | |
| break; | |
| } | |
| } | |
| allImagesUnlock(); | |
| // remove from sDynamicReferences | |
| OSSpinLockLock(&sDynamicReferencesLock); | |
| sDynamicReferences.erase(std::remove_if(sDynamicReferences.begin(), sDynamicReferences.end(), RefUsesImage(image)), sDynamicReferences.end()); | |
| OSSpinLockUnlock(&sDynamicReferencesLock); | |
| // flush find-by-address cache (do this after removed from master list, so there is no chance it can come back) | |
| if ( sLastImageByAddressCache == image ) | |
| sLastImageByAddressCache = NULL; | |
| // if in root list, pull it out | |
| for (std::vector<ImageLoader*>::iterator it=sImageRoots.begin(); it != sImageRoots.end(); it++) { | |
| if ( *it == image ) { | |
| sImageRoots.erase(it); | |
| break; | |
| } | |
| } | |
| // log if requested | |
| if ( gLinkContext.verboseLoading || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable!=NULL) && sMainExecutable->isLinked()) ) { | |
| dyld::log("dyld: unloaded: %s\n", image->getPath()); | |
| } | |
| // tell gdb, new way | |
| removeImageFromAllImages(image->machHeader()); | |
| } | |
| void runImageStaticTerminators(ImageLoader* image) | |
| { | |
| // if in termination list, pull it out and run terminator | |
| bool mightBeMore; | |
| do { | |
| mightBeMore = false; | |
| for (std::vector<ImageLoader*>::iterator it=sImageFilesNeedingTermination.begin(); it != sImageFilesNeedingTermination.end(); it++) { | |
| if ( *it == image ) { | |
| sImageFilesNeedingTermination.erase(it); | |
| if (gLogAPIs) dyld::log("dlclose(), running static terminators for %p %s\n", image, image->getShortName()); | |
| image->doTermination(gLinkContext); | |
| mightBeMore = true; | |
| break; | |
| } | |
| } | |
| } while ( mightBeMore ); | |
| } | |
| static void terminationRecorder(ImageLoader* image) | |
| { | |
| sImageFilesNeedingTermination.push_back(image); | |
| } | |
| const char* getExecutablePath() | |
| { | |
| return sExecPath; | |
| } | |
| static void runAllStaticTerminators(void* extra) | |
| { | |
| try { | |
| const size_t imageCount = sImageFilesNeedingTermination.size(); | |
| for(size_t i=imageCount; i > 0; --i){ | |
| ImageLoader* image = sImageFilesNeedingTermination[i-1]; | |
| image->doTermination(gLinkContext); | |
| } | |
| sImageFilesNeedingTermination.clear(); | |
| notifyBatch(dyld_image_state_terminated, false); | |
| } | |
| catch (const char* msg) { | |
| halt(msg); | |
| } | |
| } | |
| void initializeMainExecutable() | |
| { | |
| // record that we've reached this step | |
| gLinkContext.startedInitializingMainExecutable = true; | |
| // run initialzers for any inserted dylibs | |
| ImageLoader::InitializerTimingList initializerTimes[allImagesCount()]; | |
| initializerTimes[0].count = 0; | |
| const size_t rootCount = sImageRoots.size(); | |
| if ( rootCount > 1 ) { | |
| for(size_t i=1; i < rootCount; ++i) { | |
| sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]); | |
| } | |
| } | |
| // run initializers for main executable and everything it brings up | |
| sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]); | |
| // register cxa_atexit() handler to run static terminators in all loaded images when this process exits | |
| if ( gLibSystemHelpers != NULL ) | |
| (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL); | |
| // dump info if requested | |
| if ( sEnv.DYLD_PRINT_STATISTICS ) | |
| ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]); | |
| if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS ) | |
| ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]); | |
| } | |
| bool mainExecutablePrebound() | |
| { | |
| return sMainExecutable->usablePrebinding(gLinkContext); | |
| } | |
| ImageLoader* mainExecutable() | |
| { | |
| return sMainExecutable; | |
| } | |
| #if SUPPORT_VERSIONED_PATHS | |
| // forward reference | |
| static bool getDylibVersionAndInstallname(const char* dylibPath, uint32_t* version, char* installName); | |
| // | |
| // Examines a dylib file and if its current_version is newer than the installed | |
| // dylib at its install_name, then add the dylib file to sDylibOverrides. | |
| // | |
| static void checkDylibOverride(const char* dylibFile) | |
| { | |
| //dyld::log("checkDylibOverride('%s')\n", dylibFile); | |
| uint32_t altVersion; | |
| char sysInstallName[PATH_MAX]; | |
| if ( getDylibVersionAndInstallname(dylibFile, &altVersion, sysInstallName) && (sysInstallName[0] =='/') ) { | |
| //dyld::log("%s has version 0x%08X and install name %s\n", dylibFile, altVersion, sysInstallName); | |
| uint32_t sysVersion; | |
| if ( getDylibVersionAndInstallname(sysInstallName, &sysVersion, NULL) ) { | |
| //dyld::log("%s has version 0x%08X\n", sysInstallName, sysVersion); | |
| if ( altVersion > sysVersion ) { | |
| //dyld::log("override found: %s -> %s\n", sysInstallName, dylibFile); | |
| // see if there already is an override for this dylib | |
| bool entryExists = false; | |
| for (std::vector<DylibOverride>::iterator it = sDylibOverrides.begin(); it != sDylibOverrides.end(); ++it) { | |
| if ( strcmp(it->installName, sysInstallName) == 0 ) { | |
| entryExists = true; | |
| uint32_t prevVersion; | |
| if ( getDylibVersionAndInstallname(it->override, &prevVersion, NULL) ) { | |
| if ( altVersion > prevVersion ) { | |
| // found an even newer override | |
| free((void*)(it->override)); | |
| char resolvedPath[PATH_MAX]; | |
| if ( realpath(dylibFile, resolvedPath) != NULL ) | |
| it->override = strdup(resolvedPath); | |
| else | |
| it->override = strdup(dylibFile); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| if ( ! entryExists ) { | |
| DylibOverride entry; | |
| entry.installName = strdup(sysInstallName); | |
| char resolvedPath[PATH_MAX]; | |
| if ( realpath(dylibFile, resolvedPath) != NULL ) | |
| entry.override = strdup(resolvedPath); | |
| else | |
| entry.override = strdup(dylibFile); | |
| sDylibOverrides.push_back(entry); | |
| //dyld::log("added override: %s -> %s\n", entry.installName, entry.override); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| static void checkDylibOverridesInDir(const char* dirPath) | |
| { | |
| //dyld::log("checkDylibOverridesInDir('%s')\n", dirPath); | |
| char dylibPath[PATH_MAX]; | |
| long dirPathLen = strlcpy(dylibPath, dirPath, PATH_MAX-1); | |
| if ( dirPathLen >= PATH_MAX ) | |
| return; | |
| DIR* dirp = opendir(dirPath); | |
| if ( dirp != NULL) { | |
| dirent entry; | |
| dirent* entp = NULL; | |
| while ( readdir_r(dirp, &entry, &entp) == 0 ) { | |
| if ( entp == NULL ) | |
| break; | |
| if ( entp->d_type != DT_REG ) | |
| continue; | |
| dylibPath[dirPathLen] = '/'; | |
| dylibPath[dirPathLen+1] = '\0'; | |
| if ( strlcat(dylibPath, entp->d_name, PATH_MAX) >= PATH_MAX ) | |
| continue; | |
| checkDylibOverride(dylibPath); | |
| } | |
| closedir(dirp); | |
| } | |
| } | |
| static void checkFrameworkOverridesInDir(const char* dirPath) | |
| { | |
| //dyld::log("checkFrameworkOverridesInDir('%s')\n", dirPath); | |
| char frameworkPath[PATH_MAX]; | |
| long dirPathLen = strlcpy(frameworkPath, dirPath, PATH_MAX-1); | |
| if ( dirPathLen >= PATH_MAX ) | |
| return; | |
| DIR* dirp = opendir(dirPath); | |
| if ( dirp != NULL) { | |
| dirent entry; | |
| dirent* entp = NULL; | |
| while ( readdir_r(dirp, &entry, &entp) == 0 ) { | |
| if ( entp == NULL ) | |
| break; | |
| if ( entp->d_type != DT_DIR ) | |
| continue; | |
| frameworkPath[dirPathLen] = '/'; | |
| frameworkPath[dirPathLen+1] = '\0'; | |
| int dirNameLen = (int)strlen(entp->d_name); | |
| if ( dirNameLen < 11 ) | |
| continue; | |
| if ( strcmp(&entp->d_name[dirNameLen-10], ".framework") != 0 ) | |
| continue; | |
| if ( strlcat(frameworkPath, entp->d_name, PATH_MAX) >= PATH_MAX ) | |
| continue; | |
| if ( strlcat(frameworkPath, "/", PATH_MAX) >= PATH_MAX ) | |
| continue; | |
| if ( strlcat(frameworkPath, entp->d_name, PATH_MAX) >= PATH_MAX ) | |
| continue; | |
| frameworkPath[strlen(frameworkPath)-10] = '\0'; | |
| checkDylibOverride(frameworkPath); | |
| } | |
| closedir(dirp); | |
| } | |
| } | |
| #endif // SUPPORT_VERSIONED_PATHS | |
| // | |
| // Turns a colon separated list of strings into a NULL terminated array | |
| // of string pointers. If mainExecutableDir param is not NULL, | |
| // substitutes @loader_path with main executable's dir. | |
| // | |
| static const char** parseColonList(const char* list, const char* mainExecutableDir) | |
| { | |
| static const char* sEmptyList[] = { NULL }; | |
| if ( list[0] == '\0' ) | |
| return sEmptyList; | |
| int colonCount = 0; | |
| for(const char* s=list; *s != '\0'; ++s) { | |
| if (*s == ':') | |
| ++colonCount; | |
| } | |
| int index = 0; | |
| const char* start = list; | |
| char** result = new char*[colonCount+2]; | |
| for(const char* s=list; *s != '\0'; ++s) { | |
| if (*s == ':') { | |
| size_t len = s-start; | |
| if ( (mainExecutableDir != NULL) && (strncmp(start, "@loader_path/", 13) == 0) ) { | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| if ( gLinkContext.processIsRestricted ) { | |
| dyld::log("dyld: warning: @loader_path/ ignored in restricted process\n"); | |
| continue; | |
| } | |
| #endif | |
| size_t mainExecDirLen = strlen(mainExecutableDir); | |
| char* str = new char[mainExecDirLen+len+1]; | |
| strcpy(str, mainExecutableDir); | |
| strlcat(str, &start[13], mainExecDirLen+len+1); | |
| str[mainExecDirLen+len-13] = '\0'; | |
| start = &s[1]; | |
| result[index++] = str; | |
| } | |
| else if ( (mainExecutableDir != NULL) && (strncmp(start, "@executable_path/", 17) == 0) ) { | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| if ( gLinkContext.processIsRestricted ) { | |
| dyld::log("dyld: warning: @executable_path/ ignored in restricted process\n"); | |
| continue; | |
| } | |
| #endif | |
| size_t mainExecDirLen = strlen(mainExecutableDir); | |
| char* str = new char[mainExecDirLen+len+1]; | |
| strcpy(str, mainExecutableDir); | |
| strlcat(str, &start[17], mainExecDirLen+len+1); | |
| str[mainExecDirLen+len-17] = '\0'; | |
| start = &s[1]; | |
| result[index++] = str; | |
| } | |
| else { | |
| char* str = new char[len+1]; | |
| strncpy(str, start, len); | |
| str[len] = '\0'; | |
| start = &s[1]; | |
| result[index++] = str; | |
| } | |
| } | |
| } | |
| size_t len = strlen(start); | |
| if ( (mainExecutableDir != NULL) && (strncmp(start, "@loader_path/", 13) == 0) ) { | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| if ( gLinkContext.processIsRestricted ) { | |
| dyld::log("dyld: warning: @loader_path/ ignored in restricted process\n"); | |
| } | |
| else | |
| #endif | |
| { | |
| size_t mainExecDirLen = strlen(mainExecutableDir); | |
| char* str = new char[mainExecDirLen+len+1]; | |
| strcpy(str, mainExecutableDir); | |
| strlcat(str, &start[13], mainExecDirLen+len+1); | |
| str[mainExecDirLen+len-13] = '\0'; | |
| result[index++] = str; | |
| } | |
| } | |
| else if ( (mainExecutableDir != NULL) && (strncmp(start, "@executable_path/", 17) == 0) ) { | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| if ( gLinkContext.processIsRestricted ) { | |
| dyld::log("dyld: warning: @executable_path/ ignored in restricted process\n"); | |
| } | |
| else | |
| #endif | |
| { | |
| size_t mainExecDirLen = strlen(mainExecutableDir); | |
| char* str = new char[mainExecDirLen+len+1]; | |
| strcpy(str, mainExecutableDir); | |
| strlcat(str, &start[17], mainExecDirLen+len+1); | |
| str[mainExecDirLen+len-17] = '\0'; | |
| result[index++] = str; | |
| } | |
| } | |
| else { | |
| char* str = new char[len+1]; | |
| strcpy(str, start); | |
| result[index++] = str; | |
| } | |
| result[index] = NULL; | |
| //dyld::log("parseColonList(%s)\n", list); | |
| //for(int i=0; result[i] != NULL; ++i) | |
| // dyld::log(" %s\n", result[i]); | |
| return (const char**)result; | |
| } | |
| static void appendParsedColonList(const char* list, const char* mainExecutableDir, const char* const ** storage) | |
| { | |
| const char** newlist = parseColonList(list, mainExecutableDir); | |
| if ( *storage == NULL ) { | |
| // first time, just set | |
| *storage = newlist; | |
| } | |
| else { | |
| // need to append to existing list | |
| const char* const* existing = *storage; | |
| int count = 0; | |
| for(int i=0; existing[i] != NULL; ++i) | |
| ++count; | |
| for(int i=0; newlist[i] != NULL; ++i) | |
| ++count; | |
| const char** combinedList = new const char*[count+2]; | |
| int index = 0; | |
| for(int i=0; existing[i] != NULL; ++i) | |
| combinedList[index++] = existing[i]; | |
| for(int i=0; newlist[i] != NULL; ++i) | |
| combinedList[index++] = newlist[i]; | |
| combinedList[index] = NULL; | |
| // leak old arrays | |
| *storage = combinedList; | |
| } | |
| } | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| static void paths_expand_roots(const char **paths, const char *key, const char *val) | |
| { | |
| // assert(val != NULL); | |
| // assert(paths != NULL); | |
| if(NULL != key) { | |
| size_t keyLen = strlen(key); | |
| for(int i=0; paths[i] != NULL; ++i) { | |
| if ( strncmp(paths[i], key, keyLen) == 0 ) { | |
| char* newPath = new char[strlen(val) + (strlen(paths[i]) - keyLen) + 1]; | |
| strcpy(newPath, val); | |
| strcat(newPath, &paths[i][keyLen]); | |
| paths[i] = newPath; | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| static void removePathWithPrefix(const char* paths[], const char* prefix) | |
| { | |
| size_t prefixLen = strlen(prefix); | |
| int skip = 0; | |
| int i; | |
| for(i = 0; paths[i] != NULL; ++i) { | |
| if ( strncmp(paths[i], prefix, prefixLen) == 0 ) | |
| ++skip; | |
| else | |
| paths[i-skip] = paths[i]; | |
| } | |
| paths[i-skip] = NULL; | |
| } | |
| #endif | |
| #if 0 | |
| static void paths_dump(const char **paths) | |
| { | |
| // assert(paths != NULL); | |
| const char **strs = paths; | |
| while(*strs != NULL) | |
| { | |
| dyld::log("\"%s\"\n", *strs); | |
| strs++; | |
| } | |
| return; | |
| } | |
| #endif | |
| static void printOptions(const char* argv[]) | |
| { | |
| uint32_t i = 0; | |
| while ( NULL != argv[i] ) { | |
| dyld::log("opt[%i] = \"%s\"\n", i, argv[i]); | |
| i++; | |
| } | |
| } | |
| static void printEnvironmentVariables(const char* envp[]) | |
| { | |
| while ( NULL != *envp ) { | |
| dyld::log("%s\n", *envp); | |
| envp++; | |
| } | |
| } | |
| void processDyldEnvironmentVariable(const char* key, const char* value, const char* mainExecutableDir) | |
| { | |
| if ( strcmp(key, "DYLD_FRAMEWORK_PATH") == 0 ) { | |
| appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_FRAMEWORK_PATH); | |
| } | |
| else if ( strcmp(key, "DYLD_FALLBACK_FRAMEWORK_PATH") == 0 ) { | |
| appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_FALLBACK_FRAMEWORK_PATH); | |
| } | |
| else if ( strcmp(key, "DYLD_LIBRARY_PATH") == 0 ) { | |
| appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_LIBRARY_PATH); | |
| } | |
| else if ( strcmp(key, "DYLD_FALLBACK_LIBRARY_PATH") == 0 ) { | |
| appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_FALLBACK_LIBRARY_PATH); | |
| } | |
| #if SUPPORT_ROOT_PATH | |
| else if ( (strcmp(key, "DYLD_ROOT_PATH") == 0) || (strcmp(key, "DYLD_PATHS_ROOT") == 0) ) { | |
| if ( strcmp(value, "/") != 0 ) { | |
| gLinkContext.rootPaths = parseColonList(value, mainExecutableDir); | |
| for (int i=0; gLinkContext.rootPaths[i] != NULL; ++i) { | |
| if ( gLinkContext.rootPaths[i][0] != '/' ) { | |
| dyld::warn("DYLD_ROOT_PATH not used because it contains a non-absolute path\n"); | |
| gLinkContext.rootPaths = NULL; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| else if ( strcmp(key, "DYLD_IMAGE_SUFFIX") == 0 ) { | |
| gLinkContext.imageSuffix = value; | |
| } | |
| else if ( strcmp(key, "DYLD_INSERT_LIBRARIES") == 0 ) { | |
| sEnv.DYLD_INSERT_LIBRARIES = parseColonList(value, NULL); | |
| #if SUPPORT_ACCELERATE_TABLES | |
| sDisableAcceleratorTables = true; | |
| #endif | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_OPTS") == 0 ) { | |
| sEnv.DYLD_PRINT_OPTS = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_ENV") == 0 ) { | |
| sEnv.DYLD_PRINT_ENV = true; | |
| } | |
| else if ( strcmp(key, "DYLD_DISABLE_DOFS") == 0 ) { | |
| sEnv.DYLD_DISABLE_DOFS = true; | |
| } | |
| else if ( strcmp(key, "DYLD_DISABLE_PREFETCH") == 0 ) { | |
| gLinkContext.preFetchDisabled = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_LIBRARIES") == 0 ) { | |
| gLinkContext.verboseLoading = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_LIBRARIES_POST_LAUNCH") == 0 ) { | |
| sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH = true; | |
| } | |
| else if ( strcmp(key, "DYLD_BIND_AT_LAUNCH") == 0 ) { | |
| sEnv.DYLD_BIND_AT_LAUNCH = true; | |
| } | |
| else if ( strcmp(key, "DYLD_FORCE_FLAT_NAMESPACE") == 0 ) { | |
| gLinkContext.bindFlat = true; | |
| } | |
| else if ( strcmp(key, "DYLD_NEW_LOCAL_SHARED_REGIONS") == 0 ) { | |
| // ignore, no longer relevant but some scripts still set it | |
| } | |
| else if ( strcmp(key, "DYLD_NO_FIX_PREBINDING") == 0 ) { | |
| } | |
| else if ( strcmp(key, "DYLD_PREBIND_DEBUG") == 0 ) { | |
| gLinkContext.verbosePrebinding = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_INITIALIZERS") == 0 ) { | |
| gLinkContext.verboseInit = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_DOFS") == 0 ) { | |
| gLinkContext.verboseDOF = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_STATISTICS") == 0 ) { | |
| sEnv.DYLD_PRINT_STATISTICS = true; | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR | |
| // <rdar://problem/26614838> DYLD_PRINT_STATISTICS no longer logs to xcode console for device apps | |
| sForceStderr = true; | |
| #endif | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_TO_STDERR") == 0 ) { | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR | |
| // <rdar://problem/26633440> DYLD_PRINT_STATISTICS no longer logs to xcode console for device apps | |
| sForceStderr = true; | |
| #endif | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_STATISTICS_DETAILS") == 0 ) { | |
| sEnv.DYLD_PRINT_STATISTICS_DETAILS = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_SEGMENTS") == 0 ) { | |
| gLinkContext.verboseMapping = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_BINDINGS") == 0 ) { | |
| gLinkContext.verboseBind = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_WEAK_BINDINGS") == 0 ) { | |
| gLinkContext.verboseWeakBind = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_REBASINGS") == 0 ) { | |
| gLinkContext.verboseRebase = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_APIS") == 0 ) { | |
| gLogAPIs = true; | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| else if ( strcmp(key, "DYLD_PRINT_APIS_APP") == 0 ) { | |
| gLogAppAPIs = true; | |
| } | |
| #endif | |
| else if ( strcmp(key, "DYLD_PRINT_WARNINGS") == 0 ) { | |
| gLinkContext.verboseWarnings = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_RPATHS") == 0 ) { | |
| gLinkContext.verboseRPaths = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_CS_NOTIFICATIONS") == 0 ) { | |
| sEnv.DYLD_PRINT_CS_NOTIFICATIONS = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_INTERPOSING") == 0 ) { | |
| gLinkContext.verboseInterposing = true; | |
| } | |
| else if ( strcmp(key, "DYLD_PRINT_CODE_SIGNATURES") == 0 ) { | |
| gLinkContext.verboseCodeSignatures = true; | |
| } | |
| else if ( strcmp(key, "DYLD_SHARED_REGION") == 0 ) { | |
| if ( strcmp(value, "private") == 0 ) { | |
| gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion; | |
| } | |
| else if ( strcmp(value, "avoid") == 0 ) { | |
| gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; | |
| } | |
| else if ( strcmp(value, "use") == 0 ) { | |
| gLinkContext.sharedRegionMode = ImageLoader::kUseSharedRegion; | |
| } | |
| else if ( value[0] == '\0' ) { | |
| gLinkContext.sharedRegionMode = ImageLoader::kUseSharedRegion; | |
| } | |
| else { | |
| dyld::warn("unknown option to DYLD_SHARED_REGION. Valid options are: use, private, avoid\n"); | |
| } | |
| } | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| else if ( strcmp(key, "DYLD_SHARED_CACHE_DIR") == 0 ) { | |
| sSharedCacheDir = value; | |
| } | |
| else if ( strcmp(key, "DYLD_SHARED_CACHE_DONT_VALIDATE") == 0 ) { | |
| sSharedCacheIgnoreInodeAndTimeStamp = true; | |
| } | |
| #endif | |
| else if ( strcmp(key, "DYLD_IGNORE_PREBINDING") == 0 ) { | |
| if ( strcmp(value, "all") == 0 ) { | |
| gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding; | |
| } | |
| else if ( strcmp(value, "app") == 0 ) { | |
| gLinkContext.prebindUsage = ImageLoader::kUseAllButAppPredbinding; | |
| } | |
| else if ( strcmp(value, "nonsplit") == 0 ) { | |
| gLinkContext.prebindUsage = ImageLoader::kUseSplitSegPrebinding; | |
| } | |
| else if ( value[0] == '\0' ) { | |
| gLinkContext.prebindUsage = ImageLoader::kUseSplitSegPrebinding; | |
| } | |
| else { | |
| dyld::warn("unknown option to DYLD_IGNORE_PREBINDING. Valid options are: all, app, nonsplit\n"); | |
| } | |
| } | |
| #if SUPPORT_VERSIONED_PATHS | |
| else if ( strcmp(key, "DYLD_VERSIONED_LIBRARY_PATH") == 0 ) { | |
| appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_VERSIONED_LIBRARY_PATH); | |
| #if SUPPORT_ACCELERATE_TABLES | |
| sDisableAcceleratorTables = true; | |
| #endif | |
| } | |
| else if ( strcmp(key, "DYLD_VERSIONED_FRAMEWORK_PATH") == 0 ) { | |
| appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_VERSIONED_FRAMEWORK_PATH); | |
| #if SUPPORT_ACCELERATE_TABLES | |
| sDisableAcceleratorTables = true; | |
| #endif | |
| } | |
| #endif | |
| #if !TARGET_IPHONE_SIMULATOR | |
| else if ( (strcmp(key, "DYLD_PRINT_TO_FILE") == 0) && (mainExecutableDir == NULL) ) { | |
| int fd = open(value, O_WRONLY | O_CREAT | O_APPEND, 0644); | |
| if ( fd != -1 ) { | |
| sLogfile = fd; | |
| sLogToFile = true; | |
| } | |
| else { | |
| dyld::log("dyld: could not open DYLD_PRINT_TO_FILE='%s', errno=%d\n", value, errno); | |
| } | |
| } | |
| #endif | |
| else { | |
| dyld::warn("unknown environment variable: %s\n", key); | |
| } | |
| } | |
| #if SUPPORT_LC_DYLD_ENVIRONMENT | |
| static void checkLoadCommandEnvironmentVariables() | |
| { | |
| // <rdar://problem/8440934> Support augmenting dyld environment variables in load commands | |
| const uint32_t cmd_count = sMainExecutableMachHeader->ncmds; | |
| const struct load_command* const cmds = (struct load_command*)(((char*)sMainExecutableMachHeader)+sizeof(macho_header)); | |
| const struct load_command* cmd = cmds; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| switch (cmd->cmd) { | |
| case LC_DYLD_ENVIRONMENT: | |
| { | |
| const struct dylinker_command* envcmd = (struct dylinker_command*)cmd; | |
| const char* keyEqualsValue = (char*)envcmd + envcmd->name.offset; | |
| char mainExecutableDir[strlen(sExecPath)+2]; | |
| strcpy(mainExecutableDir, sExecPath); | |
| char* lastSlash = strrchr(mainExecutableDir, '/'); | |
| if ( lastSlash != NULL) | |
| lastSlash[1] = '\0'; | |
| // only process variables that start with DYLD_ and end in _PATH | |
| if ( (strncmp(keyEqualsValue, "DYLD_", 5) == 0) ) { | |
| const char* equals = strchr(keyEqualsValue, '='); | |
| if ( equals != NULL ) { | |
| if ( strncmp(&equals[-5], "_PATH", 5) == 0 ) { | |
| const char* value = &equals[1]; | |
| const size_t keyLen = equals-keyEqualsValue; | |
| // <rdar://problem/22799635> don't let malformed load command overflow stack | |
| if ( keyLen < 40 ) { | |
| char key[keyLen+1]; | |
| strncpy(key, keyEqualsValue, keyLen); | |
| key[keyLen] = '\0'; | |
| //dyld::log("processing: %s\n", keyEqualsValue); | |
| //dyld::log("mainExecutableDir: %s\n", mainExecutableDir); | |
| processDyldEnvironmentVariable(key, value, mainExecutableDir); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| break; | |
| } | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| } | |
| } | |
| #endif // SUPPORT_LC_DYLD_ENVIRONMENT | |
| static bool hasCodeSignatureLoadCommand(const macho_header* mh) | |
| { | |
| const uint32_t cmd_count = mh->ncmds; | |
| const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); | |
| const struct load_command* cmd = cmds; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| if (cmd->cmd == LC_CODE_SIGNATURE) | |
| return true; | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| } | |
| return false; | |
| } | |
| #if SUPPORT_VERSIONED_PATHS | |
| static void checkVersionedPaths() | |
| { | |
| // search DYLD_VERSIONED_LIBRARY_PATH directories for dylibs and check if they are newer | |
| if ( sEnv.DYLD_VERSIONED_LIBRARY_PATH != NULL ) { | |
| for(const char* const* lp = sEnv.DYLD_VERSIONED_LIBRARY_PATH; *lp != NULL; ++lp) { | |
| checkDylibOverridesInDir(*lp); | |
| } | |
| } | |
| // search DYLD_VERSIONED_FRAMEWORK_PATH directories for dylibs and check if they are newer | |
| if ( sEnv.DYLD_VERSIONED_FRAMEWORK_PATH != NULL ) { | |
| for(const char* const* fp = sEnv.DYLD_VERSIONED_FRAMEWORK_PATH; *fp != NULL; ++fp) { | |
| checkFrameworkOverridesInDir(*fp); | |
| } | |
| } | |
| } | |
| #endif | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| // | |
| // For security, setuid programs ignore DYLD_* environment variables. | |
| // Additionally, the DYLD_* enviroment variables are removed | |
| // from the environment, so that any child processes don't see them. | |
| // | |
| static void pruneEnvironmentVariables(const char* envp[], const char*** applep) | |
| { | |
| #if SUPPORT_LC_DYLD_ENVIRONMENT | |
| checkLoadCommandEnvironmentVariables(); | |
| #endif | |
| // delete all DYLD_* and LD_LIBRARY_PATH environment variables | |
| int removedCount = 0; | |
| const char** d = envp; | |
| for(const char** s = envp; *s != NULL; s++) { | |
| if ( (strncmp(*s, "DYLD_", 5) != 0) && (strncmp(*s, "LD_LIBRARY_PATH=", 16) != 0) ) { | |
| *d++ = *s; | |
| } | |
| else { | |
| ++removedCount; | |
| } | |
| } | |
| *d++ = NULL; | |
| // slide apple parameters | |
| if ( removedCount > 0 ) { | |
| *applep = d; | |
| do { | |
| *d = d[removedCount]; | |
| } while ( *d++ != NULL ); | |
| for(int i=0; i < removedCount; ++i) | |
| *d++ = NULL; | |
| } | |
| // disable framework and library fallback paths for setuid binaries rdar://problem/4589305 | |
| sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = NULL; | |
| sEnv.DYLD_FALLBACK_LIBRARY_PATH = NULL; | |
| if ( removedCount > 0 ) | |
| strlcat(sLoadingCrashMessage, ", ignoring DYLD_* env vars", sizeof(sLoadingCrashMessage)); | |
| } | |
| #endif | |
| static void defaultUninitializedFallbackPaths(const char* envp[]) | |
| { | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| if ( gLinkContext.processIsRestricted ) { | |
| sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = sRestrictedFrameworkFallbackPaths; | |
| sEnv.DYLD_FALLBACK_LIBRARY_PATH = sRestrictedLibraryFallbackPaths; | |
| return; | |
| } | |
| // default value for DYLD_FALLBACK_FRAMEWORK_PATH, if not set in environment | |
| const char* home = _simple_getenv(envp, "HOME");; | |
| if ( sEnv.DYLD_FALLBACK_FRAMEWORK_PATH == NULL ) { | |
| const char** fpaths = sFrameworkFallbackPaths; | |
| if ( home == NULL ) | |
| removePathWithPrefix(fpaths, "$HOME"); | |
| else | |
| paths_expand_roots(fpaths, "$HOME", home); | |
| sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = fpaths; | |
| } | |
| // default value for DYLD_FALLBACK_LIBRARY_PATH, if not set in environment | |
| if ( sEnv.DYLD_FALLBACK_LIBRARY_PATH == NULL ) { | |
| const char** lpaths = sLibraryFallbackPaths; | |
| if ( home == NULL ) | |
| removePathWithPrefix(lpaths, "$HOME"); | |
| else | |
| paths_expand_roots(lpaths, "$HOME", home); | |
| sEnv.DYLD_FALLBACK_LIBRARY_PATH = lpaths; | |
| } | |
| #else | |
| if ( sEnv.DYLD_FALLBACK_FRAMEWORK_PATH == NULL ) | |
| sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = sFrameworkFallbackPaths; | |
| if ( sEnv.DYLD_FALLBACK_LIBRARY_PATH == NULL ) | |
| sEnv.DYLD_FALLBACK_LIBRARY_PATH = sLibraryFallbackPaths; | |
| #endif | |
| } | |
| static void checkEnvironmentVariables(const char* envp[]) | |
| { | |
| if ( sEnvMode == envNone ) | |
| return; | |
| const char** p; | |
| for(p = envp; *p != NULL; p++) { | |
| const char* keyEqualsValue = *p; | |
| if ( strncmp(keyEqualsValue, "DYLD_", 5) == 0 ) { | |
| const char* equals = strchr(keyEqualsValue, '='); | |
| if ( equals != NULL ) { | |
| strlcat(sLoadingCrashMessage, "\n", sizeof(sLoadingCrashMessage)); | |
| strlcat(sLoadingCrashMessage, keyEqualsValue, sizeof(sLoadingCrashMessage)); | |
| const char* value = &equals[1]; | |
| const size_t keyLen = equals-keyEqualsValue; | |
| char key[keyLen+1]; | |
| strncpy(key, keyEqualsValue, keyLen); | |
| key[keyLen] = '\0'; | |
| if ( (sEnvMode == envPrintOnly) && (strncmp(key, "DYLD_PRINT_", 11) != 0) ) | |
| continue; | |
| processDyldEnvironmentVariable(key, value, NULL); | |
| } | |
| } | |
| else if ( strncmp(keyEqualsValue, "LD_LIBRARY_PATH=", 16) == 0 ) { | |
| const char* path = &keyEqualsValue[16]; | |
| sEnv.LD_LIBRARY_PATH = parseColonList(path, NULL); | |
| } | |
| } | |
| #if SUPPORT_LC_DYLD_ENVIRONMENT | |
| checkLoadCommandEnvironmentVariables(); | |
| #endif // SUPPORT_LC_DYLD_ENVIRONMENT | |
| #if SUPPORT_ROOT_PATH | |
| // <rdar://problem/11281064> DYLD_IMAGE_SUFFIX and DYLD_ROOT_PATH cannot be used together | |
| if ( (gLinkContext.imageSuffix != NULL) && (gLinkContext.rootPaths != NULL) ) { | |
| dyld::warn("Ignoring DYLD_IMAGE_SUFFIX because DYLD_ROOT_PATH is used.\n"); | |
| gLinkContext.imageSuffix = NULL; | |
| } | |
| #endif | |
| } | |
| #if __x86_64__ && DYLD_SHARED_CACHE_SUPPORT | |
| static bool isGCProgram(const macho_header* mh, uintptr_t slide) | |
| { | |
| const uint32_t cmd_count = mh->ncmds; | |
| const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); | |
| const struct load_command* cmd = cmds; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| switch (cmd->cmd) { | |
| case LC_SEGMENT_COMMAND: | |
| { | |
| const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; | |
| if (strcmp(seg->segname, "__DATA") == 0) { | |
| const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); | |
| const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; | |
| for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { | |
| if (strncmp(sect->sectname, "__objc_imageinfo", 16) == 0) { | |
| const uint32_t* objcInfo = (uint32_t*)(sect->addr + slide); | |
| return (objcInfo[1] & 6); // 6 = (OBJC_IMAGE_SUPPORTS_GC | OBJC_IMAGE_REQUIRES_GC) | |
| } | |
| } | |
| } | |
| } | |
| break; | |
| } | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| } | |
| return false; | |
| } | |
| #endif | |
| static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide) | |
| { | |
| #if CPU_SUBTYPES_SUPPORTED | |
| #if __ARM_ARCH_7K__ | |
| sHostCPU = CPU_TYPE_ARM; | |
| sHostCPUsubtype = CPU_SUBTYPE_ARM_V7K; | |
| #elif __ARM_ARCH_7A__ | |
| sHostCPU = CPU_TYPE_ARM; | |
| sHostCPUsubtype = CPU_SUBTYPE_ARM_V7; | |
| #elif __ARM_ARCH_6K__ | |
| sHostCPU = CPU_TYPE_ARM; | |
| sHostCPUsubtype = CPU_SUBTYPE_ARM_V6; | |
| #elif __ARM_ARCH_7F__ | |
| sHostCPU = CPU_TYPE_ARM; | |
| sHostCPUsubtype = CPU_SUBTYPE_ARM_V7F; | |
| #elif __ARM_ARCH_7S__ | |
| sHostCPU = CPU_TYPE_ARM; | |
| sHostCPUsubtype = CPU_SUBTYPE_ARM_V7S; | |
| #else | |
| struct host_basic_info info; | |
| mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; | |
| mach_port_t hostPort = mach_host_self(); | |
| kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count); | |
| if ( result != KERN_SUCCESS ) | |
| throw "host_info() failed"; | |
| sHostCPU = info.cpu_type; | |
| sHostCPUsubtype = info.cpu_subtype; | |
| mach_port_deallocate(mach_task_self(), hostPort); | |
| #if __x86_64__ | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| sHaswell = (sHostCPUsubtype == CPU_SUBTYPE_X86_64_H); | |
| // <rdar://problem/18528074> x86_64h: Fall back to the x86_64 slice if an app requires GC. | |
| if ( sHaswell ) { | |
| if ( isGCProgram(mainExecutableMH, mainExecutableSlide) ) { | |
| // When running a GC program on a haswell machine, don't use and 'h slices | |
| sHostCPUsubtype = CPU_SUBTYPE_X86_64_ALL; | |
| sHaswell = false; | |
| gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; | |
| } | |
| } | |
| #endif | |
| #endif | |
| #endif | |
| #endif | |
| } | |
| static void checkSharedRegionDisable() | |
| { | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| // if main executable has segments that overlap the shared region, | |
| // then disable using the shared region | |
| if ( sMainExecutable->overlapsWithAddressRange((void*)(uintptr_t)SHARED_REGION_BASE, (void*)(uintptr_t)(SHARED_REGION_BASE + SHARED_REGION_SIZE)) ) { | |
| gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; | |
| if ( gLinkContext.verboseMapping ) | |
| dyld::warn("disabling shared region because main executable overlaps\n"); | |
| } | |
| #if __i386__ | |
| if ( gLinkContext.processIsRestricted ) { | |
| // <rdar://problem/15280847> use private or no shared region for suid processes | |
| gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion; | |
| } | |
| #endif | |
| #endif | |
| // iPhoneOS cannot run without shared region | |
| } | |
| bool validImage(const ImageLoader* possibleImage) | |
| { | |
| const size_t imageCount = sAllImages.size(); | |
| for(size_t i=0; i < imageCount; ++i) { | |
| if ( possibleImage == sAllImages[i] ) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| uint32_t getImageCount() | |
| { | |
| return (uint32_t)sAllImages.size(); | |
| } | |
| ImageLoader* getIndexedImage(unsigned int index) | |
| { | |
| if ( index < sAllImages.size() ) | |
| return sAllImages[index]; | |
| return NULL; | |
| } | |
| ImageLoader* findImageByMachHeader(const struct mach_header* target) | |
| { | |
| return findMappedRange((uintptr_t)target); | |
| } | |
| ImageLoader* findImageContainingAddress(const void* addr) | |
| { | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( sAllCacheImagesProxy != NULL ) { | |
| const mach_header* mh; | |
| const char* path; | |
| unsigned index; | |
| if ( sAllCacheImagesProxy->addressInCache(addr, &mh, &path, &index) ) | |
| return sAllCacheImagesProxy; | |
| } | |
| #endif | |
| return findMappedRange((uintptr_t)addr); | |
| } | |
| ImageLoader* findImageContainingSymbol(const void* symbol) | |
| { | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* anImage = *it; | |
| if ( anImage->containsSymbol(symbol) ) | |
| return anImage; | |
| } | |
| return NULL; | |
| } | |
| void forEachImageDo( void (*callback)(ImageLoader*, void* userData), void* userData) | |
| { | |
| const size_t imageCount = sAllImages.size(); | |
| for(size_t i=0; i < imageCount; ++i) { | |
| ImageLoader* anImage = sAllImages[i]; | |
| (*callback)(anImage, userData); | |
| } | |
| } | |
| ImageLoader* findLoadedImage(const struct stat& stat_buf) | |
| { | |
| const size_t imageCount = sAllImages.size(); | |
| for(size_t i=0; i < imageCount; ++i){ | |
| ImageLoader* anImage = sAllImages[i]; | |
| if ( anImage->statMatch(stat_buf) ) | |
| return anImage; | |
| } | |
| return NULL; | |
| } | |
| // based on ANSI-C strstr() | |
| static const char* strrstr(const char* str, const char* sub) | |
| { | |
| const size_t sublen = strlen(sub); | |
| for(const char* p = &str[strlen(str)]; p != str; --p) { | |
| if ( strncmp(p, sub, sublen) == 0 ) | |
| return p; | |
| } | |
| return NULL; | |
| } | |
| // | |
| // Find framework path | |
| // | |
| // /path/foo.framework/foo => foo.framework/foo | |
| // /path/foo.framework/Versions/A/foo => foo.framework/Versions/A/foo | |
| // /path/foo.framework/Frameworks/bar.framework/bar => bar.framework/bar | |
| // /path/foo.framework/Libraries/bar.dylb => NULL | |
| // /path/foo.framework/bar => NULL | |
| // | |
| // Returns NULL if not a framework path | |
| // | |
| static const char* getFrameworkPartialPath(const char* path) | |
| { | |
| const char* dirDot = strrstr(path, ".framework/"); | |
| if ( dirDot != NULL ) { | |
| const char* dirStart = dirDot; | |
| for ( ; dirStart >= path; --dirStart) { | |
| if ( (*dirStart == '/') || (dirStart == path) ) { | |
| const char* frameworkStart = &dirStart[1]; | |
| if ( dirStart == path ) | |
| --frameworkStart; | |
| size_t len = dirDot - frameworkStart; | |
| char framework[len+1]; | |
| strncpy(framework, frameworkStart, len); | |
| framework[len] = '\0'; | |
| const char* leaf = strrchr(path, '/'); | |
| if ( leaf != NULL ) { | |
| if ( strcmp(framework, &leaf[1]) == 0 ) { | |
| return frameworkStart; | |
| } | |
| if ( gLinkContext.imageSuffix != NULL ) { | |
| // some debug frameworks have install names that end in _debug | |
| if ( strncmp(framework, &leaf[1], len) == 0 ) { | |
| if ( strcmp( gLinkContext.imageSuffix, &leaf[len+1]) == 0 ) | |
| return frameworkStart; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return NULL; | |
| } | |
| static const char* getLibraryLeafName(const char* path) | |
| { | |
| const char* start = strrchr(path, '/'); | |
| if ( start != NULL ) | |
| return &start[1]; | |
| else | |
| return path; | |
| } | |
| // only for architectures that use cpu-sub-types | |
| #if CPU_SUBTYPES_SUPPORTED | |
| const cpu_subtype_t CPU_SUBTYPE_END_OF_LIST = -1; | |
| // | |
| // A fat file may contain multiple sub-images for the same CPU type. | |
| // In that case, dyld picks which sub-image to use by scanning a table | |
| // of preferred cpu-sub-types for the running cpu. | |
| // | |
| // There is one row in the table for each cpu-sub-type on which dyld might run. | |
| // The first entry in a row is that cpu-sub-type. It is followed by all | |
| // cpu-sub-types that can run on that cpu, if preferred order. Each row ends with | |
| // a "SUBTYPE_ALL" (to denote that images written to run on any cpu-sub-type are usable), | |
| // followed by one or more CPU_SUBTYPE_END_OF_LIST to pad out this row. | |
| // | |
| #if __arm__ | |
| // | |
| // ARM sub-type lists | |
| // | |
| const int kARM_RowCount = 8; | |
| static const cpu_subtype_t kARM[kARM_RowCount][9] = { | |
| // armv7f can run: v7f, v7, v6, v5, and v4 | |
| { CPU_SUBTYPE_ARM_V7F, CPU_SUBTYPE_ARM_V7, CPU_SUBTYPE_ARM_V6, CPU_SUBTYPE_ARM_V5TEJ, CPU_SUBTYPE_ARM_V4T, CPU_SUBTYPE_ARM_ALL, CPU_SUBTYPE_END_OF_LIST }, | |
| // armv7k can run: v7k | |
| { CPU_SUBTYPE_ARM_V7K, CPU_SUBTYPE_END_OF_LIST }, | |
| // armv7s can run: v7s, v7, v7f, v7k, v6, v5, and v4 | |
| { CPU_SUBTYPE_ARM_V7S, CPU_SUBTYPE_ARM_V7, CPU_SUBTYPE_ARM_V7F, CPU_SUBTYPE_ARM_V6, CPU_SUBTYPE_ARM_V5TEJ, CPU_SUBTYPE_ARM_V4T, CPU_SUBTYPE_ARM_ALL, CPU_SUBTYPE_END_OF_LIST }, | |
| // armv7 can run: v7, v6, v5, and v4 | |
| { CPU_SUBTYPE_ARM_V7, CPU_SUBTYPE_ARM_V6, CPU_SUBTYPE_ARM_V5TEJ, CPU_SUBTYPE_ARM_V4T, CPU_SUBTYPE_ARM_ALL, CPU_SUBTYPE_END_OF_LIST }, | |
| // armv6 can run: v6, v5, and v4 | |
| { CPU_SUBTYPE_ARM_V6, CPU_SUBTYPE_ARM_V5TEJ, CPU_SUBTYPE_ARM_V4T, CPU_SUBTYPE_ARM_ALL, CPU_SUBTYPE_END_OF_LIST, CPU_SUBTYPE_END_OF_LIST }, | |
| // xscale can run: xscale, v5, and v4 | |
| { CPU_SUBTYPE_ARM_XSCALE, CPU_SUBTYPE_ARM_V5TEJ, CPU_SUBTYPE_ARM_V4T, CPU_SUBTYPE_ARM_ALL, CPU_SUBTYPE_END_OF_LIST, CPU_SUBTYPE_END_OF_LIST }, | |
| // armv5 can run: v5 and v4 | |
| { CPU_SUBTYPE_ARM_V5TEJ, CPU_SUBTYPE_ARM_V4T, CPU_SUBTYPE_ARM_ALL, CPU_SUBTYPE_END_OF_LIST, CPU_SUBTYPE_END_OF_LIST, CPU_SUBTYPE_END_OF_LIST }, | |
| // armv4 can run: v4 | |
| { CPU_SUBTYPE_ARM_V4T, CPU_SUBTYPE_ARM_ALL, CPU_SUBTYPE_END_OF_LIST, CPU_SUBTYPE_END_OF_LIST, CPU_SUBTYPE_END_OF_LIST, CPU_SUBTYPE_END_OF_LIST }, | |
| }; | |
| #endif | |
| #if __x86_64__ | |
| // | |
| // x86_64 sub-type lists | |
| // | |
| const int kX86_64_RowCount = 2; | |
| static const cpu_subtype_t kX86_64[kX86_64_RowCount][5] = { | |
| // x86_64h can run: x86_64h, x86_64h(lib), x86_64(lib), and x86_64 | |
| { CPU_SUBTYPE_X86_64_H, CPU_SUBTYPE_LIB64|CPU_SUBTYPE_X86_64_H, CPU_SUBTYPE_LIB64|CPU_SUBTYPE_X86_64_ALL, CPU_SUBTYPE_X86_64_ALL, CPU_SUBTYPE_END_OF_LIST }, | |
| // x86_64 can run: x86_64(lib) and x86_64 | |
| { CPU_SUBTYPE_X86_64_ALL, CPU_SUBTYPE_LIB64|CPU_SUBTYPE_X86_64_ALL, CPU_SUBTYPE_END_OF_LIST }, | |
| }; | |
| #endif | |
| // scan the tables above to find the cpu-sub-type-list for this machine | |
| static const cpu_subtype_t* findCPUSubtypeList(cpu_type_t cpu, cpu_subtype_t subtype) | |
| { | |
| switch (cpu) { | |
| #if __arm__ | |
| case CPU_TYPE_ARM: | |
| for (int i=0; i < kARM_RowCount ; ++i) { | |
| if ( kARM[i][0] == subtype ) | |
| return kARM[i]; | |
| } | |
| break; | |
| #endif | |
| #if __x86_64__ | |
| case CPU_TYPE_X86_64: | |
| for (int i=0; i < kX86_64_RowCount ; ++i) { | |
| if ( kX86_64[i][0] == subtype ) | |
| return kX86_64[i]; | |
| } | |
| break; | |
| #endif | |
| } | |
| return NULL; | |
| } | |
| // scan fat table-of-contents for best most preferred subtype | |
| static bool fatFindBestFromOrderedList(cpu_type_t cpu, const cpu_subtype_t list[], const fat_header* fh, uint64_t* offset, uint64_t* len) | |
| { | |
| const fat_arch* const archs = (fat_arch*)(((char*)fh)+sizeof(fat_header)); | |
| for (uint32_t subTypeIndex=0; list[subTypeIndex] != CPU_SUBTYPE_END_OF_LIST; ++subTypeIndex) { | |
| for(uint32_t fatIndex=0; fatIndex < OSSwapBigToHostInt32(fh->nfat_arch); ++fatIndex) { | |
| if ( ((cpu_type_t)OSSwapBigToHostInt32(archs[fatIndex].cputype) == cpu) | |
| && (list[subTypeIndex] == (cpu_subtype_t)OSSwapBigToHostInt32(archs[fatIndex].cpusubtype)) ) { | |
| *offset = OSSwapBigToHostInt32(archs[fatIndex].offset); | |
| *len = OSSwapBigToHostInt32(archs[fatIndex].size); | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| // scan fat table-of-contents for exact match of cpu and cpu-sub-type | |
| static bool fatFindExactMatch(cpu_type_t cpu, cpu_subtype_t subtype, const fat_header* fh, uint64_t* offset, uint64_t* len) | |
| { | |
| const fat_arch* archs = (fat_arch*)(((char*)fh)+sizeof(fat_header)); | |
| for(uint32_t i=0; i < OSSwapBigToHostInt32(fh->nfat_arch); ++i) { | |
| if ( ((cpu_type_t)OSSwapBigToHostInt32(archs[i].cputype) == cpu) | |
| && ((cpu_subtype_t)OSSwapBigToHostInt32(archs[i].cpusubtype) == subtype) ) { | |
| *offset = OSSwapBigToHostInt32(archs[i].offset); | |
| *len = OSSwapBigToHostInt32(archs[i].size); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // scan fat table-of-contents for image with matching cpu-type and runs-on-all-sub-types | |
| static bool fatFindRunsOnAllCPUs(cpu_type_t cpu, const fat_header* fh, uint64_t* offset, uint64_t* len) | |
| { | |
| const fat_arch* archs = (fat_arch*)(((char*)fh)+sizeof(fat_header)); | |
| for(uint32_t i=0; i < OSSwapBigToHostInt32(fh->nfat_arch); ++i) { | |
| if ( (cpu_type_t)OSSwapBigToHostInt32(archs[i].cputype) == cpu) { | |
| switch (cpu) { | |
| #if __arm__ | |
| case CPU_TYPE_ARM: | |
| if ( (cpu_subtype_t)OSSwapBigToHostInt32(archs[i].cpusubtype) == CPU_SUBTYPE_ARM_ALL ) { | |
| *offset = OSSwapBigToHostInt32(archs[i].offset); | |
| *len = OSSwapBigToHostInt32(archs[i].size); | |
| return true; | |
| } | |
| break; | |
| #endif | |
| #if __x86_64__ | |
| case CPU_TYPE_X86_64: | |
| if ( (cpu_subtype_t)OSSwapBigToHostInt32(archs[i].cpusubtype) == CPU_SUBTYPE_X86_64_ALL ) { | |
| *offset = OSSwapBigToHostInt32(archs[i].offset); | |
| *len = OSSwapBigToHostInt32(archs[i].size); | |
| return true; | |
| } | |
| break; | |
| #endif | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| #endif // CPU_SUBTYPES_SUPPORTED | |
| // | |
| // Validate the fat_header and fat_arch array: | |
| // | |
| // 1) arch count would not cause array to extend past 4096 byte read buffer | |
| // 2) no slice overlaps the fat_header and arch array | |
| // 3) arch list does not contain duplicate cputype/cpusubtype tuples | |
| // 4) arch list does not have two overlapping slices. | |
| // | |
| static bool fatValidate(const fat_header* fh) | |
| { | |
| if ( fh->magic != OSSwapBigToHostInt32(FAT_MAGIC) ) | |
| return false; | |
| // since only first 4096 bytes of file read, we can only handle up to 204 slices. | |
| const uint32_t sliceCount = OSSwapBigToHostInt32(fh->nfat_arch); | |
| if ( sliceCount > 204 ) | |
| return false; | |
| // compare all slices looking for conflicts | |
| const fat_arch* archs = (fat_arch*)(((char*)fh)+sizeof(fat_header)); | |
| for (uint32_t i=0; i < sliceCount; ++i) { | |
| uint32_t i_offset = OSSwapBigToHostInt32(archs[i].offset); | |
| uint32_t i_size = OSSwapBigToHostInt32(archs[i].size); | |
| uint32_t i_cputype = OSSwapBigToHostInt32(archs[i].cputype); | |
| uint32_t i_cpusubtype = OSSwapBigToHostInt32(archs[i].cpusubtype); | |
| uint32_t i_end = i_offset + i_size; | |
| // slice cannot overlap with header | |
| if ( i_offset < 4096 ) | |
| return false; | |
| // slice size cannot overflow | |
| if ( i_end < i_offset ) | |
| return false; | |
| for (uint32_t j=i+1; j < sliceCount; ++j) { | |
| uint32_t j_offset = OSSwapBigToHostInt32(archs[j].offset); | |
| uint32_t j_size = OSSwapBigToHostInt32(archs[j].size); | |
| uint32_t j_cputype = OSSwapBigToHostInt32(archs[j].cputype); | |
| uint32_t j_cpusubtype = OSSwapBigToHostInt32(archs[j].cpusubtype); | |
| uint32_t j_end = j_offset + j_size; | |
| // duplicate slices types not allowed | |
| if ( (i_cputype == j_cputype) && (i_cpusubtype == j_cpusubtype) ) | |
| return false; | |
| // slice size cannot overflow | |
| if ( j_end < j_offset ) | |
| return false; | |
| // check for overlap of slices | |
| if ( i_offset <= j_offset ) { | |
| if ( j_offset < i_end ) | |
| return false; // j overlaps end of i | |
| } | |
| else { | |
| // j overlaps end of i | |
| if ( i_offset < j_end ) | |
| return false; // i overlaps end of j | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| // | |
| // A fat file may contain multiple sub-images for the same cpu-type, | |
| // each optimized for a different cpu-sub-type (e.g G3 or G5). | |
| // This routine picks the optimal sub-image. | |
| // | |
| static bool fatFindBest(const fat_header* fh, uint64_t* offset, uint64_t* len) | |
| { | |
| if ( !fatValidate(fh) ) | |
| return false; | |
| #if CPU_SUBTYPES_SUPPORTED | |
| // assume all dylibs loaded must have same cpu type as main executable | |
| const cpu_type_t cpu = sMainExecutableMachHeader->cputype; | |
| // We only know the subtype to use if the main executable cpu type matches the host | |
| if ( (cpu & CPU_TYPE_MASK) == sHostCPU ) { | |
| // get preference ordered list of subtypes | |
| const cpu_subtype_t* subTypePreferenceList = findCPUSubtypeList(cpu, sHostCPUsubtype); | |
| // use ordered list to find best sub-image in fat file | |
| if ( subTypePreferenceList != NULL ) { | |
| if ( fatFindBestFromOrderedList(cpu, subTypePreferenceList, fh, offset, len) ) | |
| return true; | |
| } | |
| // if running cpu is not in list, try for an exact match | |
| if ( fatFindExactMatch(cpu, sHostCPUsubtype, fh, offset, len) ) | |
| return true; | |
| } | |
| // running on an uknown cpu, can only load generic code | |
| return fatFindRunsOnAllCPUs(cpu, fh, offset, len); | |
| #else | |
| // just find first slice with matching architecture | |
| const fat_arch* archs = (fat_arch*)(((char*)fh)+sizeof(fat_header)); | |
| for(uint32_t i=0; i < OSSwapBigToHostInt32(fh->nfat_arch); ++i) { | |
| if ( (cpu_type_t)OSSwapBigToHostInt32(archs[i].cputype) == sMainExecutableMachHeader->cputype) { | |
| *offset = OSSwapBigToHostInt32(archs[i].offset); | |
| *len = OSSwapBigToHostInt32(archs[i].size); | |
| return true; | |
| } | |
| } | |
| return false; | |
| #endif | |
| } | |
| // | |
| // This is used to validate if a non-fat (aka thin or raw) mach-o file can be used | |
| // on the current processor. // | |
| bool isCompatibleMachO(const uint8_t* firstPage, const char* path) | |
| { | |
| #if CPU_SUBTYPES_SUPPORTED | |
| // It is deemed compatible if any of the following are true: | |
| // 1) mach_header subtype is in list of compatible subtypes for running processor | |
| // 2) mach_header subtype is same as running processor subtype | |
| // 3) mach_header subtype runs on all processor variants | |
| const mach_header* mh = (mach_header*)firstPage; | |
| if ( mh->magic == sMainExecutableMachHeader->magic ) { | |
| if ( mh->cputype == sMainExecutableMachHeader->cputype ) { | |
| if ( (mh->cputype & CPU_TYPE_MASK) == sHostCPU ) { | |
| // get preference ordered list of subtypes that this machine can use | |
| const cpu_subtype_t* subTypePreferenceList = findCPUSubtypeList(mh->cputype, sHostCPUsubtype); | |
| if ( subTypePreferenceList != NULL ) { | |
| // if image's subtype is in the list, it is compatible | |
| for (const cpu_subtype_t* p = subTypePreferenceList; *p != CPU_SUBTYPE_END_OF_LIST; ++p) { | |
| if ( *p == mh->cpusubtype ) | |
| return true; | |
| } | |
| // have list and not in list, so not compatible | |
| throwf("incompatible cpu-subtype: 0x%08X in %s", mh->cpusubtype, path); | |
| } | |
| // unknown cpu sub-type, but if exact match for current subtype then ok to use | |
| if ( mh->cpusubtype == sHostCPUsubtype ) | |
| return true; | |
| } | |
| // cpu type has no ordered list of subtypes | |
| switch (mh->cputype) { | |
| case CPU_TYPE_I386: | |
| case CPU_TYPE_X86_64: | |
| // subtypes are not used or these architectures | |
| return true; | |
| } | |
| } | |
| } | |
| #else | |
| // For architectures that don't support cpu-sub-types | |
| // this just check the cpu type. | |
| const mach_header* mh = (mach_header*)firstPage; | |
| if ( mh->magic == sMainExecutableMachHeader->magic ) { | |
| if ( mh->cputype == sMainExecutableMachHeader->cputype ) { | |
| return true; | |
| } | |
| } | |
| #endif | |
| return false; | |
| } | |
| // The kernel maps in main executable before dyld gets control. We need to | |
| // make an ImageLoader* for the already mapped in main executable. | |
| static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path) | |
| { | |
| // try mach-o loader | |
| if ( isCompatibleMachO((const uint8_t*)mh, path) ) { | |
| ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext); | |
| addImage(image); | |
| return (ImageLoaderMachO*)image; | |
| } | |
| throw "main executable not a known format"; | |
| } | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| static bool dylibsCanOverrideCache() | |
| { | |
| uint32_t devFlags = *((uint32_t*)_COMM_PAGE_DEV_FIRM); | |
| if ( (devFlags & 1) == 0 ) | |
| return false; | |
| return ( (sSharedCache != NULL) && (sSharedCache->cacheType == kDyldSharedCacheTypeDevelopment) ); | |
| } | |
| #endif | |
| static bool findInSharedCacheImage(const char* path, bool searchByPath, const struct stat* stat_buf, const macho_header** mh, const char** pathInCache, long* slide) | |
| { | |
| if ( sSharedCache != NULL ) { | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| // Mac OS X always requires inode/mtime to valid cache | |
| // if stat() not done yet, do it now | |
| struct stat statb; | |
| if ( stat_buf == NULL ) { | |
| if ( my_stat(path, &statb) == -1 ) | |
| return false; | |
| stat_buf = &statb; | |
| } | |
| #endif | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| uint64_t hash = 0; | |
| for (const char* s=path; *s != '\0'; ++s) | |
| hash += hash*4 + *s; | |
| #endif | |
| // walk shared cache to see if there is a cached image that matches the inode/mtime/path desired | |
| const dyld_cache_image_info* const start = (dyld_cache_image_info*)((uint8_t*)sSharedCache + sSharedCache->imagesOffset); | |
| const dyld_cache_image_info* const end = &start[sSharedCache->imagesCount]; | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| const bool cacheHasHashInfo = (start->modTime == 0); | |
| #endif | |
| for( const dyld_cache_image_info* p = start; p != end; ++p) { | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| // just check path | |
| const char* aPath = (char*)sSharedCache + p->pathFileOffset; | |
| if ( cacheHasHashInfo && (p->inode != hash) ) | |
| continue; | |
| if ( strcmp(path, aPath) == 0 ) { | |
| // found image in cache | |
| *mh = (macho_header*)(p->address+sSharedCacheSlide); | |
| *pathInCache = aPath; | |
| *slide = sSharedCacheSlide; | |
| if ( aPath < (char*)(*mh) ) { | |
| // <rdar://problem/22056997> found alias, rescan list to get canonical name | |
| for (const dyld_cache_image_info* p2 = start; p2 != end; ++p2) { | |
| if ( p2->address == p->address ) { | |
| *pathInCache = (char*)sSharedCache + p2->pathFileOffset; | |
| break; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| #elif __MAC_OS_X_VERSION_MIN_REQUIRED | |
| // check mtime and inode first because it is fast | |
| bool inodeMatch = ( ((time_t)p->modTime == stat_buf->st_mtime) && ((ino_t)p->inode == stat_buf->st_ino) ); | |
| if ( searchByPath || sSharedCacheIgnoreInodeAndTimeStamp || inodeMatch ) { | |
| // mod-time and inode match an image in the shared cache, now check path | |
| const char* aPath = (char*)sSharedCache + p->pathFileOffset; | |
| bool cacheHit = (strcmp(path, aPath) == 0); | |
| if ( inodeMatch && !cacheHit ) { | |
| // path does not match install name of dylib in cache, but inode and mtime does match | |
| // perhaps path is a symlink to the cached dylib | |
| struct stat pathInCacheStatBuf; | |
| if ( my_stat(aPath, &pathInCacheStatBuf) != -1 ) | |
| cacheHit = ( (pathInCacheStatBuf.st_dev == stat_buf->st_dev) && (pathInCacheStatBuf.st_ino == stat_buf->st_ino) ); | |
| } | |
| if ( cacheHit ) { | |
| // found image in cache, return info | |
| *mh = (macho_header*)(p->address+sSharedCacheSlide); | |
| //dyld::log("findInSharedCacheImage(), mh=%p, p->address=0x%0llX, slid=0x%0lX, path=%s\n", | |
| // *mh, p->address, sSharedCacheSlide, aPath); | |
| *pathInCache = aPath; | |
| *slide = sSharedCacheSlide; | |
| return true; | |
| } | |
| } | |
| #endif | |
| } | |
| } | |
| return false; | |
| } | |
| bool inSharedCache(const char* path) | |
| { | |
| const macho_header* mhInCache; | |
| const char* pathInCache; | |
| long slide; | |
| return findInSharedCacheImage(path, true, NULL, &mhInCache, &pathInCache, &slide); | |
| } | |
| #endif | |
| static ImageLoader* checkandAddImage(ImageLoader* image, const LoadContext& context) | |
| { | |
| // now sanity check that this loaded image does not have the same install path as any existing image | |
| const char* loadedImageInstallPath = image->getInstallPath(); | |
| if ( image->isDylib() && (loadedImageInstallPath != NULL) && (loadedImageInstallPath[0] == '/') ) { | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* anImage = *it; | |
| const char* installPath = anImage->getInstallPath(); | |
| if ( installPath != NULL) { | |
| if ( strcmp(loadedImageInstallPath, installPath) == 0 ) { | |
| //dyld::log("duplicate(%s) => %p\n", installPath, anImage); | |
| removeImage(image); | |
| ImageLoader::deleteImage(image); | |
| return anImage; | |
| } | |
| } | |
| } | |
| } | |
| // some API's restrict what they can load | |
| if ( context.mustBeBundle && !image->isBundle() ) | |
| throw "not a bundle"; | |
| if ( context.mustBeDylib && !image->isDylib() ) | |
| throw "not a dylib"; | |
| // regular main executables cannot be loaded | |
| if ( image->isExecutable() ) { | |
| if ( !context.canBePIE || !image->isPositionIndependentExecutable() ) | |
| throw "can't load a main executable"; | |
| } | |
| // don't add bundles to global list, they can be loaded but not linked. When linked it will be added to list | |
| if ( ! image->isBundle() ) | |
| addImage(image); | |
| return image; | |
| } | |
| #if TARGET_IPHONE_SIMULATOR | |
| static bool isSimulatorBinary(const uint8_t* firstPages, const char* path) | |
| { | |
| const macho_header* mh = (macho_header*)firstPages; | |
| const uint32_t cmd_count = mh->ncmds; | |
| const load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); | |
| const load_command* const cmdsEnd = (load_command*)((char*)cmds + mh->sizeofcmds); | |
| const struct load_command* cmd = cmds; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| switch (cmd->cmd) { | |
| #if TARGET_OS_WATCH | |
| case LC_VERSION_MIN_WATCHOS: | |
| return true; | |
| #elif TARGET_OS_TV | |
| case LC_VERSION_MIN_TVOS: | |
| return true; | |
| #elif TARGET_OS_IOS | |
| case LC_VERSION_MIN_IPHONEOS: | |
| return true; | |
| #endif | |
| case LC_VERSION_MIN_MACOSX: | |
| // grandfather in a few libSystem dylibs | |
| if ((strcmp(path, "/usr/lib/system/libsystem_kernel.dylib") == 0) || | |
| (strcmp(path, "/usr/lib/system/libsystem_platform.dylib") == 0) || | |
| (strcmp(path, "/usr/lib/system/libsystem_pthread.dylib") == 0)) | |
| return true; | |
| return false; | |
| } | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| if ( cmd > cmdsEnd ) | |
| return false; | |
| } | |
| return false; | |
| } | |
| #endif | |
| // map in file and instantiate an ImageLoader | |
| static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* path, const LoadContext& context) | |
| { | |
| //dyld::log("%s(%s)\n", __func__ , path); | |
| uint64_t fileOffset = 0; | |
| uint64_t fileLength = stat_buf.st_size; | |
| // validate it is a file (not directory) | |
| if ( (stat_buf.st_mode & S_IFMT) != S_IFREG ) | |
| throw "not a file"; | |
| uint8_t firstPages[MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE]; | |
| bool shortPage = false; | |
| // min mach-o file is 4K | |
| if ( fileLength < 4096 ) { | |
| if ( pread(fd, firstPages, fileLength, 0) != (ssize_t)fileLength ) | |
| throwf("pread of short file failed: %d", errno); | |
| shortPage = true; | |
| } | |
| else { | |
| // optimistically read only first 4KB | |
| if ( pread(fd, firstPages, 4096, 0) != 4096 ) | |
| throwf("pread of first 4K failed: %d", errno); | |
| } | |
| // if fat wrapper, find usable sub-file | |
| const fat_header* fileStartAsFat = (fat_header*)firstPages; | |
| if ( fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) { | |
| if ( OSSwapBigToHostInt32(fileStartAsFat->nfat_arch) > ((4096 - sizeof(fat_header)) / sizeof(fat_arch)) ) | |
| throwf("fat header too large: %u entries", OSSwapBigToHostInt32(fileStartAsFat->nfat_arch)); | |
| if ( fatFindBest(fileStartAsFat, &fileOffset, &fileLength) ) { | |
| if ( (fileOffset+fileLength) > (uint64_t)(stat_buf.st_size) ) | |
| throwf("truncated fat file. file length=%llu, but needed slice goes to %llu", stat_buf.st_size, fileOffset+fileLength); | |
| if (pread(fd, firstPages, 4096, fileOffset) != 4096) | |
| throwf("pread of fat file failed: %d", errno); | |
| } | |
| else { | |
| throw "no matching architecture in universal wrapper"; | |
| } | |
| } | |
| // try mach-o loader | |
| if ( shortPage ) | |
| throw "file too short"; | |
| if ( isCompatibleMachO(firstPages, path) ) { | |
| // only MH_BUNDLE, MH_DYLIB, and some MH_EXECUTE can be dynamically loaded | |
| const mach_header* mh = (mach_header*)firstPages; | |
| switch ( mh->filetype ) { | |
| case MH_EXECUTE: | |
| case MH_DYLIB: | |
| case MH_BUNDLE: | |
| break; | |
| default: | |
| throw "mach-o, but wrong filetype"; | |
| } | |
| uint32_t headerAndLoadCommandsSize = sizeof(macho_header) + mh->sizeofcmds; | |
| if ( headerAndLoadCommandsSize > MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE ) | |
| throwf("malformed mach-o: load commands size (%u) > %u", headerAndLoadCommandsSize, MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE); | |
| if ( headerAndLoadCommandsSize > fileLength ) | |
| dyld::throwf("malformed mach-o: load commands size (%u) > mach-o file size (%llu)", headerAndLoadCommandsSize, fileLength); | |
| if ( headerAndLoadCommandsSize > 4096 ) { | |
| // read more pages | |
| unsigned readAmount = headerAndLoadCommandsSize - 4096; | |
| if ( pread(fd, &firstPages[4096], readAmount, fileOffset+4096) != readAmount ) | |
| throwf("pread of extra load commands past 4KB failed: %d", errno); | |
| } | |
| #if TARGET_IPHONE_SIMULATOR | |
| // <rdar://problem/14168872> dyld_sim should restrict loading osx binaries | |
| if ( !isSimulatorBinary(firstPages, path) ) { | |
| #if TARGET_OS_WATCH | |
| throw "mach-o, but not built for watchOS simulator"; | |
| #elif TARGET_OS_TV | |
| throw "mach-o, but not built for tvOS simulator"; | |
| #else | |
| throw "mach-o, but not built for iOS simulator"; | |
| #endif | |
| } | |
| #endif | |
| // instantiate an image | |
| ImageLoader* image = ImageLoaderMachO::instantiateFromFile(path, fd, firstPages, headerAndLoadCommandsSize, fileOffset, fileLength, stat_buf, gLinkContext); | |
| // validate | |
| return checkandAddImage(image, context); | |
| } | |
| // try other file formats here... | |
| // throw error about what was found | |
| switch (*(uint32_t*)firstPages) { | |
| case MH_MAGIC: | |
| case MH_CIGAM: | |
| case MH_MAGIC_64: | |
| case MH_CIGAM_64: | |
| throw "mach-o, but wrong architecture"; | |
| default: | |
| throwf("unknown file type, first eight bytes: 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X", | |
| firstPages[0], firstPages[1], firstPages[2], firstPages[3], firstPages[4], firstPages[5], firstPages[6],firstPages[7]); | |
| } | |
| } | |
| static ImageLoader* loadPhase5open(const char* path, const LoadContext& context, const struct stat& stat_buf, std::vector<const char*>* exceptions) | |
| { | |
| //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); | |
| // open file (automagically closed when this function exits) | |
| FileOpener file(path); | |
| // just return NULL if file not found, but record any other errors | |
| if ( file.getFileDescriptor() == -1 ) { | |
| int err = errno; | |
| if ( err != ENOENT ) { | |
| const char* newMsg; | |
| if ( (err == EPERM) && sandboxBlockedOpen(path) ) | |
| newMsg = dyld::mkstringf("file system sandbox blocked open() of '%s'", path); | |
| else | |
| newMsg = dyld::mkstringf("%s: open() failed with errno=%d", path, err); | |
| exceptions->push_back(newMsg); | |
| } | |
| return NULL; | |
| } | |
| try { | |
| return loadPhase6(file.getFileDescriptor(), stat_buf, path, context); | |
| } | |
| catch (const char* msg) { | |
| const char* newMsg = dyld::mkstringf("%s: %s", path, msg); | |
| exceptions->push_back(newMsg); | |
| free((void*)msg); | |
| return NULL; | |
| } | |
| } | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector<const char*>* exceptions) | |
| { | |
| //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); | |
| ImageLoader* image = NULL; | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( sAllCacheImagesProxy != NULL ) { | |
| unsigned index; | |
| if ( sAllCacheImagesProxy->hasDylib(path, &index) ) | |
| return sAllCacheImagesProxy; | |
| } | |
| #endif | |
| // just return NULL if file not found, but record any other errors | |
| struct stat stat_buf; | |
| if ( my_stat(path, &stat_buf) == -1 ) { | |
| int err = errno; | |
| if ( err != ENOENT ) { | |
| if ( (err == EPERM) && sandboxBlockedStat(path) ) | |
| exceptions->push_back(dyld::mkstringf("%s: file system sandbox blocked stat()", path)); | |
| else | |
| exceptions->push_back(dyld::mkstringf("%s: stat() failed with errno=%d", path, err)); | |
| } | |
| return NULL; | |
| } | |
| // in case image was renamed or found via symlinks, check for inode match | |
| image = findLoadedImage(stat_buf); | |
| if ( image != NULL ) | |
| return image; | |
| // do nothing if not already loaded and if RTLD_NOLOAD or NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED | |
| if ( context.dontLoad ) | |
| return NULL; | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| // see if this image is in shared cache | |
| const macho_header* mhInCache; | |
| const char* pathInCache; | |
| long slideInCache; | |
| if ( findInSharedCacheImage(path, false, &stat_buf, &mhInCache, &pathInCache, &slideInCache) ) { | |
| image = ImageLoaderMachO::instantiateFromCache(mhInCache, pathInCache, slideInCache, stat_buf, gLinkContext); | |
| return checkandAddImage(image, context); | |
| } | |
| #endif | |
| // file exists and is not in dyld shared cache, so open it | |
| return loadPhase5open(path, context, stat_buf, exceptions); | |
| } | |
| #endif // __MAC_OS_X_VERSION_MIN_REQUIRED | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| static ImageLoader* loadPhase5stat(const char* path, const LoadContext& context, struct stat* stat_buf, | |
| int* statErrNo, bool* imageFound, std::vector<const char*>* exceptions) | |
| { | |
| ImageLoader* image = NULL; | |
| *imageFound = false; | |
| *statErrNo = 0; | |
| if ( my_stat(path, stat_buf) == 0 ) { | |
| // in case image was renamed or found via symlinks, check for inode match | |
| image = findLoadedImage(*stat_buf); | |
| if ( image != NULL ) { | |
| *imageFound = true; | |
| return image; | |
| } | |
| // do nothing if not already loaded and if RTLD_NOLOAD | |
| if ( context.dontLoad ) { | |
| *imageFound = true; | |
| return NULL; | |
| } | |
| image = loadPhase5open(path, context, *stat_buf, exceptions); | |
| if ( image != NULL ) { | |
| *imageFound = true; | |
| return image; | |
| } | |
| } | |
| else { | |
| *statErrNo = errno; | |
| } | |
| return NULL; | |
| } | |
| // try to open file | |
| static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector<const char*>* exceptions) | |
| { | |
| //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); | |
| struct stat stat_buf; | |
| bool imageFound; | |
| int statErrNo; | |
| ImageLoader* image; | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( sAllCacheImagesProxy != NULL ) { | |
| if ( sAllCacheImagesProxy->hasDylib(path, &cacheIndex) ) | |
| return sAllCacheImagesProxy; | |
| } | |
| #endif | |
| if ( dylibsCanOverrideCache() ) { | |
| // flag is set that allows installed framework roots to override dyld shared cache | |
| image = loadPhase5stat(path, context, &stat_buf, &statErrNo, &imageFound, exceptions); | |
| if ( imageFound ) | |
| return image; | |
| } | |
| // see if this image is in shared cache | |
| const macho_header* mhInCache; | |
| const char* pathInCache; | |
| long slideInCache; | |
| if ( findInSharedCacheImage(path, true, NULL, &mhInCache, &pathInCache, &slideInCache) ) { | |
| // see if this image in the cache was already loaded via a different path | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); ++it) { | |
| ImageLoader* anImage = *it; | |
| if ( (const macho_header*)anImage->machHeader() == mhInCache ) | |
| return anImage; | |
| } | |
| // do nothing if not already loaded and if RTLD_NOLOAD | |
| if ( context.dontLoad ) | |
| return NULL; | |
| // nope, so instantiate a new image from dyld shared cache | |
| // <rdar://problem/7014995> zero out stat buffer so mtime, etc are zero for items from the shared cache | |
| bzero(&stat_buf, sizeof(stat_buf)); | |
| image = ImageLoaderMachO::instantiateFromCache(mhInCache, pathInCache, slideInCache, stat_buf, gLinkContext); | |
| return checkandAddImage(image, context); | |
| } | |
| if ( !dylibsCanOverrideCache() ) { | |
| // flag is not set, and not in cache to try opening it | |
| image = loadPhase5stat(path, context, &stat_buf, &statErrNo, &imageFound, exceptions); | |
| if ( imageFound ) | |
| return image; | |
| } | |
| #else | |
| image = loadPhase5stat(path, context, &stat_buf, &statErrNo, &imageFound, exceptions); | |
| if ( imageFound ) | |
| return image; | |
| #endif | |
| // just return NULL if file not found, but record any other errors | |
| if ( (statErrNo != ENOENT) && (statErrNo != 0) ) { | |
| if ( (statErrNo == EPERM) && sandboxBlockedStat(path) ) | |
| exceptions->push_back(dyld::mkstringf("%s: file system sandbox blocked stat()", path)); | |
| else | |
| exceptions->push_back(dyld::mkstringf("%s: stat() failed with errno=%d", path, statErrNo)); | |
| } | |
| return NULL; | |
| } | |
| #endif // __IPHONE_OS_VERSION_MIN_REQUIRED | |
| // look for path match with existing loaded images | |
| static ImageLoader* loadPhase5check(const char* path, const char* orgPath, const LoadContext& context) | |
| { | |
| //dyld::log("%s(%s, %s)\n", __func__ , path, orgPath); | |
| // search path against load-path and install-path of all already loaded images | |
| uint32_t hash = ImageLoader::hash(path); | |
| //dyld::log("check() hash=%d, path=%s\n", hash, path); | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* anImage = *it; | |
| // check hash first to cut down on strcmp calls | |
| //dyld::log(" check() hash=%d, path=%s\n", anImage->getPathHash(), anImage->getPath()); | |
| if ( anImage->getPathHash() == hash ) { | |
| if ( strcmp(path, anImage->getPath()) == 0 ) { | |
| // if we are looking for a dylib don't return something else | |
| if ( !context.mustBeDylib || anImage->isDylib() ) | |
| return anImage; | |
| } | |
| } | |
| if ( context.matchByInstallName || anImage->matchInstallPath() ) { | |
| const char* installPath = anImage->getInstallPath(); | |
| if ( installPath != NULL) { | |
| if ( strcmp(path, installPath) == 0 ) { | |
| // if we are looking for a dylib don't return something else | |
| if ( !context.mustBeDylib || anImage->isDylib() ) | |
| return anImage; | |
| } | |
| } | |
| } | |
| // an install name starting with @rpath should match by install name, not just real path | |
| if ( (orgPath[0] == '@') && (strncmp(orgPath, "@rpath/", 7) == 0) ) { | |
| const char* installPath = anImage->getInstallPath(); | |
| if ( installPath != NULL) { | |
| if ( !context.mustBeDylib || anImage->isDylib() ) { | |
| if ( strcmp(orgPath, installPath) == 0 ) | |
| return anImage; | |
| } | |
| } | |
| } | |
| } | |
| //dyld::log("%s(%s) => NULL\n", __func__, path); | |
| return NULL; | |
| } | |
| // open or check existing | |
| static ImageLoader* loadPhase5(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector<const char*>* exceptions) | |
| { | |
| //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); | |
| // check for specific dylib overrides | |
| for (std::vector<DylibOverride>::iterator it = sDylibOverrides.begin(); it != sDylibOverrides.end(); ++it) { | |
| if ( strcmp(it->installName, path) == 0 ) { | |
| path = it->override; | |
| break; | |
| } | |
| } | |
| if ( exceptions != NULL ) | |
| return loadPhase5load(path, orgPath, context, cacheIndex, exceptions); | |
| else | |
| return loadPhase5check(path, orgPath, context); | |
| } | |
| // try with and without image suffix | |
| static ImageLoader* loadPhase4(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector<const char*>* exceptions) | |
| { | |
| //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); | |
| ImageLoader* image = NULL; | |
| if ( gLinkContext.imageSuffix != NULL ) { | |
| char pathWithSuffix[strlen(path)+strlen( gLinkContext.imageSuffix)+2]; | |
| ImageLoader::addSuffix(path, gLinkContext.imageSuffix, pathWithSuffix); | |
| image = loadPhase5(pathWithSuffix, orgPath, context, cacheIndex, exceptions); | |
| } | |
| if ( image == NULL ) | |
| image = loadPhase5(path, orgPath, context, cacheIndex, exceptions); | |
| return image; | |
| } | |
| static ImageLoader* loadPhase2(const char* path, const char* orgPath, const LoadContext& context, | |
| const char* const frameworkPaths[], const char* const libraryPaths[], | |
| unsigned& cacheIndex, std::vector<const char*>* exceptions); // forward reference | |
| // expand @ variables | |
| static ImageLoader* loadPhase3(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector<const char*>* exceptions) | |
| { | |
| //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); | |
| ImageLoader* image = NULL; | |
| if ( strncmp(path, "@executable_path/", 17) == 0 ) { | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| // executable_path cannot be in used in any binary in a setuid process rdar://problem/4589305 | |
| if ( gLinkContext.processIsRestricted ) | |
| throwf("unsafe use of @executable_path in %s with restricted binary", context.origin); | |
| #endif | |
| // handle @executable_path path prefix | |
| const char* executablePath = sExecPath; | |
| char newPath[strlen(executablePath) + strlen(path)]; | |
| strcpy(newPath, executablePath); | |
| char* addPoint = strrchr(newPath,'/'); | |
| if ( addPoint != NULL ) | |
| strcpy(&addPoint[1], &path[17]); | |
| else | |
| strcpy(newPath, &path[17]); | |
| image = loadPhase4(newPath, orgPath, context, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| // perhaps main executable path is a sym link, find realpath and retry | |
| char resolvedPath[PATH_MAX]; | |
| if ( realpath(sExecPath, resolvedPath) != NULL ) { | |
| char newRealPath[strlen(resolvedPath) + strlen(path)]; | |
| strcpy(newRealPath, resolvedPath); | |
| addPoint = strrchr(newRealPath,'/'); | |
| if ( addPoint != NULL ) | |
| strcpy(&addPoint[1], &path[17]); | |
| else | |
| strcpy(newRealPath, &path[17]); | |
| image = loadPhase4(newRealPath, orgPath, context, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| } | |
| } | |
| else if ( (strncmp(path, "@loader_path/", 13) == 0) && (context.origin != NULL) ) { | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| // @loader_path cannot be used from the main executable of a setuid process rdar://problem/4589305 | |
| if ( gLinkContext.processIsRestricted && (strcmp(context.origin, sExecPath) == 0) ) | |
| throwf("unsafe use of @loader_path in %s with restricted binary", context.origin); | |
| #endif | |
| // handle @loader_path path prefix | |
| char newPath[strlen(context.origin) + strlen(path)]; | |
| strcpy(newPath, context.origin); | |
| char* addPoint = strrchr(newPath,'/'); | |
| if ( addPoint != NULL ) | |
| strcpy(&addPoint[1], &path[13]); | |
| else | |
| strcpy(newPath, &path[13]); | |
| image = loadPhase4(newPath, orgPath, context, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| // perhaps loader path is a sym link, find realpath and retry | |
| char resolvedPath[PATH_MAX]; | |
| if ( realpath(context.origin, resolvedPath) != NULL ) { | |
| char newRealPath[strlen(resolvedPath) + strlen(path)]; | |
| strcpy(newRealPath, resolvedPath); | |
| addPoint = strrchr(newRealPath,'/'); | |
| if ( addPoint != NULL ) | |
| strcpy(&addPoint[1], &path[13]); | |
| else | |
| strcpy(newRealPath, &path[13]); | |
| image = loadPhase4(newRealPath, orgPath, context, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| } | |
| } | |
| else if ( context.implicitRPath || (strncmp(path, "@rpath/", 7) == 0) ) { | |
| const char* trailingPath = (strncmp(path, "@rpath/", 7) == 0) ? &path[7] : path; | |
| // substitute @rpath with all -rpath paths up the load chain | |
| for(const ImageLoader::RPathChain* rp=context.rpath; rp != NULL; rp=rp->next) { | |
| if (rp->paths != NULL ) { | |
| for(std::vector<const char*>::iterator it=rp->paths->begin(); it != rp->paths->end(); ++it) { | |
| const char* anRPath = *it; | |
| char newPath[strlen(anRPath) + strlen(trailingPath)+2]; | |
| strcpy(newPath, anRPath); | |
| if ( newPath[strlen(newPath)-1] != '/' ) | |
| strcat(newPath, "/"); | |
| strcat(newPath, trailingPath); | |
| image = loadPhase4(newPath, orgPath, context, cacheIndex, exceptions); | |
| if ( gLinkContext.verboseRPaths && (exceptions != NULL) ) { | |
| if ( image != NULL ) | |
| dyld::log("RPATH successful expansion of %s to: %s\n", orgPath, newPath); | |
| else | |
| dyld::log("RPATH failed to expanding %s to: %s\n", orgPath, newPath); | |
| } | |
| if ( image != NULL ) | |
| return image; | |
| } | |
| } | |
| } | |
| // substitute @rpath with LD_LIBRARY_PATH | |
| if ( sEnv.LD_LIBRARY_PATH != NULL ) { | |
| image = loadPhase2(trailingPath, orgPath, context, NULL, sEnv.LD_LIBRARY_PATH, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| } | |
| // if this is the "open" pass, don't try to open @rpath/... as a relative path | |
| if ( (exceptions != NULL) && (trailingPath != path) ) | |
| return NULL; | |
| } | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| else if ( gLinkContext.processIsRestricted && (path[0] != '/' ) ) { | |
| throwf("unsafe use of relative rpath %s in %s with restricted binary", path, context.origin); | |
| } | |
| #endif | |
| return loadPhase4(path, orgPath, context, cacheIndex, exceptions); | |
| } | |
| // try search paths | |
| static ImageLoader* loadPhase2(const char* path, const char* orgPath, const LoadContext& context, | |
| const char* const frameworkPaths[], const char* const libraryPaths[], | |
| unsigned& cacheIndex, std::vector<const char*>* exceptions) | |
| { | |
| //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); | |
| ImageLoader* image = NULL; | |
| const char* frameworkPartialPath = getFrameworkPartialPath(path); | |
| if ( frameworkPaths != NULL ) { | |
| if ( frameworkPartialPath != NULL ) { | |
| const size_t frameworkPartialPathLen = strlen(frameworkPartialPath); | |
| for(const char* const* fp = frameworkPaths; *fp != NULL; ++fp) { | |
| char npath[strlen(*fp)+frameworkPartialPathLen+8]; | |
| strcpy(npath, *fp); | |
| strcat(npath, "/"); | |
| strcat(npath, frameworkPartialPath); | |
| //dyld::log("dyld: fallback framework path used: %s() -> loadPhase4(\"%s\", ...)\n", __func__, npath); | |
| image = loadPhase4(npath, orgPath, context, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| } | |
| } | |
| } | |
| // <rdar://problem/12649639> An executable with the same name as a framework & DYLD_LIBRARY_PATH pointing to it gets loaded twice | |
| // <rdar://problem/14160846> Some apps depend on frameworks being found via library paths | |
| if ( (libraryPaths != NULL) && ((frameworkPartialPath == NULL) || sFrameworksFoundAsDylibs) ) { | |
| const char* libraryLeafName = getLibraryLeafName(path); | |
| const size_t libraryLeafNameLen = strlen(libraryLeafName); | |
| for(const char* const* lp = libraryPaths; *lp != NULL; ++lp) { | |
| char libpath[strlen(*lp)+libraryLeafNameLen+8]; | |
| strcpy(libpath, *lp); | |
| strcat(libpath, "/"); | |
| strcat(libpath, libraryLeafName); | |
| //dyld::log("dyld: fallback library path used: %s() -> loadPhase4(\"%s\", ...)\n", __func__, libpath); | |
| image = loadPhase4(libpath, orgPath, context, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| } | |
| } | |
| return NULL; | |
| } | |
| // try search overrides and fallbacks | |
| static ImageLoader* loadPhase1(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector<const char*>* exceptions) | |
| { | |
| //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); | |
| ImageLoader* image = NULL; | |
| // handle LD_LIBRARY_PATH environment variables that force searching | |
| if ( context.useLdLibraryPath && (sEnv.LD_LIBRARY_PATH != NULL) ) { | |
| image = loadPhase2(path, orgPath, context, NULL, sEnv.LD_LIBRARY_PATH, cacheIndex,exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| } | |
| // handle DYLD_ environment variables that force searching | |
| if ( context.useSearchPaths && ((sEnv.DYLD_FRAMEWORK_PATH != NULL) || (sEnv.DYLD_LIBRARY_PATH != NULL)) ) { | |
| image = loadPhase2(path, orgPath, context, sEnv.DYLD_FRAMEWORK_PATH, sEnv.DYLD_LIBRARY_PATH, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| } | |
| // try raw path | |
| image = loadPhase3(path, orgPath, context, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| // try fallback paths during second time (will open file) | |
| const char* const* fallbackLibraryPaths = sEnv.DYLD_FALLBACK_LIBRARY_PATH; | |
| if ( (fallbackLibraryPaths != NULL) && !context.useFallbackPaths ) | |
| fallbackLibraryPaths = NULL; | |
| if ( !context.dontLoad && (exceptions != NULL) && ((sEnv.DYLD_FALLBACK_FRAMEWORK_PATH != NULL) || (fallbackLibraryPaths != NULL)) ) { | |
| image = loadPhase2(path, orgPath, context, sEnv.DYLD_FALLBACK_FRAMEWORK_PATH, fallbackLibraryPaths, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| } | |
| return NULL; | |
| } | |
| // try root substitutions | |
| static ImageLoader* loadPhase0(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector<const char*>* exceptions) | |
| { | |
| //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); | |
| #if SUPPORT_ROOT_PATH | |
| // handle DYLD_ROOT_PATH which forces absolute paths to use a new root | |
| if ( (gLinkContext.rootPaths != NULL) && (path[0] == '/') ) { | |
| for(const char* const* rootPath = gLinkContext.rootPaths ; *rootPath != NULL; ++rootPath) { | |
| char newPath[strlen(*rootPath) + strlen(path)+2]; | |
| strcpy(newPath, *rootPath); | |
| strcat(newPath, path); | |
| ImageLoader* image = loadPhase1(newPath, orgPath, context, cacheIndex, exceptions); | |
| if ( image != NULL ) | |
| return image; | |
| } | |
| } | |
| #endif | |
| // try raw path | |
| return loadPhase1(path, orgPath, context, cacheIndex, exceptions); | |
| } | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| static bool cacheablePath(const char* path) { | |
| if (strncmp(path, "/usr/lib/", 9) == 0) | |
| return true; | |
| if (strncmp(path, "/System/Library/", 16) == 0) | |
| return true; | |
| return false; | |
| } | |
| #endif | |
| // | |
| // Given all the DYLD_ environment variables, the general case for loading libraries | |
| // is that any given path expands into a list of possible locations to load. We | |
| // also must take care to ensure two copies of the "same" library are never loaded. | |
| // | |
| // The algorithm used here is that there is a separate function for each "phase" of the | |
| // path expansion. Each phase function calls the next phase with each possible expansion | |
| // of that phase. The result is the last phase is called with all possible paths. | |
| // | |
| // To catch duplicates the algorithm is run twice. The first time, the last phase checks | |
| // the path against all loaded images. The second time, the last phase calls open() on | |
| // the path. Either time, if an image is found, the phases all unwind without checking | |
| // for other paths. | |
| // | |
| ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex) | |
| { | |
| CRSetCrashLogMessage2(path); | |
| const char* orgPath = path; | |
| cacheIndex = UINT32_MAX; | |
| //dyld::log("%s(%s)\n", __func__ , path); | |
| char realPath[PATH_MAX]; | |
| // when DYLD_IMAGE_SUFFIX is in used, do a realpath(), otherwise a load of "Foo.framework/Foo" will not match | |
| if ( context.useSearchPaths && ( gLinkContext.imageSuffix != NULL) ) { | |
| if ( realpath(path, realPath) != NULL ) | |
| path = realPath; | |
| } | |
| // try all path permutations and check against existing loaded images | |
| ImageLoader* image = loadPhase0(path, orgPath, context, cacheIndex, NULL); | |
| if ( image != NULL ) { | |
| CRSetCrashLogMessage2(NULL); | |
| return image; | |
| } | |
| // try all path permutations and try open() until first success | |
| std::vector<const char*> exceptions; | |
| image = loadPhase0(path, orgPath, context, cacheIndex, &exceptions); | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED && DYLD_SHARED_CACHE_SUPPORT && !TARGET_IPHONE_SIMULATOR | |
| // <rdar://problem/16704628> support symlinks on disk to a path in dyld shared cache | |
| if ( (image == NULL) && cacheablePath(path) && !context.dontLoad ) { | |
| char resolvedPath[PATH_MAX]; | |
| realpath(path, resolvedPath); | |
| int myerr = errno; | |
| // If realpath() resolves to a path which does not exist on disk, errno is set to ENOENT | |
| if ( (myerr == ENOENT) || (myerr == 0) ) | |
| { | |
| // see if this image is in shared cache | |
| const macho_header* mhInCache; | |
| const char* pathInCache; | |
| long slideInCache; | |
| if ( findInSharedCacheImage(resolvedPath, false, NULL, &mhInCache, &pathInCache, &slideInCache) ) { | |
| struct stat stat_buf; | |
| bzero(&stat_buf, sizeof(stat_buf)); | |
| try { | |
| image = ImageLoaderMachO::instantiateFromCache(mhInCache, pathInCache, slideInCache, stat_buf, gLinkContext); | |
| image = checkandAddImage(image, context); | |
| } | |
| catch (...) { | |
| image = NULL; | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| CRSetCrashLogMessage2(NULL); | |
| if ( image != NULL ) { | |
| // <rdar://problem/6916014> leak in dyld during dlopen when using DYLD_ variables | |
| for (std::vector<const char*>::iterator it = exceptions.begin(); it != exceptions.end(); ++it) { | |
| free((void*)(*it)); | |
| } | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| // if loaded image is not from cache, but original path is in cache | |
| // set gSharedCacheOverridden flag to disable some ObjC optimizations | |
| if ( !gSharedCacheOverridden && !image->inSharedCache() && image->isDylib() && cacheablePath(path) && inSharedCache(path) ) { | |
| gSharedCacheOverridden = true; | |
| } | |
| #endif | |
| return image; | |
| } | |
| else if ( exceptions.size() == 0 ) { | |
| if ( context.dontLoad ) { | |
| return NULL; | |
| } | |
| else | |
| throw "image not found"; | |
| } | |
| else { | |
| const char* msgStart = "no suitable image found. Did find:"; | |
| const char* delim = "\n\t"; | |
| size_t allsizes = strlen(msgStart)+8; | |
| for (size_t i=0; i < exceptions.size(); ++i) | |
| allsizes += (strlen(exceptions[i]) + strlen(delim)); | |
| char* fullMsg = new char[allsizes]; | |
| strcpy(fullMsg, msgStart); | |
| for (size_t i=0; i < exceptions.size(); ++i) { | |
| strcat(fullMsg, delim); | |
| strcat(fullMsg, exceptions[i]); | |
| free((void*)exceptions[i]); | |
| } | |
| throw (const char*)fullMsg; | |
| } | |
| } | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| #if __i386__ | |
| #define ARCH_NAME "i386" | |
| #define ARCH_CACHE_MAGIC "dyld_v1 i386" | |
| #elif __x86_64__ | |
| #define ARCH_NAME "x86_64" | |
| #define ARCH_CACHE_MAGIC "dyld_v1 x86_64" | |
| #define ARCH_NAME_H "x86_64h" | |
| #define ARCH_CACHE_MAGIC_H "dyld_v1 x86_64h" | |
| #elif __ARM_ARCH_5TEJ__ | |
| #define ARCH_NAME "armv5" | |
| #define ARCH_CACHE_MAGIC "dyld_v1 armv5" | |
| #elif __ARM_ARCH_6K__ | |
| #define ARCH_NAME "armv6" | |
| #define ARCH_CACHE_MAGIC "dyld_v1 armv6" | |
| #elif __ARM_ARCH_7F__ | |
| #define ARCH_NAME "armv7f" | |
| #define ARCH_CACHE_MAGIC "dyld_v1 armv7f" | |
| #elif __ARM_ARCH_7K__ | |
| #define ARCH_NAME "armv7k" | |
| #define ARCH_CACHE_MAGIC "dyld_v1 armv7k" | |
| #elif __ARM_ARCH_7A__ | |
| #define ARCH_NAME "armv7" | |
| #define ARCH_CACHE_MAGIC "dyld_v1 armv7" | |
| #elif __ARM_ARCH_7S__ | |
| #define ARCH_NAME "armv7s" | |
| #define ARCH_CACHE_MAGIC "dyld_v1 armv7s" | |
| #elif __arm64__ | |
| #define ARCH_NAME "arm64" | |
| #define ARCH_CACHE_MAGIC "dyld_v1 arm64" | |
| #endif | |
| static int __attribute__((noinline)) _shared_region_check_np(uint64_t* start_address) | |
| { | |
| if ( gLinkContext.sharedRegionMode == ImageLoader::kUseSharedRegion ) | |
| return syscall(294, start_address); | |
| return -1; | |
| } | |
| static void rebaseChain(uint8_t* pageContent, uint16_t startOffset, uintptr_t slideAmount, const dyld_cache_slide_info2* slideInfo) | |
| { | |
| const uintptr_t deltaMask = (uintptr_t)(slideInfo->delta_mask); | |
| const uintptr_t valueMask = ~deltaMask; | |
| const uintptr_t valueAdd = (uintptr_t)(slideInfo->value_add); | |
| const unsigned deltaShift = __builtin_ctzll(deltaMask) - 2; | |
| uint32_t pageOffset = startOffset; | |
| uint32_t delta = 1; | |
| while ( delta != 0 ) { | |
| uint8_t* loc = pageContent + pageOffset; | |
| uintptr_t rawValue = *((uintptr_t*)loc); | |
| delta = (uint32_t)((rawValue & deltaMask) >> deltaShift); | |
| uintptr_t value = (rawValue & valueMask); | |
| if ( value != 0 ) { | |
| value += valueAdd; | |
| value += slideAmount; | |
| } | |
| *((uintptr_t*)loc) = value; | |
| //dyld::log(" pageOffset=0x%03X, loc=%p, org value=0x%08llX, new value=0x%08llX, delta=0x%X\n", pageOffset, loc, (uint64_t)rawValue, (uint64_t)value, delta); | |
| pageOffset += delta; | |
| } | |
| } | |
| static void loadAndCheckCodeSignature(int fd, uint32_t count, const shared_file_mapping_np mappings[], | |
| off_t codeSignatureOffset, size_t codeSignatureSize, | |
| const void *firstPages, size_t firstPagesSize) | |
| { | |
| // register code signature blob for whole dyld cache | |
| fsignatures_t siginfo; | |
| siginfo.fs_file_start = 0; // cache always starts at beginning of file | |
| siginfo.fs_blob_start = (void*)codeSignatureOffset; | |
| siginfo.fs_blob_size = codeSignatureSize; | |
| int result = fcntl(fd, F_ADDFILESIGS_RETURN, &siginfo); | |
| // <rdar://problem/12891874> don't warn in chrooted case because mapping syscall is about to fail too | |
| if ( result == -1 ) { | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| throwf("code signature registration for shared cache failed with errno=%d\n", errno); | |
| #else | |
| if ( gLinkContext.verboseMapping ) | |
| dyld::log("dyld: code signature registration for shared cache failed with errno=%d\n", errno); | |
| #endif | |
| } | |
| uint64_t codeSignedLength = siginfo.fs_file_start; | |
| for (uint32_t i = 0; i < count; ++i) { | |
| if ( (mappings[i].sfm_size > codeSignedLength) || (mappings[i].sfm_file_offset > (codeSignedLength - mappings[i].sfm_size)) ) | |
| throw "dyld shared cache mapping not covered by code signature"; | |
| } | |
| void *fdata = xmmap(NULL, firstPagesSize, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0); | |
| if ( fdata == MAP_FAILED ) | |
| throwf("mmap() errno=%d validating first page of shared cache", errno); | |
| if ( memcmp(fdata, firstPages, firstPagesSize) != 0 ) | |
| throwf("mmap() page compare failed for shared cache"); | |
| munmap(fdata, firstPagesSize); | |
| } | |
| static int __attribute__((noinline)) _shared_region_map_and_slide_np(int fd, uint32_t count, const shared_file_mapping_np mappings[], | |
| long slide, void* slideInfo, unsigned long slideInfoSize) | |
| { | |
| if ( gLinkContext.sharedRegionMode == ImageLoader::kUseSharedRegion ) { | |
| return syscall(438, fd, count, mappings, slide, slideInfo, slideInfoSize); | |
| } | |
| // remove the shared region sub-map | |
| vm_deallocate(mach_task_self(), (vm_address_t)SHARED_REGION_BASE, SHARED_REGION_SIZE); | |
| // notify gdb or other lurkers that this process is no longer using the shared region | |
| dyld::gProcessInfo->processDetachedFromSharedRegion = true; | |
| // map cache just for this process with mmap() | |
| const shared_file_mapping_np* const start = mappings; | |
| const shared_file_mapping_np* const end = &mappings[count]; | |
| for (const shared_file_mapping_np* p = start; p < end; ++p ) { | |
| void* mmapAddress = (void*)(uintptr_t)(p->sfm_address); | |
| size_t size = p->sfm_size; | |
| //dyld::log("dyld: mapping address %p with size 0x%08lX\n", mmapAddress, size); | |
| int protection = 0; | |
| if ( p->sfm_init_prot & VM_PROT_EXECUTE ) | |
| protection |= PROT_EXEC; | |
| if ( p->sfm_init_prot & VM_PROT_READ ) | |
| protection |= PROT_READ; | |
| if ( p->sfm_init_prot & VM_PROT_WRITE ) | |
| protection |= PROT_WRITE; | |
| off_t offset = p->sfm_file_offset; | |
| if ( mmap(mmapAddress, size, protection, MAP_FIXED | MAP_PRIVATE, fd, offset) != mmapAddress ) { | |
| // failed to map some chunk of this shared cache file | |
| // clear shared region | |
| vm_deallocate(mach_task_self(), (vm_address_t)SHARED_REGION_BASE, SHARED_REGION_SIZE); | |
| // go back to not using shared region at all | |
| gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; | |
| if ( gLinkContext.verboseMapping ) { | |
| dyld::log("dyld: shared cached region cannot be mapped at address %p with size 0x%08lX\n", | |
| mmapAddress, size); | |
| } | |
| // return failure | |
| return -1; | |
| } | |
| } | |
| // update all __DATA pages with slide info | |
| const dyld_cache_slide_info* slideInfoHeader = (dyld_cache_slide_info*)slideInfo; | |
| if ( slideInfoHeader->version == 2 ) { | |
| const dyld_cache_slide_info2* slideHeader = (dyld_cache_slide_info2*)slideInfo; | |
| const uint32_t page_size = slideHeader->page_size; | |
| const uint16_t* page_starts = (uint16_t*)((long)(slideInfo) + slideHeader->page_starts_offset); | |
| const uint16_t* page_extras = (uint16_t*)((long)(slideInfo) + slideHeader->page_extras_offset); | |
| const uintptr_t dataPagesStart = mappings[1].sfm_address; | |
| for (int i=0; i < slideHeader->page_starts_count; ++i) { | |
| uint8_t* page = (uint8_t*)(long)(dataPagesStart + (page_size*i)); | |
| uint16_t pageEntry = page_starts[i]; | |
| //dyld::log("page[%d]: page_starts[i]=0x%04X\n", i, pageEntry); | |
| if ( pageEntry == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE ) | |
| continue; | |
| if ( pageEntry & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA ) { | |
| uint16_t chainIndex = (pageEntry & 0x3FFF); | |
| bool done = false; | |
| while ( !done ) { | |
| uint16_t info = page_extras[chainIndex]; | |
| uint16_t pageStartOffset = (info & 0x3FFF)*4; | |
| //dyld::log(" chain[%d] pageOffset=0x%03X\n", chainIndex, pageStartOffset); | |
| rebaseChain(page, pageStartOffset, slide, slideHeader); | |
| done = (info & DYLD_CACHE_SLIDE_PAGE_ATTR_END); | |
| ++chainIndex; | |
| } | |
| } | |
| else { | |
| uint32_t pageOffset = pageEntry * 4; | |
| //dyld::log(" start pageOffset=0x%03X\n", pageOffset); | |
| rebaseChain(page, pageOffset, slide, slideHeader); | |
| } | |
| } | |
| } | |
| else if ( slide != 0 ) { | |
| const uintptr_t dataPagesStart = mappings[1].sfm_address; | |
| const uint16_t* toc = (uint16_t*)((long)(slideInfoHeader) + slideInfoHeader->toc_offset); | |
| const uint8_t* entries = (uint8_t*)((long)(slideInfoHeader) + slideInfoHeader->entries_offset); | |
| for(uint32_t i=0; i < slideInfoHeader->toc_count; ++i) { | |
| const uint8_t* entry = &entries[toc[i]*slideInfoHeader->entries_size]; | |
| const uint8_t* page = (uint8_t*)(long)(dataPagesStart + (4096*i)); | |
| //dyld::log("page=%p toc[%d]=%d entries=%p\n", page, i, toc[i], entry); | |
| for(int j=0; j < 128; ++j) { | |
| uint8_t b = entry[j]; | |
| //dyld::log(" entry[%d] = 0x%02X\n", j, b); | |
| if ( b != 0 ) { | |
| for(int k=0; k < 8; ++k) { | |
| if ( b & (1<<k) ) { | |
| uintptr_t* p = (uintptr_t*)(page + j*8*4 + k*4); | |
| uintptr_t value = *p; | |
| //dyld::log(" *%p was 0x%lX will be 0x%lX\n", p, value, value+sSharedCacheSlide); | |
| *p = value + slide; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // succesfully mapped shared cache for just this process | |
| gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion; | |
| return 0; | |
| } | |
| const void* imMemorySharedCacheHeader() | |
| { | |
| return sSharedCache; | |
| } | |
| const char* getStandardSharedCacheFilePath() | |
| { | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME; | |
| #else | |
| #if __x86_64__ | |
| if ( sHaswell ) { | |
| const char* path2 = MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME_H; | |
| struct stat statBuf; | |
| if ( my_stat(path2, &statBuf) == 0 ) | |
| return path2; | |
| } | |
| #endif | |
| return MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME; | |
| #endif | |
| } | |
| int openSharedCacheFile() | |
| { | |
| char path[MAXPATHLEN]; | |
| strlcpy(path, sSharedCacheDir, MAXPATHLEN); | |
| strlcat(path, "/", MAXPATHLEN); | |
| #if __x86_64__ | |
| if ( sHaswell ) { | |
| strlcat(path, DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME_H, MAXPATHLEN); | |
| int fd = my_open(path, O_RDONLY, 0); | |
| if ( fd != -1 ) { | |
| if ( gLinkContext.verboseMapping ) | |
| dyld::log("dyld: Mapping%s shared cache from %s\n", (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion) ? " private": "", path); | |
| return fd; | |
| } | |
| strlcpy(path, sSharedCacheDir, MAXPATHLEN); | |
| } | |
| #endif | |
| strlcat(path, DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, MAXPATHLEN); | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| struct stat enableStatBuf; | |
| struct stat devCacheStatBuf; | |
| struct stat prodCacheStatBuf; | |
| if ( ((my_stat(IPHONE_DYLD_SHARED_CACHE_DIR "enable-dylibs-to-override-cache", &enableStatBuf) == 0) | |
| && (enableStatBuf.st_size < ENABLE_DYLIBS_TO_OVERRIDE_CACHE_SIZE) | |
| && (my_stat(IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME DYLD_SHARED_CACHE_DEVELOPMENT_EXT, &devCacheStatBuf) == 0)) | |
| || (my_stat(IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, &prodCacheStatBuf) != 0)) | |
| strlcat(path, DYLD_SHARED_CACHE_DEVELOPMENT_EXT, MAXPATHLEN); | |
| #endif | |
| if ( gLinkContext.verboseMapping ) | |
| dyld::log("dyld: Mapping%s shared cache from %s\n", (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion) ? " private": "", path); | |
| return my_open(path, O_RDONLY, 0); | |
| } | |
| static void getCacheBounds(uint32_t mappingsCount, const shared_file_mapping_np mappings[], uint64_t& lowAddress, uint64_t& highAddress) | |
| { | |
| lowAddress = 0; | |
| highAddress = 0; | |
| for(uint32_t i=0; i < mappingsCount; ++i) { | |
| if ( lowAddress == 0 ) { | |
| lowAddress = mappings[i].sfm_address; | |
| highAddress = mappings[i].sfm_address + mappings[i].sfm_size; | |
| } | |
| else { | |
| if ( mappings[i].sfm_address < lowAddress ) | |
| lowAddress = mappings[i].sfm_address; | |
| if ( (mappings[i].sfm_address + mappings[i].sfm_size) > highAddress ) | |
| highAddress = mappings[i].sfm_address + mappings[i].sfm_size; | |
| } | |
| } | |
| } | |
| static long pickCacheSlide(uint32_t mappingsCount, shared_file_mapping_np mappings[]) | |
| { | |
| #if __x86_64__ | |
| // x86_64 has a two memory regions: | |
| // 256MB at 0x00007FFF70000000 | |
| // 1024MB at 0x00007FFF80000000 | |
| // Some old shared caches have r/w region after rx region, so all regions slide within 1GB range | |
| // Newer shared caches have r/w region based at 0x7FFF70000000 and r/o regions at 0x7FFF80000000, so each part has max slide | |
| if ( (mappingsCount >= 3) && (mappings[1].sfm_init_prot == (VM_PROT_READ|VM_PROT_WRITE)) && (mappings[1].sfm_address == 0x00007FFF70000000) ) { | |
| const uint64_t rwSize = mappings[1].sfm_size; | |
| const uint64_t rwSlop = 0x10000000ULL - rwSize; | |
| const uint64_t roSize = (mappings[2].sfm_address + mappings[2].sfm_size) - mappings[0].sfm_address; | |
| const uint64_t roSlop = 0x40000000ULL - roSize; | |
| const uint64_t space = (rwSlop < roSlop) ? rwSlop : roSlop; | |
| // choose new random slide | |
| long slide = (arc4random() % space) & (-4096); | |
| //dyld::log("rwSlop=0x%0llX, roSlop=0x%0llX\n", rwSlop, roSlop); | |
| //dyld::log("space=0x%0llX, slide=0x%0lX\n", space, slide); | |
| // update mappings | |
| for(uint32_t i=0; i < mappingsCount; ++i) { | |
| mappings[i].sfm_address += slide; | |
| } | |
| return slide; | |
| } | |
| // else fall through to handle old style cache | |
| #endif | |
| // get bounds of cache | |
| uint64_t lowAddress; | |
| uint64_t highAddress; | |
| getCacheBounds(mappingsCount, mappings, lowAddress, highAddress); | |
| // find slop space | |
| const uint64_t space = (SHARED_REGION_BASE + SHARED_REGION_SIZE) - highAddress; | |
| // choose new random slide | |
| #if __arm__ | |
| // <rdar://problem/20848977> change shared cache slide for 32-bit arm to always be 16k aligned | |
| long slide = ((arc4random() % space) & (-16384)); | |
| #else | |
| long slide = dyld_page_trunc(arc4random() % space); | |
| #endif | |
| //dyld::log("slideSpace=0x%0llX\n", space); | |
| //dyld::log("slide=0x%0lX\n", slide); | |
| // update mappings | |
| for(uint32_t i=0; i < mappingsCount; ++i) { | |
| mappings[i].sfm_address += slide; | |
| } | |
| return slide; | |
| } | |
| static void mapSharedCache() | |
| { | |
| uint64_t cacheBaseAddress = 0; | |
| // quick check if a cache is already mapped into shared region | |
| if ( _shared_region_check_np(&cacheBaseAddress) == 0 ) { | |
| sSharedCache = (dyld_cache_header*)cacheBaseAddress; | |
| // if we don't understand the currently mapped shared cache, then ignore | |
| #if __x86_64__ | |
| const char* magic = (sHaswell ? ARCH_CACHE_MAGIC_H : ARCH_CACHE_MAGIC); | |
| #else | |
| const char* magic = ARCH_CACHE_MAGIC; | |
| #endif | |
| if ( strcmp(sSharedCache->magic, magic) != 0 ) { | |
| sSharedCache = NULL; | |
| if ( gLinkContext.verboseMapping ) { | |
| dyld::log("dyld: existing shared cached in memory is not compatible\n"); | |
| return; | |
| } | |
| } | |
| // check if cache file is slidable | |
| const dyld_cache_header* header = sSharedCache; | |
| if ( (header->mappingOffset >= 0x48) && (header->slideInfoSize != 0) ) { | |
| // solve for slide by comparing loaded address to address of first region | |
| const uint8_t* loadedAddress = (uint8_t*)sSharedCache; | |
| const dyld_cache_mapping_info* const mappings = (dyld_cache_mapping_info*)(loadedAddress+header->mappingOffset); | |
| const uint8_t* preferedLoadAddress = (uint8_t*)(long)(mappings[0].address); | |
| sSharedCacheSlide = loadedAddress - preferedLoadAddress; | |
| dyld::gProcessInfo->sharedCacheSlide = sSharedCacheSlide; | |
| dyld::gProcessInfo->sharedCacheBaseAddress = cacheBaseAddress; | |
| //dyld::log("sSharedCacheSlide=0x%08lX, loadedAddress=%p, preferedLoadAddress=%p\n", sSharedCacheSlide, loadedAddress, preferedLoadAddress); | |
| } | |
| // if cache has a uuid, copy it | |
| if ( header->mappingOffset >= 0x68 ) { | |
| memcpy(dyld::gProcessInfo->sharedCacheUUID, header->uuid, 16); | |
| } | |
| // verbose logging | |
| if ( gLinkContext.verboseMapping ) { | |
| dyld::log("dyld: re-using existing %s shared cache mapping\n", (header->cacheType == kDyldSharedCacheTypeDevelopment ? "development" : "production")); | |
| } | |
| if (header->mappingOffset >= 0x68) { | |
| dyld_kernel_image_info_t kernelCacheInfo; | |
| memcpy(&kernelCacheInfo.uuid[0], &sSharedCache->uuid[0], sizeof(uuid_t)); | |
| kernelCacheInfo.load_addr = (uint64_t)sSharedCache; | |
| kernelCacheInfo.fsobjid.fid_objno = 0; | |
| kernelCacheInfo.fsobjid.fid_generation = 0; | |
| kernelCacheInfo.fsid.val[0] = 0; | |
| kernelCacheInfo.fsid.val[0] = 0; | |
| task_register_dyld_shared_cache_image_info(mach_task_self(), kernelCacheInfo, false, false); | |
| } | |
| } | |
| else { | |
| #if __i386__ || __x86_64__ | |
| // <rdar://problem/5925940> Safe Boot should disable dyld shared cache | |
| // if we are in safe-boot mode and the cache was not made during this boot cycle, | |
| // delete the cache file | |
| uint32_t safeBootValue = 0; | |
| size_t safeBootValueSize = sizeof(safeBootValue); | |
| if ( (sysctlbyname("kern.safeboot", &safeBootValue, &safeBootValueSize, NULL, 0) == 0) && (safeBootValue != 0) ) { | |
| // user booted machine in safe-boot mode | |
| struct stat dyldCacheStatInfo; | |
| // Don't use custom DYLD_SHARED_CACHE_DIR if provided, use standard path | |
| if ( my_stat(MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, &dyldCacheStatInfo) == 0 ) { | |
| struct timeval bootTimeValue; | |
| size_t bootTimeValueSize = sizeof(bootTimeValue); | |
| if ( (sysctlbyname("kern.boottime", &bootTimeValue, &bootTimeValueSize, NULL, 0) == 0) && (bootTimeValue.tv_sec != 0) ) { | |
| // if the cache file was created before this boot, then throw it away and let it rebuild itself | |
| if ( dyldCacheStatInfo.st_mtime < bootTimeValue.tv_sec ) { | |
| ::unlink(MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME); | |
| gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| // map in shared cache to shared region | |
| int fd = openSharedCacheFile(); | |
| if ( fd != -1 ) { | |
| uint8_t firstPages[8192]; | |
| if ( ::read(fd, firstPages, 8192) == 8192 ) { | |
| dyld_cache_header* header = (dyld_cache_header*)firstPages; | |
| #if __x86_64__ | |
| const char* magic = (sHaswell ? ARCH_CACHE_MAGIC_H : ARCH_CACHE_MAGIC); | |
| #else | |
| const char* magic = ARCH_CACHE_MAGIC; | |
| #endif | |
| if ( strcmp(header->magic, magic) == 0 ) { | |
| const dyld_cache_mapping_info* const fileMappingsStart = (dyld_cache_mapping_info*)&firstPages[header->mappingOffset]; | |
| const dyld_cache_mapping_info* const fileMappingsEnd = &fileMappingsStart[header->mappingCount]; | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| if ( (header->mappingCount != 3) | |
| || (header->mappingOffset > 256) | |
| || (fileMappingsStart[0].fileOffset != 0) | |
| || (fileMappingsStart[0].address != SHARED_REGION_BASE) | |
| || ((fileMappingsStart[0].address + fileMappingsStart[0].size) > fileMappingsStart[1].address) | |
| || ((fileMappingsStart[1].address + fileMappingsStart[1].size) > fileMappingsStart[2].address) | |
| || ((fileMappingsStart[0].fileOffset + fileMappingsStart[0].size) != fileMappingsStart[1].fileOffset) | |
| || ((fileMappingsStart[1].fileOffset + fileMappingsStart[1].size) != fileMappingsStart[2].fileOffset) ) | |
| throw "dyld shared cache file is invalid"; | |
| #endif | |
| shared_file_mapping_np mappings[header->mappingCount]; | |
| unsigned int mappingCount = header->mappingCount; | |
| int readWriteMappingIndex = -1; | |
| int readOnlyMappingIndex = -1; | |
| // validate that the cache file has not been truncated | |
| bool goodCache = false; | |
| struct stat stat_buf; | |
| if ( fstat(fd, &stat_buf) == 0 ) { | |
| goodCache = true; | |
| int i=0; | |
| for (const dyld_cache_mapping_info* p = fileMappingsStart; p < fileMappingsEnd; ++p, ++i) { | |
| mappings[i].sfm_address = p->address; | |
| mappings[i].sfm_size = p->size; | |
| mappings[i].sfm_file_offset = p->fileOffset; | |
| mappings[i].sfm_max_prot = p->maxProt; | |
| mappings[i].sfm_init_prot = p->initProt; | |
| // rdar://problem/5694507 old update_dyld_shared_cache tool could make a cache file | |
| // that is not page aligned, but otherwise ok. | |
| if ( p->fileOffset+p->size > (uint64_t)(stat_buf.st_size+4095 & (-4096)) ) { | |
| dyld::log("dyld: shared cached file is corrupt: %s" DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME "\n", sSharedCacheDir); | |
| goodCache = false; | |
| } | |
| if ( (mappings[i].sfm_init_prot & (VM_PROT_READ|VM_PROT_WRITE)) == (VM_PROT_READ|VM_PROT_WRITE) ) { | |
| readWriteMappingIndex = i; | |
| } | |
| if ( mappings[i].sfm_init_prot == VM_PROT_READ ) { | |
| readOnlyMappingIndex = i; | |
| } | |
| } | |
| // if shared cache is code signed, add a mapping for the code signature | |
| uint64_t signatureSize = header->codeSignatureSize; | |
| // zero size in header means signature runs to end-of-file | |
| if ( signatureSize == 0 ) | |
| signatureSize = stat_buf.st_size - header->codeSignatureOffset; | |
| if ( signatureSize != 0 ) { | |
| #if __arm__ || __arm64__ | |
| size_t alignedSignatureSize = (signatureSize+16383) & (-16384); | |
| #else | |
| size_t alignedSignatureSize = (signatureSize+4095) & (-4096); | |
| #endif | |
| // <rdar://problem/23188073> validate code signature covers entire shared cache | |
| loadAndCheckCodeSignature(fd, mappingCount, mappings, header->codeSignatureOffset, alignedSignatureSize, firstPages, sizeof(firstPages)); | |
| } | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| else { | |
| throw "dyld shared cache file not code signed"; | |
| } | |
| #endif | |
| } | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| // sanity check that /usr/lib/libSystem.B.dylib stat() info matches cache | |
| if ( header->imagesCount * sizeof(dyld_cache_image_info) + header->imagesOffset < 8192 ) { | |
| bool foundLibSystem = false; | |
| if ( my_stat("/usr/lib/libSystem.B.dylib", &stat_buf) == 0 ) { | |
| const dyld_cache_image_info* images = (dyld_cache_image_info*)&firstPages[header->imagesOffset]; | |
| const dyld_cache_image_info* const imagesEnd = &images[header->imagesCount]; | |
| for (const dyld_cache_image_info* p = images; p < imagesEnd; ++p) { | |
| if ( ((time_t)p->modTime == stat_buf.st_mtime) && ((ino_t)p->inode == stat_buf.st_ino) ) { | |
| foundLibSystem = true; | |
| break; | |
| } | |
| } | |
| } | |
| if ( !sSharedCacheIgnoreInodeAndTimeStamp && !foundLibSystem ) { | |
| dyld::log("dyld: shared cached file was built against a different libSystem.dylib, ignoring cache.\n" | |
| "to update dyld shared cache run: 'sudo update_dyld_shared_cache' then reboot.\n"); | |
| goodCache = false; | |
| } | |
| } | |
| #endif | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| { | |
| uint64_t lowAddress; | |
| uint64_t highAddress; | |
| getCacheBounds(mappingCount, mappings, lowAddress, highAddress); | |
| if ( (highAddress-lowAddress) > SHARED_REGION_SIZE ) | |
| throw "dyld shared cache is too big to fit in shared region"; | |
| } | |
| #endif | |
| if ( goodCache && (readWriteMappingIndex == -1) ) { | |
| dyld::log("dyld: shared cached file is missing read/write mapping: %s" DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME "\n", sSharedCacheDir); | |
| goodCache = false; | |
| } | |
| if ( goodCache && (readOnlyMappingIndex == -1) ) { | |
| dyld::log("dyld: shared cached file is missing read-only mapping: %s" DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME "\n", sSharedCacheDir); | |
| goodCache = false; | |
| } | |
| if ( goodCache ) { | |
| long cacheSlide = 0; | |
| void* slideInfo = (void*)(long)(mappings[readOnlyMappingIndex].sfm_address + (header->slideInfoOffset - mappings[readOnlyMappingIndex].sfm_file_offset));; | |
| uint64_t slideInfoSize = header->slideInfoSize; | |
| // check if shared cache contains slid info | |
| if ( slideInfoSize != 0 ) { | |
| // <rdar://problem/8611968> don't slide shared cache if ASLR disabled (main executable didn't slide) | |
| if ( sMainExecutable->isPositionIndependentExecutable() && (sMainExecutable->getSlide() == 0) ) { | |
| cacheSlide = 0; | |
| } | |
| else { | |
| // generate random slide amount | |
| cacheSlide = pickCacheSlide(mappingCount, mappings); | |
| } | |
| slideInfo = (void*)((uint8_t*)slideInfo + cacheSlide); | |
| // add VM_PROT_SLIDE bit to __DATA area of cache | |
| mappings[readWriteMappingIndex].sfm_max_prot |= VM_PROT_SLIDE; | |
| mappings[readWriteMappingIndex].sfm_init_prot |= VM_PROT_SLIDE; | |
| } | |
| if ( gLinkContext.verboseMapping ) { | |
| dyld::log("dyld: calling _shared_region_map_and_slide_np() with regions:\n"); | |
| for (int i=0; i < mappingCount; ++i) { | |
| dyld::log(" address=0x%08llX, size=0x%08llX, fileOffset=0x%08llX\n", mappings[i].sfm_address, mappings[i].sfm_size, mappings[i].sfm_file_offset); | |
| } | |
| } | |
| if (_shared_region_map_and_slide_np(fd, mappingCount, mappings, cacheSlide, slideInfo, slideInfoSize) == 0) { | |
| // successfully mapped cache into shared region | |
| sSharedCache = (dyld_cache_header*)mappings[0].sfm_address; | |
| sSharedCacheSlide = cacheSlide; | |
| dyld::gProcessInfo->sharedCacheSlide = cacheSlide; | |
| dyld::gProcessInfo->sharedCacheBaseAddress = mappings[0].sfm_address; | |
| //dyld::log("sSharedCache=%p sSharedCacheSlide=0x%08lX\n", sSharedCache, sSharedCacheSlide); | |
| // if cache has a uuid, copy it | |
| if ( header->mappingOffset >= 0x68 ) { | |
| const bool privateSharedCache = gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion; | |
| memcpy(dyld::gProcessInfo->sharedCacheUUID, header->uuid, 16); | |
| dyld_kernel_image_info_t kernelCacheInfo; | |
| memcpy(&kernelCacheInfo.uuid[0], &sSharedCache->uuid[0], sizeof(uuid_t)); | |
| kernelCacheInfo.load_addr = (uint64_t)sSharedCache; | |
| kernelCacheInfo.fsobjid.fid_objno = 0; | |
| kernelCacheInfo.fsobjid.fid_generation = 0; | |
| kernelCacheInfo.fsid.val[0] = 0; | |
| kernelCacheInfo.fsid.val[0] = 0; | |
| if (privateSharedCache) { | |
| kernelCacheInfo.fsobjid = *(fsobj_id_t*)(&stat_buf.st_ino); | |
| struct statfs statfs_buf; | |
| if ( fstatfs(fd, &statfs_buf) == 0 ) { | |
| kernelCacheInfo.fsid = statfs_buf.f_fsid; | |
| } | |
| } | |
| task_register_dyld_shared_cache_image_info(mach_task_self(), kernelCacheInfo, false, privateSharedCache); | |
| } | |
| } | |
| else { | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| throwf("dyld shared cache could not be mapped. errno=%d, slide=0x%08lX, slideInfo=%p, slideInfoSize=0x%08llX, mappingCount=%u, " | |
| "address/size/off/init/max [0]=0x%0llX/0x%0llX/0x%0llX/0x%02X/0x%02X, [1]=0x%0llX/0x%0llX/0x%0llX/0x%02X/0x%02X, [2]=0x%0llX/0x%0llX/0x%0llX/0x%02X/0x%02X", | |
| errno, cacheSlide, slideInfo, slideInfoSize, mappingCount, | |
| mappings[0].sfm_address, mappings[0].sfm_size, mappings[0].sfm_file_offset, mappings[0].sfm_init_prot, mappings[0].sfm_max_prot, | |
| mappings[1].sfm_address, mappings[1].sfm_size, mappings[1].sfm_file_offset, mappings[1].sfm_init_prot, mappings[1].sfm_max_prot, | |
| mappings[2].sfm_address, mappings[2].sfm_size, mappings[2].sfm_file_offset, mappings[2].sfm_init_prot, mappings[2].sfm_max_prot); | |
| #endif | |
| if ( gLinkContext.verboseMapping ) | |
| dyld::log("dyld: shared cached file could not be mapped\n"); | |
| } | |
| } | |
| } | |
| else { | |
| if ( gLinkContext.verboseMapping ) | |
| dyld::log("dyld: shared cached file is invalid\n"); | |
| } | |
| } | |
| else { | |
| if ( gLinkContext.verboseMapping ) | |
| dyld::log("dyld: shared cached file cannot be read\n"); | |
| } | |
| close(fd); | |
| } | |
| else { | |
| if ( gLinkContext.verboseMapping ) | |
| dyld::log("dyld: shared cached file cannot be opened\n"); | |
| } | |
| } | |
| // remember if dyld loaded at same address as when cache built | |
| if ( sSharedCache != NULL ) { | |
| gLinkContext.dyldLoadedAtSameAddressNeededBySharedCache = ((uintptr_t)(sSharedCache->dyldBaseAddress) == (uintptr_t)&_mh_dylinker_header); | |
| } | |
| // tell gdb where the shared cache is | |
| if ( sSharedCache != NULL ) { | |
| const dyld_cache_mapping_info* const start = (dyld_cache_mapping_info*)((uint8_t*)sSharedCache + sSharedCache->mappingOffset); | |
| dyld_shared_cache_ranges.sharedRegionsCount = sSharedCache->mappingCount; | |
| // only room to tell gdb about first four regions | |
| if ( dyld_shared_cache_ranges.sharedRegionsCount > 4 ) | |
| dyld_shared_cache_ranges.sharedRegionsCount = 4; | |
| const dyld_cache_mapping_info* const end = &start[dyld_shared_cache_ranges.sharedRegionsCount]; | |
| int index = 0; | |
| for (const dyld_cache_mapping_info* p = start; p < end; ++p, ++index ) { | |
| dyld_shared_cache_ranges.ranges[index].start = p->address+sSharedCacheSlide; | |
| dyld_shared_cache_ranges.ranges[index].length = p->size; | |
| if ( gLinkContext.verboseMapping ) { | |
| dyld::log(" 0x%08llX->0x%08llX %s%s%s init=%x, max=%x\n", | |
| p->address+sSharedCacheSlide, p->address+sSharedCacheSlide+p->size-1, | |
| ((p->initProt & VM_PROT_READ) ? "read " : ""), | |
| ((p->initProt & VM_PROT_WRITE) ? "write " : ""), | |
| ((p->initProt & VM_PROT_EXECUTE) ? "execute " : ""), p->initProt, p->maxProt); | |
| } | |
| #if __i386__ | |
| // If a non-writable and executable region is found in the R/W shared region, then this is __IMPORT segments | |
| // This is an old cache. Make writable. dyld no longer supports turn W on and off as it binds | |
| if ( (p->initProt == (VM_PROT_READ|VM_PROT_EXECUTE)) && ((p->address & 0xF0000000) == 0xA0000000) ) { | |
| if ( p->size != 0 ) { | |
| vm_prot_t prot = VM_PROT_EXECUTE | PROT_READ | VM_PROT_WRITE; | |
| vm_protect(mach_task_self(), p->address, p->size, false, prot); | |
| if ( gLinkContext.verboseMapping ) { | |
| dyld::log("%18s at 0x%08llX->0x%08llX altered permissions to %c%c%c\n", "", p->address, | |
| p->address+p->size-1, | |
| (prot & PROT_READ) ? 'r' : '.', (prot & PROT_WRITE) ? 'w' : '.', (prot & PROT_EXEC) ? 'x' : '.' ); | |
| } | |
| } | |
| } | |
| #endif | |
| } | |
| if ( gLinkContext.verboseMapping ) { | |
| // list the code blob | |
| dyld_cache_header* header = (dyld_cache_header*)sSharedCache; | |
| uint64_t signatureSize = header->codeSignatureSize; | |
| // zero size in header means signature runs to end-of-file | |
| if ( signatureSize == 0 ) { | |
| struct stat stat_buf; | |
| // FIXME: need size of cache file actually used | |
| if ( my_stat(IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, &stat_buf) == 0 ) | |
| signatureSize = stat_buf.st_size - header->codeSignatureOffset; | |
| } | |
| if ( signatureSize != 0 ) { | |
| const dyld_cache_mapping_info* const last = &start[dyld_shared_cache_ranges.sharedRegionsCount-1]; | |
| uint64_t codeBlobStart = last->address + last->size; | |
| dyld::log(" 0x%08llX->0x%08llX (code signature)\n", codeBlobStart, codeBlobStart+signatureSize); | |
| } | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCache->mappingOffset > 0x80) && (sSharedCache->accelerateInfoAddr != 0) ) { | |
| sAllCacheImagesProxy = ImageLoaderMegaDylib::makeImageLoaderMegaDylib(sSharedCache, sSharedCacheSlide, gLinkContext); | |
| } | |
| #endif | |
| } | |
| } | |
| #endif // #if DYLD_SHARED_CACHE_SUPPORT | |
| // create when NSLinkModule is called for a second time on a bundle | |
| ImageLoader* cloneImage(ImageLoader* image) | |
| { | |
| // open file (automagically closed when this function exits) | |
| FileOpener file(image->getPath()); | |
| struct stat stat_buf; | |
| if ( fstat(file.getFileDescriptor(), &stat_buf) == -1) | |
| throw "stat error"; | |
| dyld::LoadContext context; | |
| context.useSearchPaths = false; | |
| context.useFallbackPaths = false; | |
| context.useLdLibraryPath = false; | |
| context.implicitRPath = false; | |
| context.matchByInstallName = false; | |
| context.dontLoad = false; | |
| context.mustBeBundle = true; | |
| context.mustBeDylib = false; | |
| context.canBePIE = false; | |
| context.origin = NULL; | |
| context.rpath = NULL; | |
| return loadPhase6(file.getFileDescriptor(), stat_buf, image->getPath(), context); | |
| } | |
| ImageLoader* loadFromMemory(const uint8_t* mem, uint64_t len, const char* moduleName) | |
| { | |
| // if fat wrapper, find usable sub-file | |
| const fat_header* memStartAsFat = (fat_header*)mem; | |
| uint64_t fileOffset = 0; | |
| uint64_t fileLength = len; | |
| if ( memStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) { | |
| if ( fatFindBest(memStartAsFat, &fileOffset, &fileLength) ) { | |
| mem = &mem[fileOffset]; | |
| len = fileLength; | |
| } | |
| else { | |
| throw "no matching architecture in universal wrapper"; | |
| } | |
| } | |
| // try each loader | |
| if ( isCompatibleMachO(mem, moduleName) ) { | |
| ImageLoader* image = ImageLoaderMachO::instantiateFromMemory(moduleName, (macho_header*)mem, len, gLinkContext); | |
| // don't add bundles to global list, they can be loaded but not linked. When linked it will be added to list | |
| if ( ! image->isBundle() ) | |
| addImage(image); | |
| return image; | |
| } | |
| // try other file formats here... | |
| // throw error about what was found | |
| switch (*(uint32_t*)mem) { | |
| case MH_MAGIC: | |
| case MH_CIGAM: | |
| case MH_MAGIC_64: | |
| case MH_CIGAM_64: | |
| throw "mach-o, but wrong architecture"; | |
| default: | |
| throwf("unknown file type, first eight bytes: 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X", | |
| mem[0], mem[1], mem[2], mem[3], mem[4], mem[5], mem[6],mem[7]); | |
| } | |
| } | |
| void registerAddCallback(ImageCallback func) | |
| { | |
| // now add to list to get notified when any more images are added | |
| sAddImageCallbacks.push_back(func); | |
| // call callback with all existing images | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* image = *it; | |
| if ( image->getState() >= dyld_image_state_bound && image->getState() < dyld_image_state_terminated ) | |
| (*func)(image->machHeader(), image->getSlide()); | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( sAllCacheImagesProxy != NULL ) { | |
| dyld_image_info infos[allImagesCount()+1]; | |
| unsigned cacheCount = sAllCacheImagesProxy->appendImagesToNotify(dyld_image_state_bound, true, infos); | |
| for (unsigned i=0; i < cacheCount; ++i) { | |
| (*func)(infos[i].imageLoadAddress, sSharedCacheSlide); | |
| } | |
| } | |
| #endif | |
| } | |
| void registerRemoveCallback(ImageCallback func) | |
| { | |
| // <rdar://problem/15025198> ignore calls to register a notification during a notification | |
| if ( sRemoveImageCallbacksInUse ) | |
| return; | |
| sRemoveImageCallbacks.push_back(func); | |
| } | |
| void clearErrorMessage() | |
| { | |
| error_string[0] = '\0'; | |
| } | |
| void setErrorMessage(const char* message) | |
| { | |
| // save off error message in global buffer for CrashReporter to find | |
| strlcpy(error_string, message, sizeof(error_string)); | |
| } | |
| const char* getErrorMessage() | |
| { | |
| return error_string; | |
| } | |
| void halt(const char* message) | |
| { | |
| dyld::log("dyld: %s\n", message); | |
| setErrorMessage(message); | |
| dyld::gProcessInfo->errorMessage = error_string; | |
| if ( !gLinkContext.startedInitializingMainExecutable ) | |
| dyld::gProcessInfo->terminationFlags = 1; | |
| else | |
| dyld::gProcessInfo->terminationFlags = 0; | |
| char payloadBuffer[EXIT_REASON_PAYLOAD_MAX_LEN]; | |
| dyld_abort_payload* payload = (dyld_abort_payload*)payloadBuffer; | |
| payload->version = 1; | |
| payload->flags = gLinkContext.startedInitializingMainExecutable ? 0 : 1; | |
| payload->targetDylibPathOffset = 0; | |
| payload->clientPathOffset = 0; | |
| payload->symbolOffset = 0; | |
| int payloadSize = sizeof(dyld_abort_payload); | |
| if ( dyld::gProcessInfo->errorTargetDylibPath != NULL ) { | |
| payload->targetDylibPathOffset = payloadSize; | |
| payloadSize += strlcpy(&payloadBuffer[payloadSize], dyld::gProcessInfo->errorTargetDylibPath, sizeof(payloadBuffer)-payloadSize) + 1; | |
| } | |
| if ( dyld::gProcessInfo->errorClientOfDylibPath != NULL ) { | |
| payload->clientPathOffset = payloadSize; | |
| payloadSize += strlcpy(&payloadBuffer[payloadSize], dyld::gProcessInfo->errorClientOfDylibPath, sizeof(payloadBuffer)-payloadSize) + 1; | |
| } | |
| if ( dyld::gProcessInfo->errorSymbol != NULL ) { | |
| payload->symbolOffset = payloadSize; | |
| payloadSize += strlcpy(&payloadBuffer[payloadSize], dyld::gProcessInfo->errorSymbol, sizeof(payloadBuffer)-payloadSize) + 1; | |
| } | |
| char truncMessage[EXIT_REASON_USER_DESC_MAX_LEN]; | |
| strlcpy(truncMessage, message, EXIT_REASON_USER_DESC_MAX_LEN); | |
| abort_with_payload(OS_REASON_DYLD, dyld::gProcessInfo->errorKind ? dyld::gProcessInfo->errorKind : DYLD_EXIT_REASON_OTHER, payloadBuffer, payloadSize, truncMessage, 0); | |
| } | |
| static void setErrorStrings(unsigned errorCode, const char* errorClientOfDylibPath, | |
| const char* errorTargetDylibPath, const char* errorSymbol) | |
| { | |
| dyld::gProcessInfo->errorKind = errorCode; | |
| dyld::gProcessInfo->errorClientOfDylibPath = errorClientOfDylibPath; | |
| dyld::gProcessInfo->errorTargetDylibPath = errorTargetDylibPath; | |
| dyld::gProcessInfo->errorSymbol = errorSymbol; | |
| } | |
| uintptr_t bindLazySymbol(const mach_header* mh, uintptr_t* lazyPointer) | |
| { | |
| uintptr_t result = 0; | |
| // acquire read-lock on dyld's data structures | |
| #if 0 // rdar://problem/3811777 turn off locking until deadlock is resolved | |
| if ( gLibSystemHelpers != NULL ) | |
| (*gLibSystemHelpers->lockForReading)(); | |
| #endif | |
| // lookup and bind lazy pointer and get target address | |
| try { | |
| ImageLoader* target; | |
| #if __i386__ | |
| // fast stubs pass NULL for mh and image is instead found via the location of stub (aka lazyPointer) | |
| if ( mh == NULL ) | |
| target = dyld::findImageContainingAddress(lazyPointer); | |
| else | |
| target = dyld::findImageByMachHeader(mh); | |
| #else | |
| // note, target should always be mach-o, because only mach-o lazy handler wired up to this | |
| target = dyld::findImageByMachHeader(mh); | |
| #endif | |
| if ( target == NULL ) | |
| throwf("image not found for lazy pointer at %p", lazyPointer); | |
| result = target->doBindLazySymbol(lazyPointer, gLinkContext); | |
| } | |
| catch (const char* message) { | |
| dyld::log("dyld: lazy symbol binding failed: %s\n", message); | |
| halt(message); | |
| } | |
| // release read-lock on dyld's data structures | |
| #if 0 | |
| if ( gLibSystemHelpers != NULL ) | |
| (*gLibSystemHelpers->unlockForReading)(); | |
| #endif | |
| // return target address to glue which jumps to it with real parameters restored | |
| return result; | |
| } | |
| uintptr_t fastBindLazySymbol(ImageLoader** imageLoaderCache, uintptr_t lazyBindingInfoOffset) | |
| { | |
| uintptr_t result = 0; | |
| // get image | |
| if ( *imageLoaderCache == NULL ) { | |
| // save in cache | |
| *imageLoaderCache = dyld::findMappedRange((uintptr_t)imageLoaderCache); | |
| if ( *imageLoaderCache == NULL ) { | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( sAllCacheImagesProxy != NULL ) { | |
| const mach_header* mh; | |
| const char* path; | |
| unsigned index; | |
| if ( sAllCacheImagesProxy->addressInCache(imageLoaderCache, &mh, &path, &index) ) { | |
| result = sAllCacheImagesProxy->bindLazy(lazyBindingInfoOffset, gLinkContext, mh, index); | |
| if ( result == 0 ) { | |
| halt("dyld: lazy symbol binding failed for image in dyld shared\n"); | |
| } | |
| return result; | |
| } | |
| } | |
| #endif | |
| const char* message = "fast lazy binding from unknown image"; | |
| dyld::log("dyld: %s\n", message); | |
| halt(message); | |
| } | |
| } | |
| // bind lazy pointer and return it | |
| try { | |
| result = (*imageLoaderCache)->doBindFastLazySymbol((uint32_t)lazyBindingInfoOffset, gLinkContext, | |
| (dyld::gLibSystemHelpers != NULL) ? dyld::gLibSystemHelpers->acquireGlobalDyldLock : NULL, | |
| (dyld::gLibSystemHelpers != NULL) ? dyld::gLibSystemHelpers->releaseGlobalDyldLock : NULL); | |
| } | |
| catch (const char* message) { | |
| dyld::log("dyld: lazy symbol binding failed: %s\n", message); | |
| halt(message); | |
| } | |
| // return target address to glue which jumps to it with real parameters restored | |
| return result; | |
| } | |
| void registerUndefinedHandler(UndefinedHandler handler) | |
| { | |
| sUndefinedHandler = handler; | |
| } | |
| static void undefinedHandler(const char* symboName) | |
| { | |
| if ( sUndefinedHandler != NULL ) { | |
| (*sUndefinedHandler)(symboName); | |
| } | |
| } | |
| static bool findExportedSymbol(const char* name, bool onlyInCoalesced, const ImageLoader::Symbol** sym, const ImageLoader** image) | |
| { | |
| // search all images in order | |
| const ImageLoader* firstWeakImage = NULL; | |
| const ImageLoader::Symbol* firstWeakSym = NULL; | |
| const size_t imageCount = sAllImages.size(); | |
| for(size_t i=0; i < imageCount; ++i) { | |
| ImageLoader* anImage = sAllImages[i]; | |
| // the use of inserted libraries alters search order | |
| // so that inserted libraries are found before the main executable | |
| if ( sInsertedDylibCount > 0 ) { | |
| if ( i < sInsertedDylibCount ) | |
| anImage = sAllImages[i+1]; | |
| else if ( i == sInsertedDylibCount ) | |
| anImage = sAllImages[0]; | |
| } | |
| if ( ! anImage->hasHiddenExports() && (!onlyInCoalesced || anImage->hasCoalescedExports()) ) { | |
| *sym = anImage->findExportedSymbol(name, false, image); | |
| if ( *sym != NULL ) { | |
| // if weak definition found, record first one found | |
| if ( ((*image)->getExportedSymbolInfo(*sym) & ImageLoader::kWeakDefinition) != 0 ) { | |
| if ( firstWeakImage == NULL ) { | |
| firstWeakImage = *image; | |
| firstWeakSym = *sym; | |
| } | |
| } | |
| else { | |
| // found non-weak, so immediately return with it | |
| return true; | |
| } | |
| } | |
| } | |
| } | |
| if ( firstWeakSym != NULL ) { | |
| // found a weak definition, but no non-weak, so return first weak found | |
| *sym = firstWeakSym; | |
| *image = firstWeakImage; | |
| return true; | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( sAllCacheImagesProxy != NULL ) { | |
| if ( sAllCacheImagesProxy->flatFindSymbol(name, onlyInCoalesced, sym, image) ) | |
| return true; | |
| } | |
| #endif | |
| return false; | |
| } | |
| bool flatFindExportedSymbol(const char* name, const ImageLoader::Symbol** sym, const ImageLoader** image) | |
| { | |
| return findExportedSymbol(name, false, sym, image); | |
| } | |
| bool findCoalescedExportedSymbol(const char* name, const ImageLoader::Symbol** sym, const ImageLoader** image) | |
| { | |
| return findExportedSymbol(name, true, sym, image); | |
| } | |
| bool flatFindExportedSymbolWithHint(const char* name, const char* librarySubstring, const ImageLoader::Symbol** sym, const ImageLoader** image) | |
| { | |
| // search all images in order | |
| const size_t imageCount = sAllImages.size(); | |
| for(size_t i=0; i < imageCount; ++i){ | |
| ImageLoader* anImage = sAllImages[i]; | |
| // only look at images whose paths contain the hint string (NULL hint string is wildcard) | |
| if ( ! anImage->isBundle() && ((librarySubstring==NULL) || (strstr(anImage->getPath(), librarySubstring) != NULL)) ) { | |
| *sym = anImage->findExportedSymbol(name, false, image); | |
| if ( *sym != NULL ) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| unsigned int getCoalescedImages(ImageLoader* images[], unsigned imageIndex[]) | |
| { | |
| unsigned int count = 0; | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* image = *it; | |
| if ( image->participatesInCoalescing() ) { | |
| images[count] = *it; | |
| imageIndex[count] = 0; | |
| ++count; | |
| } | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( sAllCacheImagesProxy != NULL ) { | |
| sAllCacheImagesProxy->appendImagesNeedingCoalescing(images, imageIndex, count); | |
| } | |
| #endif | |
| return count; | |
| } | |
| static ImageLoader::MappedRegion* getMappedRegions(ImageLoader::MappedRegion* regions) | |
| { | |
| ImageLoader::MappedRegion* end = regions; | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| (*it)->getMappedRegions(end); | |
| } | |
| return end; | |
| } | |
| void registerImageStateSingleChangeHandler(dyld_image_states state, dyld_image_state_change_handler handler) | |
| { | |
| // mark the image that the handler is in as never-unload because dyld has a reference into it | |
| ImageLoader* handlerImage = findImageContainingAddress((void*)handler); | |
| if ( handlerImage != NULL ) | |
| handlerImage->setNeverUnload(); | |
| // add to list of handlers | |
| std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers); | |
| if ( handlers != NULL ) { | |
| // <rdar://problem/10332417> need updateAllImages() to be last in dyld_image_state_mapped list | |
| // so that if ObjC adds a handler that prevents a load, it happens before the gdb list is updated | |
| if ( state == dyld_image_state_mapped ) | |
| handlers->insert(handlers->begin(), handler); | |
| else | |
| handlers->push_back(handler); | |
| // call callback with all existing images | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* image = *it; | |
| dyld_image_info info; | |
| info.imageLoadAddress = image->machHeader(); | |
| info.imageFilePath = image->getRealPath(); | |
| info.imageFileModDate = image->lastModified(); | |
| // should only call handler if state == image->state | |
| if ( image->getState() == state ) | |
| (*handler)(state, 1, &info); | |
| // ignore returned string, too late to do anything | |
| } | |
| } | |
| } | |
| void registerImageStateBatchChangeHandler(dyld_image_states state, dyld_image_state_change_handler handler) | |
| { | |
| // mark the image that the handler is in as never-unload because dyld has a reference into it | |
| ImageLoader* handlerImage = findImageContainingAddress((void*)handler); | |
| if ( handlerImage != NULL ) | |
| handlerImage->setNeverUnload(); | |
| // add to list of handlers | |
| std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sBatchHandlers); | |
| if ( handlers != NULL ) { | |
| // insert at front, so that gdb handler is always last | |
| handlers->insert(handlers->begin(), handler); | |
| // call callback with all existing images | |
| try { | |
| notifyBatchPartial(state, true, handler, false, false); | |
| } | |
| catch (const char* msg) { | |
| // ignore request to abort during registration | |
| } | |
| } | |
| } | |
| void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped) | |
| { | |
| // record functions to call | |
| sNotifyObjCMapped = mapped; | |
| sNotifyObjCInit = init; | |
| sNotifyObjCUnmapped = unmapped; | |
| // call 'mapped' function with all images mapped so far | |
| try { | |
| notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true); | |
| } | |
| catch (const char* msg) { | |
| // ignore request to abort during registration | |
| } | |
| } | |
| bool sharedCacheUUID(uuid_t uuid) | |
| { | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| if ( sSharedCache == NULL ) | |
| return false; | |
| memcpy(uuid, sSharedCache->uuid, 16); | |
| return true; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| bool dlopenFromCache(const char* path, int mode, void** handle) | |
| { | |
| if ( sAllCacheImagesProxy == NULL ) | |
| return false; | |
| bool result = sAllCacheImagesProxy->dlopenFromCache(gLinkContext, path, mode, handle); | |
| if ( !result && (strchr(path, '/') == NULL) ) { | |
| // POSIX says you can call dlopen() with a leaf name (e.g. dlopen("libz.dylb")) | |
| char fallbackPath[PATH_MAX]; | |
| strcpy(fallbackPath, "/usr/lib/"); | |
| strlcat(fallbackPath, path, PATH_MAX); | |
| result = sAllCacheImagesProxy->dlopenFromCache(gLinkContext, fallbackPath, mode, handle); | |
| } | |
| return result; | |
| } | |
| bool makeCacheHandle(ImageLoader* image, unsigned cacheIndex, int mode, void** result) | |
| { | |
| if ( sAllCacheImagesProxy == NULL ) | |
| return false; | |
| return sAllCacheImagesProxy->makeCacheHandle(gLinkContext, cacheIndex, mode, result); | |
| } | |
| bool isCacheHandle(void* handle) | |
| { | |
| if ( sAllCacheImagesProxy == NULL ) | |
| return false; | |
| return sAllCacheImagesProxy->isCacheHandle(handle, NULL, NULL); | |
| } | |
| bool isPathInCache(const char* path) | |
| { | |
| if ( sAllCacheImagesProxy == NULL ) | |
| return false; | |
| unsigned index; | |
| return sAllCacheImagesProxy->hasDylib(path, &index); | |
| } | |
| const char* getPathFromIndex(unsigned cacheIndex) | |
| { | |
| if ( sAllCacheImagesProxy == NULL ) | |
| return NULL; | |
| return sAllCacheImagesProxy->getIndexedPath(cacheIndex); | |
| } | |
| void* dlsymFromCache(void* handle, const char* symName, unsigned index) | |
| { | |
| if ( sAllCacheImagesProxy == NULL ) | |
| return NULL; | |
| return sAllCacheImagesProxy->dlsymFromCache(gLinkContext, handle, symName, index); | |
| } | |
| bool addressInCache(const void* address, const mach_header** mh, const char** path, unsigned* index) | |
| { | |
| if ( sAllCacheImagesProxy == NULL ) | |
| return false; | |
| unsigned ignore; | |
| return sAllCacheImagesProxy->addressInCache(address, mh, path, index ? index : &ignore); | |
| } | |
| bool findUnwindSections(const void* addr, dyld_unwind_sections* info) | |
| { | |
| if ( sAllCacheImagesProxy == NULL ) | |
| return false; | |
| return sAllCacheImagesProxy->findUnwindSections(addr, info); | |
| } | |
| bool dladdrFromCache(const void* address, Dl_info* info) | |
| { | |
| if ( sAllCacheImagesProxy == NULL ) | |
| return false; | |
| return sAllCacheImagesProxy->dladdrFromCache(address, info); | |
| } | |
| #endif | |
| static ImageLoader* libraryLocator(const char* libraryName, bool search, const char* origin, const ImageLoader::RPathChain* rpaths, unsigned& cacheIndex) | |
| { | |
| dyld::LoadContext context; | |
| context.useSearchPaths = search; | |
| context.useFallbackPaths = search; | |
| context.useLdLibraryPath = false; | |
| context.implicitRPath = false; | |
| context.matchByInstallName = false; | |
| context.dontLoad = false; | |
| context.mustBeBundle = false; | |
| context.mustBeDylib = true; | |
| context.canBePIE = false; | |
| context.origin = origin; | |
| context.rpath = rpaths; | |
| return load(libraryName, context, cacheIndex); | |
| } | |
| static const char* basename(const char* path) | |
| { | |
| const char* last = path; | |
| for (const char* s = path; *s != '\0'; s++) { | |
| if (*s == '/') | |
| last = s+1; | |
| } | |
| return last; | |
| } | |
| static void setContext(const macho_header* mainExecutableMH, int argc, const char* argv[], const char* envp[], const char* apple[]) | |
| { | |
| gLinkContext.loadLibrary = &libraryLocator; | |
| gLinkContext.terminationRecorder = &terminationRecorder; | |
| gLinkContext.flatExportFinder = &flatFindExportedSymbol; | |
| gLinkContext.coalescedExportFinder = &findCoalescedExportedSymbol; | |
| gLinkContext.getCoalescedImages = &getCoalescedImages; | |
| gLinkContext.undefinedHandler = &undefinedHandler; | |
| gLinkContext.getAllMappedRegions = &getMappedRegions; | |
| gLinkContext.bindingHandler = NULL; | |
| gLinkContext.notifySingle = ¬ifySingle; | |
| gLinkContext.notifyBatch = ¬ifyBatch; | |
| gLinkContext.removeImage = &removeImage; | |
| gLinkContext.registerDOFs = ®isterDOFs; | |
| gLinkContext.clearAllDepths = &clearAllDepths; | |
| gLinkContext.printAllDepths = &printAllDepths; | |
| gLinkContext.imageCount = &imageCount; | |
| gLinkContext.setNewProgramVars = &setNewProgramVars; | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| gLinkContext.inSharedCache = &inSharedCache; | |
| #endif | |
| gLinkContext.setErrorStrings = &setErrorStrings; | |
| #if SUPPORT_OLD_CRT_INITIALIZATION | |
| gLinkContext.setRunInitialzersOldWay= &setRunInitialzersOldWay; | |
| #endif | |
| gLinkContext.findImageContainingAddress = &findImageContainingAddress; | |
| gLinkContext.addDynamicReference = &addDynamicReference; | |
| #if SUPPORT_ACCELERATE_TABLES | |
| gLinkContext.notifySingleFromCache = ¬ifySingleFromCache; | |
| gLinkContext.getPreInitNotifyHandler= &getPreInitNotifyHandler; | |
| gLinkContext.getBoundBatchHandler = &getBoundBatchHandler; | |
| #endif | |
| gLinkContext.bindingOptions = ImageLoader::kBindingNone; | |
| gLinkContext.argc = argc; | |
| gLinkContext.argv = argv; | |
| gLinkContext.envp = envp; | |
| gLinkContext.apple = apple; | |
| gLinkContext.progname = (argv[0] != NULL) ? basename(argv[0]) : ""; | |
| gLinkContext.programVars.mh = mainExecutableMH; | |
| gLinkContext.programVars.NXArgcPtr = &gLinkContext.argc; | |
| gLinkContext.programVars.NXArgvPtr = &gLinkContext.argv; | |
| gLinkContext.programVars.environPtr = &gLinkContext.envp; | |
| gLinkContext.programVars.__prognamePtr=&gLinkContext.progname; | |
| gLinkContext.mainExecutable = NULL; | |
| gLinkContext.imageSuffix = NULL; | |
| gLinkContext.dynamicInterposeArray = NULL; | |
| gLinkContext.dynamicInterposeCount = 0; | |
| gLinkContext.prebindUsage = ImageLoader::kUseAllPrebinding; | |
| #if TARGET_IPHONE_SIMULATOR | |
| gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; | |
| #else | |
| gLinkContext.sharedRegionMode = ImageLoader::kUseSharedRegion; | |
| #endif | |
| } | |
| // | |
| // Look for a special segment in the mach header. | |
| // Its presences means that the binary wants to have DYLD ignore | |
| // DYLD_ environment variables. | |
| // | |
| static bool hasRestrictedSegment(const macho_header* mh) | |
| { | |
| const uint32_t cmd_count = mh->ncmds; | |
| const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); | |
| const struct load_command* cmd = cmds; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| switch (cmd->cmd) { | |
| case LC_SEGMENT_COMMAND: | |
| { | |
| const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; | |
| //dyld::log("seg name: %s\n", seg->segname); | |
| if (strcmp(seg->segname, "__RESTRICT") == 0) { | |
| const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); | |
| const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; | |
| for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { | |
| if (strcmp(sect->sectname, "__restrict") == 0) | |
| return true; | |
| } | |
| } | |
| } | |
| break; | |
| } | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| } | |
| return false; | |
| } | |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED | |
| static bool isFairPlayEncrypted(const macho_header* mh) | |
| { | |
| const uint32_t cmd_count = mh->ncmds; | |
| const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); | |
| const struct load_command* cmd = cmds; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| if ( cmd->cmd == LC_ENCRYPT_COMMAND ) { | |
| const encryption_info_command* enc = (encryption_info_command*)cmd; | |
| return (enc->cryptid != 0); | |
| } | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| } | |
| return false; | |
| } | |
| #endif | |
| #if SUPPORT_VERSIONED_PATHS | |
| static bool readFirstPage(const char* dylibPath, uint8_t firstPage[4096]) | |
| { | |
| firstPage[0] = 0; | |
| // open file (automagically closed when this function exits) | |
| FileOpener file(dylibPath); | |
| if ( file.getFileDescriptor() == -1 ) | |
| return false; | |
| if ( pread(file.getFileDescriptor(), firstPage, 4096, 0) != 4096 ) | |
| return false; | |
| // if fat wrapper, find usable sub-file | |
| const fat_header* fileStartAsFat = (fat_header*)firstPage; | |
| if ( fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) { | |
| uint64_t fileOffset; | |
| uint64_t fileLength; | |
| if ( fatFindBest(fileStartAsFat, &fileOffset, &fileLength) ) { | |
| if ( pread(file.getFileDescriptor(), firstPage, 4096, fileOffset) != 4096 ) | |
| return false; | |
| } | |
| else { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // | |
| // Peeks at a dylib file and returns its current_version and install_name. | |
| // Returns false on error. | |
| // | |
| static bool getDylibVersionAndInstallname(const char* dylibPath, uint32_t* version, char* installName) | |
| { | |
| uint8_t firstPage[4096]; | |
| const macho_header* mh = (macho_header*)firstPage; | |
| if ( !readFirstPage(dylibPath, firstPage) ) { | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| // If file cannot be read, check to see if path is in shared cache | |
| const macho_header* mhInCache; | |
| const char* pathInCache; | |
| long slideInCache; | |
| if ( !findInSharedCacheImage(dylibPath, true, NULL, &mhInCache, &pathInCache, &slideInCache) ) | |
| return false; | |
| mh = mhInCache; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| // check mach-o header | |
| if ( mh->magic != sMainExecutableMachHeader->magic ) | |
| return false; | |
| if ( mh->cputype != sMainExecutableMachHeader->cputype ) | |
| return false; | |
| // scan load commands for LC_ID_DYLIB | |
| const uint32_t cmd_count = mh->ncmds; | |
| const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); | |
| const struct load_command* const cmdsReadEnd = (struct load_command*)(((char*)mh)+4096); | |
| const struct load_command* cmd = cmds; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| switch (cmd->cmd) { | |
| case LC_ID_DYLIB: | |
| { | |
| const struct dylib_command* id = (struct dylib_command*)cmd; | |
| *version = id->dylib.current_version; | |
| if ( installName != NULL ) | |
| strlcpy(installName, (char *)id + id->dylib.name.offset, PATH_MAX); | |
| return true; | |
| } | |
| break; | |
| } | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| if ( cmd > cmdsReadEnd ) | |
| return false; | |
| } | |
| return false; | |
| } | |
| #endif // SUPPORT_VERSIONED_PATHS | |
| #if 0 | |
| static void printAllImages() | |
| { | |
| dyld::log("printAllImages()\n"); | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* image = *it; | |
| dyld_image_states imageState = image->getState(); | |
| dyld::log(" state=%d, dlopen-count=%d, never-unload=%d, in-use=%d, name=%s\n", | |
| imageState, image->dlopenCount(), image->neverUnload(), image->isMarkedInUse(), image->getShortName()); | |
| } | |
| } | |
| #endif | |
| void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex) | |
| { | |
| // add to list of known images. This did not happen at creation time for bundles | |
| if ( image->isBundle() && !image->isLinked() ) | |
| addImage(image); | |
| // we detect root images as those not linked in yet | |
| if ( !image->isLinked() ) | |
| addRootImage(image); | |
| // process images | |
| try { | |
| const char* path = image->getPath(); | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( image == sAllCacheImagesProxy ) | |
| path = sAllCacheImagesProxy->getIndexedPath(cacheIndex); | |
| #endif | |
| image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path); | |
| } | |
| catch (const char* msg) { | |
| garbageCollectImages(); | |
| throw; | |
| } | |
| } | |
| void runInitializers(ImageLoader* image) | |
| { | |
| // do bottom up initialization | |
| ImageLoader::InitializerTimingList initializerTimes[allImagesCount()]; | |
| initializerTimes[0].count = 0; | |
| image->runInitializers(gLinkContext, initializerTimes[0]); | |
| } | |
| // This function is called at the end of dlclose() when the reference count goes to zero. | |
| // The dylib being unloaded may have brought in other dependent dylibs when it was loaded. | |
| // Those dependent dylibs need to be unloaded, but only if they are not referenced by | |
| // something else. We use a standard mark and sweep garbage collection. | |
| // | |
| // The tricky part is that when a dylib is unloaded it may have a termination function that | |
| // can run and itself call dlclose() on yet another dylib. The problem is that this | |
| // sort of gabage collection is not re-entrant. Instead a terminator's call to dlclose() | |
| // which calls garbageCollectImages() will just set a flag to re-do the garbage collection | |
| // when the current pass is done. | |
| // | |
| // Also note that this is done within the dyld global lock, so it is always single threaded. | |
| // | |
| void garbageCollectImages() | |
| { | |
| static bool sDoingGC = false; | |
| static bool sRedo = false; | |
| if ( sDoingGC ) { | |
| // GC is currently being run, just set a flag to have it run again. | |
| sRedo = true; | |
| return; | |
| } | |
| sDoingGC = true; | |
| do { | |
| sRedo = false; | |
| // mark phase: mark all images not-in-use | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* image = *it; | |
| //dyld::log("gc: neverUnload=%d name=%s\n", image->neverUnload(), image->getShortName()); | |
| image->markNotUsed(); | |
| } | |
| // sweep phase: mark as in-use, images reachable from never-unload or in-use image | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* image = *it; | |
| if ( (image->dlopenCount() != 0) || image->neverUnload() || (image == sMainExecutable) ) { | |
| OSSpinLockLock(&sDynamicReferencesLock); | |
| image->markedUsedRecursive(sDynamicReferences); | |
| OSSpinLockUnlock(&sDynamicReferencesLock); | |
| } | |
| } | |
| // collect phase: build array of images not marked in-use | |
| ImageLoader* deadImages[sAllImages.size()]; | |
| unsigned deadCount = 0; | |
| int maxRangeCount = 0; | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* image = *it; | |
| if ( ! image->isMarkedInUse() ) { | |
| deadImages[deadCount++] = image; | |
| if (gLogAPIs) dyld::log("dlclose(), found unused image %p %s\n", image, image->getShortName()); | |
| maxRangeCount += image->segmentCount(); | |
| } | |
| } | |
| // collect phase: run termination routines for images not marked in-use | |
| __cxa_range_t ranges[maxRangeCount]; | |
| int rangeCount = 0; | |
| for (unsigned i=0; i < deadCount; ++i) { | |
| ImageLoader* image = deadImages[i]; | |
| for (unsigned int j=0; j < image->segmentCount(); ++j) { | |
| if ( !image->segExecutable(j) ) | |
| continue; | |
| if ( rangeCount < maxRangeCount ) { | |
| ranges[rangeCount].addr = (const void*)image->segActualLoadAddress(j); | |
| ranges[rangeCount].length = image->segSize(j); | |
| ++rangeCount; | |
| } | |
| } | |
| try { | |
| runImageStaticTerminators(image); | |
| } | |
| catch (const char* msg) { | |
| dyld::warn("problem running terminators for image: %s\n", msg); | |
| } | |
| } | |
| // <rdar://problem/14718598> dyld should call __cxa_finalize_ranges() | |
| if ( (rangeCount > 0) && (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 13) ) | |
| (*gLibSystemHelpers->cxa_finalize_ranges)(ranges, rangeCount); | |
| // collect phase: delete all images which are not marked in-use | |
| bool mightBeMore; | |
| do { | |
| mightBeMore = false; | |
| for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { | |
| ImageLoader* image = *it; | |
| if ( ! image->isMarkedInUse() ) { | |
| try { | |
| if (gLogAPIs) dyld::log("dlclose(), deleting %p %s\n", image, image->getShortName()); | |
| removeImage(image); | |
| ImageLoader::deleteImage(image); | |
| mightBeMore = true; | |
| break; // interator in invalidated by this removal | |
| } | |
| catch (const char* msg) { | |
| dyld::warn("problem deleting image: %s\n", msg); | |
| } | |
| } | |
| } | |
| } while ( mightBeMore ); | |
| } while (sRedo); | |
| sDoingGC = false; | |
| //printAllImages(); | |
| } | |
| static void preflight_finally(ImageLoader* image) | |
| { | |
| if ( image->isBundle() ) { | |
| removeImageFromAllImages(image->machHeader()); | |
| ImageLoader::deleteImage(image); | |
| } | |
| sBundleBeingLoaded = NULL; | |
| dyld::garbageCollectImages(); | |
| } | |
| void preflight(ImageLoader* image, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex) | |
| { | |
| try { | |
| if ( image->isBundle() ) | |
| sBundleBeingLoaded = image; // hack | |
| const char* path = image->getPath(); | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( image == sAllCacheImagesProxy ) | |
| path = sAllCacheImagesProxy->getIndexedPath(cacheIndex); | |
| #endif | |
| image->link(gLinkContext, false, true, false, loaderRPaths, path); | |
| } | |
| catch (const char* msg) { | |
| preflight_finally(image); | |
| throw; | |
| } | |
| preflight_finally(image); | |
| } | |
| static void loadInsertedDylib(const char* path) | |
| { | |
| ImageLoader* image = NULL; | |
| unsigned cacheIndex; | |
| try { | |
| LoadContext context; | |
| context.useSearchPaths = false; | |
| context.useFallbackPaths = false; | |
| context.useLdLibraryPath = false; | |
| context.implicitRPath = false; | |
| context.matchByInstallName = false; | |
| context.dontLoad = false; | |
| context.mustBeBundle = false; | |
| context.mustBeDylib = true; | |
| context.canBePIE = false; | |
| context.origin = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES | |
| context.rpath = NULL; | |
| image = load(path, context, cacheIndex); | |
| } | |
| catch (const char* msg) { | |
| #if TARGET_IPHONE_SIMULATOR | |
| dyld::log("dyld: warning: could not load inserted library '%s' because %s\n", path, msg); | |
| #else | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| if ( gLinkContext.processUsingLibraryValidation ) | |
| dyld::log("dyld: warning: could not load inserted library '%s' into library validated process because %s\n", path, msg); | |
| else | |
| #endif | |
| halt(dyld::mkstringf("could not load inserted library '%s' because %s\n", path, msg)); | |
| #endif | |
| } | |
| catch (...) { | |
| halt(dyld::mkstringf("could not load inserted library '%s'\n", path)); | |
| } | |
| } | |
| // | |
| // Sets: | |
| // sEnvMode | |
| // gLinkContext.requireCodeSignature | |
| // gLinkContext.processIsRestricted // Mac OS X only | |
| // gLinkContext.processUsingLibraryValidation // Mac OS X only | |
| // | |
| static void configureProcessRestrictions(const macho_header* mainExecutableMH) | |
| { | |
| uint32_t flags; | |
| #if TARGET_IPHONE_SIMULATOR | |
| sEnvMode = envAll; | |
| gLinkContext.requireCodeSignature = true; | |
| #elif __IPHONE_OS_VERSION_MIN_REQUIRED | |
| sEnvMode = envNone; | |
| gLinkContext.requireCodeSignature = true; | |
| if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) { | |
| if ( flags & CS_ENFORCEMENT ) { | |
| if ( flags & CS_GET_TASK_ALLOW ) { | |
| // Xcode built app for Debug allowed to use DYLD_* variables | |
| sEnvMode = envAll; | |
| } | |
| else { | |
| // Development kernel can use DYLD_PRINT_* variables on any FairPlay encrypted app | |
| uint32_t secureValue = 0; | |
| size_t secureValueSize = sizeof(secureValue); | |
| if ( (sysctlbyname("kern.secure_kernel", &secureValue, &secureValueSize, NULL, 0) == 0) && (secureValue == 0) && isFairPlayEncrypted(mainExecutableMH) ) { | |
| sEnvMode = envPrintOnly; | |
| } | |
| } | |
| } | |
| else { | |
| // Development kernel can run unsigned code | |
| sEnvMode = envAll; | |
| gLinkContext.requireCodeSignature = false; | |
| } | |
| } | |
| if ( issetugid() ) { | |
| sEnvMode = envNone; | |
| } | |
| #elif __MAC_OS_X_VERSION_MIN_REQUIRED | |
| sEnvMode = envAll; | |
| gLinkContext.requireCodeSignature = false; | |
| gLinkContext.processIsRestricted = false; | |
| gLinkContext.processUsingLibraryValidation = false; | |
| // any processes with setuid or setgid bit set or with __RESTRICT segment is restricted | |
| if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) { | |
| gLinkContext.processIsRestricted = true; | |
| } | |
| if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) { | |
| // On OS X CS_RESTRICT means the program was signed with entitlements | |
| if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0) ) { | |
| gLinkContext.processIsRestricted = true; | |
| } | |
| // Library Validation loosens searching but requires everything to be code signed | |
| if ( flags & CS_REQUIRE_LV ) { | |
| gLinkContext.processIsRestricted = false; | |
| //gLinkContext.requireCodeSignature = true; | |
| gLinkContext.processUsingLibraryValidation = true; | |
| } | |
| } | |
| #endif | |
| } | |
| bool processIsRestricted() | |
| { | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| return gLinkContext.processIsRestricted; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| // <rdar://problem/10583252> Add dyld to uuidArray to enable symbolication of stackshots | |
| static void addDyldImageToUUIDList() | |
| { | |
| const struct macho_header* mh = (macho_header*)&__dso_handle; | |
| const uint32_t cmd_count = mh->ncmds; | |
| const struct load_command* const cmds = (struct load_command*)((char*)mh + sizeof(macho_header)); | |
| const struct load_command* cmd = cmds; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| switch (cmd->cmd) { | |
| case LC_UUID: { | |
| uuid_command* uc = (uuid_command*)cmd; | |
| dyld_uuid_info info; | |
| info.imageLoadAddress = (mach_header*)mh; | |
| memcpy(info.imageUUID, uc->uuid, 16); | |
| addNonSharedCacheImageUUID(info); | |
| return; | |
| } | |
| } | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| } | |
| } | |
| void notifyKernelAboutDyld() | |
| { | |
| const struct macho_header* mh = (macho_header*)&__dso_handle; | |
| const uint32_t cmd_count = mh->ncmds; | |
| const struct load_command* const cmds = (struct load_command*)((char*)mh + sizeof(macho_header)); | |
| const struct load_command* cmd = cmds; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| switch (cmd->cmd) { | |
| case LC_UUID: { | |
| // Add dyld to the kernel image info | |
| uuid_command* uc = (uuid_command*)cmd; | |
| dyld_kernel_image_info_t kernelInfo; | |
| memcpy(kernelInfo.uuid, uc->uuid, 16); | |
| kernelInfo.load_addr = (uint64_t)mh; | |
| kernelInfo.fsobjid.fid_objno = 0; | |
| kernelInfo.fsobjid.fid_generation = 0; | |
| kernelInfo.fsid.val[0] = 0; | |
| kernelInfo.fsid.val[1] = 0; | |
| task_register_dyld_image_infos(mach_task_self(), &kernelInfo, 1); | |
| return; | |
| } | |
| } | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| } | |
| } | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| typedef int (*open_proc_t)(const char*, int, int); | |
| typedef int (*fcntl_proc_t)(int, int, void*); | |
| typedef int (*ioctl_proc_t)(int, unsigned long, void*); | |
| static void* getProcessInfo() { return dyld::gProcessInfo; } | |
| static SyscallHelpers sSysCalls = { | |
| 7, | |
| // added in version 1 | |
| (open_proc_t)&open, | |
| &close, | |
| &pread, | |
| &write, | |
| &mmap, | |
| &munmap, | |
| &madvise, | |
| &stat, | |
| (fcntl_proc_t)&fcntl, | |
| (ioctl_proc_t)&ioctl, | |
| &issetugid, | |
| &getcwd, | |
| &realpath, | |
| &vm_allocate, | |
| &vm_deallocate, | |
| &vm_protect, | |
| &vlog, | |
| &vwarn, | |
| &pthread_mutex_lock, | |
| &pthread_mutex_unlock, | |
| &mach_thread_self, | |
| &mach_port_deallocate, | |
| &task_self_trap, | |
| &mach_timebase_info, | |
| &OSAtomicCompareAndSwapPtrBarrier, | |
| &OSMemoryBarrier, | |
| &getProcessInfo, | |
| &__error, | |
| &mach_absolute_time, | |
| // added in version 2 | |
| &thread_switch, | |
| // added in version 3 | |
| &opendir, | |
| &readdir_r, | |
| &closedir, | |
| // added in version 4 | |
| &coresymbolication_load_notifier, | |
| &coresymbolication_unload_notifier, | |
| // Added in version 5 | |
| &proc_regionfilename, | |
| &getpid, | |
| &mach_port_insert_right, | |
| &mach_port_allocate, | |
| &mach_msg, | |
| // Added in version 6 | |
| &abort_with_payload, | |
| // Added in version 7 | |
| &task_register_dyld_image_infos, | |
| &task_unregister_dyld_image_infos, | |
| &task_get_dyld_image_infos, | |
| &task_register_dyld_shared_cache_image_info, | |
| &task_register_dyld_set_dyld_state, | |
| &task_register_dyld_get_process_state | |
| }; | |
| __attribute__((noinline)) | |
| static const char* useSimulatorDyld(int fd, const macho_header* mainExecutableMH, const char* dyldPath, | |
| int argc, const char* argv[], const char* envp[], const char* apple[], | |
| uintptr_t* startGlue, uintptr_t* mainAddr) | |
| { | |
| *startGlue = 0; | |
| *mainAddr = 0; | |
| // <rdar://problem/25311921> simulator does not support restricted processes | |
| uint32_t flags; | |
| if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) == -1 ) | |
| return "csops() failed"; | |
| if ( (flags & CS_RESTRICT) == CS_RESTRICT ) | |
| return "dyld_sim cannot be loaded in a restricted process"; | |
| if ( issetugid() ) | |
| return "dyld_sim cannot be loaded in a setuid process"; | |
| if ( hasRestrictedSegment(mainExecutableMH) ) | |
| return "dyld_sim cannot be loaded in a restricted process"; | |
| // get file size of dyld_sim | |
| struct stat sb; | |
| if ( fstat(fd, &sb) == -1 ) | |
| return "stat(dyld_sim) failed"; | |
| // read first page of dyld_sim file | |
| uint8_t firstPage[4096]; | |
| if ( pread(fd, firstPage, 4096, 0) != 4096 ) | |
| return "pread(dyld_sim) failed"; | |
| // if fat file, pick matching slice | |
| uint64_t fileOffset = 0; | |
| uint64_t fileLength = sb.st_size; | |
| const fat_header* fileStartAsFat = (fat_header*)firstPage; | |
| if ( fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) { | |
| if ( !fatFindBest(fileStartAsFat, &fileOffset, &fileLength) ) | |
| return "no matching arch in dyld_sim"; | |
| // re-read buffer from start of mach-o slice in fat file | |
| if ( pread(fd, firstPage, 4096, fileOffset) != 4096 ) | |
| return "pread(dyld_sim) failed"; | |
| } | |
| else if ( !isCompatibleMachO(firstPage, dyldPath) ) { | |
| return "dyld_sim not compatible mach-o"; | |
| } | |
| // calculate total size of dyld segments | |
| const macho_header* mh = (const macho_header*)firstPage; | |
| struct macho_segment_command* lastSeg = NULL; | |
| struct macho_segment_command* firstSeg = NULL; | |
| uintptr_t mappingSize = 0; | |
| uintptr_t preferredLoadAddress = 0; | |
| const uint32_t cmd_count = mh->ncmds; | |
| if ( mh->sizeofcmds > 4096 ) | |
| return "dyld_sim load commands to large"; | |
| if ( (sizeof(macho_header) + mh->sizeofcmds) > 4096 ) | |
| return "dyld_sim load commands to large"; | |
| const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); | |
| const struct load_command* const endCmds = (struct load_command*)(((char*)mh) + sizeof(macho_header) + mh->sizeofcmds); | |
| const struct load_command* cmd = cmds; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| uint32_t cmdLength = cmd->cmdsize; | |
| if ( cmdLength < 8 ) | |
| return "dyld_sim load command too small"; | |
| const struct load_command* const nextCmd = (const struct load_command*)(((char*)cmd)+cmdLength); | |
| if ( (nextCmd > endCmds) || (nextCmd < cmd) ) | |
| return "dyld_sim load command too large"; | |
| switch (cmd->cmd) { | |
| case LC_SEGMENT_COMMAND: | |
| { | |
| struct macho_segment_command* seg = (struct macho_segment_command*)cmd; | |
| if ( seg->vmaddr + seg->vmsize < seg->vmaddr ) | |
| return "dyld_sim seg wraps address space"; | |
| if ( seg->vmsize < seg->filesize ) | |
| return "dyld_sim seg vmsize too small"; | |
| if ( (seg->fileoff + seg->filesize) < seg->fileoff ) | |
| return "dyld_sim seg size wraps address space"; | |
| if ( lastSeg == NULL ) { | |
| // first segment must be __TEXT and start at beginning of file/slice | |
| firstSeg = seg; | |
| if ( strcmp(seg->segname, "__TEXT") != 0 ) | |
| return "dyld_sim first segment not __TEXT"; | |
| if ( seg->fileoff != 0 ) | |
| return "dyld_sim first segment not at file offset zero"; | |
| if ( seg->filesize < (sizeof(macho_header) + mh->sizeofcmds) ) | |
| return "dyld_sim first segment smaller than load commands"; | |
| preferredLoadAddress = seg->vmaddr; | |
| } | |
| else { | |
| // other sements must be continguous with previous segment and not executable | |
| if ( lastSeg->fileoff + lastSeg->filesize != seg->fileoff ) | |
| return "dyld_sim segments not contiguous"; | |
| if ( lastSeg->vmaddr + lastSeg->vmsize != seg->vmaddr ) | |
| return "dyld_sim segments not address contiguous"; | |
| if ( (seg->initprot & VM_PROT_EXECUTE) != 0 ) | |
| return "dyld_sim non-first segment is executable"; | |
| } | |
| mappingSize += seg->vmsize; | |
| lastSeg = seg; | |
| } | |
| break; | |
| case LC_SEGMENT_COMMAND_WRONG: | |
| return "dyld_sim wrong load segment load command"; | |
| } | |
| cmd = nextCmd; | |
| } | |
| // last segment must be named __LINKEDIT and not writable | |
| if ( strcmp(lastSeg->segname, "__LINKEDIT") != 0 ) | |
| return "dyld_sim last segment not __LINKEDIT"; | |
| if ( lastSeg->initprot & VM_PROT_WRITE ) | |
| return "dyld_sim __LINKEDIT segment writable"; | |
| // reserve space, then mmap each segment | |
| vm_address_t loadAddress = 0; | |
| if ( ::vm_allocate(mach_task_self(), &loadAddress, mappingSize, VM_FLAGS_ANYWHERE) != 0 ) | |
| return "dyld_sim cannot allocate space"; | |
| cmd = cmds; | |
| struct linkedit_data_command* codeSigCmd = NULL; | |
| struct source_version_command* dyldVersionCmd = NULL; | |
| for (uint32_t i = 0; i < cmd_count; ++i) { | |
| switch (cmd->cmd) { | |
| case LC_SEGMENT_COMMAND: | |
| { | |
| struct macho_segment_command* seg = (struct macho_segment_command*)cmd; | |
| uintptr_t requestedLoadAddress = seg->vmaddr - preferredLoadAddress + loadAddress; | |
| void* segAddress = ::mmap((void*)requestedLoadAddress, seg->filesize, seg->initprot, MAP_FIXED | MAP_PRIVATE, fd, fileOffset + seg->fileoff); | |
| //dyld::log("dyld_sim %s mapped at %p\n", seg->segname, segAddress); | |
| if ( segAddress == (void*)(-1) ) | |
| return "dyld_sim mmap() of segment failed"; | |
| if ( ((uintptr_t)segAddress < loadAddress) || ((uintptr_t)segAddress+seg->filesize > loadAddress+mappingSize) ) | |
| return "dyld_sim mmap() to wrong location"; | |
| } | |
| break; | |
| case LC_CODE_SIGNATURE: | |
| codeSigCmd = (struct linkedit_data_command*)cmd; | |
| break; | |
| case LC_SOURCE_VERSION: | |
| dyldVersionCmd = (struct source_version_command*)cmd; | |
| break; | |
| } | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| } | |
| // must have code signature which is contained within LINKEDIT segment | |
| if ( codeSigCmd == NULL ) | |
| return "dyld_sim not code signed"; | |
| if ( codeSigCmd->dataoff < lastSeg->fileoff ) | |
| return "dyld_sim code signature not in __LINKEDIT"; | |
| if ( (codeSigCmd->dataoff + codeSigCmd->datasize) < codeSigCmd->dataoff ) | |
| return "dyld_sim code signature size wraps"; | |
| if ( (codeSigCmd->dataoff + codeSigCmd->datasize) > (lastSeg->fileoff + lastSeg->filesize) ) | |
| return "dyld_sim code signature extends beyond __LINKEDIT"; | |
| fsignatures_t siginfo; | |
| siginfo.fs_file_start=fileOffset; // start of mach-o slice in fat file | |
| siginfo.fs_blob_start=(void*)(long)(codeSigCmd->dataoff); // start of code-signature in mach-o file | |
| siginfo.fs_blob_size=codeSigCmd->datasize; // size of code-signature | |
| int result = fcntl(fd, F_ADDFILESIGS_FOR_DYLD_SIM, &siginfo); | |
| if ( result == -1 ) { | |
| return mkstringf("dyld_sim fcntl(F_ADDFILESIGS_FOR_DYLD_SIM) failed with errno=%d", errno); | |
| } | |
| close(fd); | |
| // file range covered by code signature must extend up to code signature itself | |
| if ( siginfo.fs_file_start < codeSigCmd->dataoff ) | |
| return mkstringf("dyld_sim code signature does not cover all of dyld_sim. Signature covers up to 0x%08lX. Signature starts at 0x%08X", (unsigned long)siginfo.fs_file_start, codeSigCmd->dataoff); | |
| // walk newly mapped dyld_sim __TEXT load commands to find entry point | |
| uintptr_t entry = 0; | |
| cmd = (struct load_command*)(((char*)loadAddress)+sizeof(macho_header)); | |
| const uint32_t count = ((macho_header*)(loadAddress))->ncmds; | |
| for (uint32_t i = 0; i < count; ++i) { | |
| if (cmd->cmd == LC_UNIXTHREAD) { | |
| #if __i386__ | |
| const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16); | |
| // entry point must be in first segment | |
| if ( registers->__eip < firstSeg->vmaddr ) | |
| return "dyld_sim entry point not in __TEXT segment"; | |
| if ( registers->__eip > (firstSeg->vmaddr + firstSeg->vmsize) ) | |
| return "dyld_sim entry point not in __TEXT segment"; | |
| entry = (registers->__eip + loadAddress - preferredLoadAddress); | |
| #elif __x86_64__ | |
| const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16); | |
| // entry point must be in first segment | |
| if ( registers->__rip < firstSeg->vmaddr ) | |
| return "dyld_sim entry point not in __TEXT segment"; | |
| if ( registers->__rip > (firstSeg->vmaddr + firstSeg->vmsize) ) | |
| return "dyld_sim entry point not in __TEXT segment"; | |
| entry = (registers->__rip + loadAddress - preferredLoadAddress); | |
| #endif | |
| } | |
| cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); | |
| } | |
| // notify debugger that dyld_sim is loaded | |
| dyld_image_info info; | |
| info.imageLoadAddress = (mach_header*)loadAddress; | |
| info.imageFilePath = strdup(dyldPath); | |
| info.imageFileModDate = sb.st_mtime; | |
| addImagesToAllImages(1, &info); | |
| dyld::gProcessInfo->notification(dyld_image_adding, 1, &info); | |
| const char** appleParams = apple; | |
| // jump into new simulator dyld | |
| typedef uintptr_t (*sim_entry_proc_t)(int argc, const char* argv[], const char* envp[], const char* apple[], | |
| const macho_header* mainExecutableMH, const macho_header* dyldMH, uintptr_t dyldSlide, | |
| const dyld::SyscallHelpers* vtable, uintptr_t* startGlue); | |
| sim_entry_proc_t newDyld = (sim_entry_proc_t)entry; | |
| *mainAddr = (*newDyld)(argc, argv, envp, appleParams, mainExecutableMH, (macho_header*)loadAddress, | |
| loadAddress - preferredLoadAddress, | |
| &sSysCalls, startGlue); | |
| return NULL; | |
| } | |
| #endif | |
| // | |
| // Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which | |
| // sets up some registers and call this function. | |
| // | |
| // Returns address of main() in target program which __dyld_start jumps to | |
| // | |
| uintptr_t | |
| _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, | |
| int argc, const char* argv[], const char* envp[], const char* apple[], | |
| uintptr_t* startGlue) | |
| { | |
| uintptr_t result = 0; | |
| sMainExecutableMachHeader = mainExecutableMH; | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| // if this is host dyld, check to see if iOS simulator is being run | |
| const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH"); | |
| if ( rootPath != NULL ) { | |
| // Add dyld to the kernel image info before we jump to the sim | |
| notifyKernelAboutDyld(); | |
| // look to see if simulator has its own dyld | |
| char simDyldPath[PATH_MAX]; | |
| strlcpy(simDyldPath, rootPath, PATH_MAX); | |
| strlcat(simDyldPath, "/usr/lib/dyld_sim", PATH_MAX); | |
| int fd = my_open(simDyldPath, O_RDONLY, 0); | |
| if ( fd != -1 ) { | |
| const char* errMessage = useSimulatorDyld(fd, mainExecutableMH, simDyldPath, argc, argv, envp, apple, startGlue, &result); | |
| if ( errMessage != NULL ) | |
| halt(errMessage); | |
| return result; | |
| } | |
| } | |
| #endif | |
| CRSetCrashLogMessage("dyld: launch started"); | |
| setContext(mainExecutableMH, argc, argv, envp, apple); | |
| // Pickup the pointer to the exec path. | |
| sExecPath = _simple_getenv(apple, "executable_path"); | |
| // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld | |
| if (!sExecPath) sExecPath = apple[0]; | |
| if ( sExecPath[0] != '/' ) { | |
| // have relative path, use cwd to make absolute | |
| char cwdbuff[MAXPATHLEN]; | |
| if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) { | |
| // maybe use static buffer to avoid calling malloc so early... | |
| char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2]; | |
| strcpy(s, cwdbuff); | |
| strcat(s, "/"); | |
| strcat(s, sExecPath); | |
| sExecPath = s; | |
| } | |
| } | |
| // Remember short name of process for later logging | |
| sExecShortName = ::strrchr(sExecPath, '/'); | |
| if ( sExecShortName != NULL ) | |
| ++sExecShortName; | |
| else | |
| sExecShortName = sExecPath; | |
| configureProcessRestrictions(mainExecutableMH); | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| if ( gLinkContext.processIsRestricted ) { | |
| pruneEnvironmentVariables(envp, &apple); | |
| // set again because envp and apple may have changed or moved | |
| setContext(mainExecutableMH, argc, argv, envp, apple); | |
| } | |
| else | |
| #endif | |
| { | |
| checkEnvironmentVariables(envp); | |
| defaultUninitializedFallbackPaths(envp); | |
| } | |
| if ( sEnv.DYLD_PRINT_OPTS ) | |
| printOptions(argv); | |
| if ( sEnv.DYLD_PRINT_ENV ) | |
| printEnvironmentVariables(envp); | |
| getHostInfo(mainExecutableMH, mainExecutableSlide); | |
| // install gdb notifier | |
| stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB); | |
| stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages); | |
| // make initial allocations large enough that it is unlikely to need to be re-alloced | |
| sImageRoots.reserve(16); | |
| sAddImageCallbacks.reserve(4); | |
| sRemoveImageCallbacks.reserve(4); | |
| sImageFilesNeedingTermination.reserve(16); | |
| sImageFilesNeedingDOFUnregistration.reserve(8); | |
| #if !TARGET_IPHONE_SIMULATOR | |
| #ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE | |
| // <rdar://problem/6849505> Add gating mechanism to dyld support system order file generation process | |
| WAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag); | |
| #endif | |
| #endif | |
| try { | |
| // add dyld itself to UUID list | |
| addDyldImageToUUIDList(); | |
| notifyKernelAboutDyld(); | |
| #if SUPPORT_ACCELERATE_TABLES | |
| bool mainExcutableAlreadyRebased = false; | |
| reloadAllImages: | |
| #endif | |
| CRSetCrashLogMessage(sLoadingCrashMessage); | |
| // instantiate ImageLoader for main executable | |
| sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath); | |
| gLinkContext.mainExecutable = sMainExecutable; | |
| gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH); | |
| #if TARGET_IPHONE_SIMULATOR | |
| // check main executable is not too new for this OS | |
| { | |
| if ( ! isSimulatorBinary((uint8_t*)mainExecutableMH, sExecPath) ) { | |
| throwf("program was built for a platform that is not supported by this runtime"); | |
| } | |
| uint32_t mainMinOS = sMainExecutable->minOSVersion(); | |
| // dyld is always built for the current OS, so we can get the current OS version | |
| // from the load command in dyld itself. | |
| uint32_t dyldMinOS = ImageLoaderMachO::minOSVersion((const mach_header*)&__dso_handle); | |
| if ( mainMinOS > dyldMinOS ) { | |
| #if TARGET_OS_WATCH | |
| throwf("app was built for watchOS %d.%d which is newer than this simulator %d.%d", | |
| mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF), | |
| dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF)); | |
| #elif TARGET_OS_TV | |
| throwf("app was built for tvOS %d.%d which is newer than this simulator %d.%d", | |
| mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF), | |
| dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF)); | |
| #else | |
| throwf("app was built for iOS %d.%d which is newer than this simulator %d.%d", | |
| mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF), | |
| dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF)); | |
| #endif | |
| } | |
| } | |
| #endif | |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED | |
| // <rdar://problem/22805519> be less strict about old mach-o binaries | |
| uint32_t mainSDK = sMainExecutable->sdkVersion(); | |
| gLinkContext.strictMachORequired = (mainSDK >= DYLD_MACOSX_VERSION_10_12) || gLinkContext.processUsingLibraryValidation; | |
| #else | |
| // simulators, iOS, tvOS, and watchOS are always strict | |
| gLinkContext.strictMachORequired = true; | |
| #endif | |
| // load shared cache | |
| checkSharedRegionDisable(); | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) { | |
| mapSharedCache(); | |
| } else { | |
| dyld_kernel_image_info_t kernelCacheInfo; | |
| bzero(&kernelCacheInfo.uuid[0], sizeof(uuid_t)); | |
| kernelCacheInfo.load_addr = 0; | |
| kernelCacheInfo.fsobjid.fid_objno = 0; | |
| kernelCacheInfo.fsobjid.fid_generation = 0; | |
| kernelCacheInfo.fsid.val[0] = 0; | |
| kernelCacheInfo.fsid.val[0] = 0; | |
| task_register_dyld_shared_cache_image_info(mach_task_self(), kernelCacheInfo, true, false); | |
| } | |
| #endif | |
| #if SUPPORT_ACCELERATE_TABLES | |
| sAllImages.reserve((sAllCacheImagesProxy != NULL) ? 16 : INITIAL_IMAGE_COUNT); | |
| #else | |
| sAllImages.reserve(INITIAL_IMAGE_COUNT); | |
| #endif | |
| // Now that shared cache is loaded, setup an versioned dylib overrides | |
| #if SUPPORT_VERSIONED_PATHS | |
| checkVersionedPaths(); | |
| #endif | |
| // dyld_all_image_infos image list does not contain dyld | |
| // add it as dyldPath field in dyld_all_image_infos | |
| // for simulator, dyld_sim is in image list, need host dyld added | |
| #if TARGET_IPHONE_SIMULATOR | |
| // get path of host dyld from table of syscall vectors in host dyld | |
| void* addressInDyld = gSyscallHelpers; | |
| #else | |
| // get path of dyld itself | |
| void* addressInDyld = (void*)&__dso_handle; | |
| #endif | |
| char dyldPathBuffer[MAXPATHLEN+1]; | |
| int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN); | |
| if ( (len != 0) && (strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0) ) { | |
| gProcessInfo->dyldPath = strdup(dyldPathBuffer); | |
| } | |
| // load any inserted libraries | |
| if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { | |
| for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) | |
| loadInsertedDylib(*lib); | |
| } | |
| // record count of inserted libraries so that a flat search will look at | |
| // inserted libraries, then main, then others. | |
| sInsertedDylibCount = sAllImages.size()-1; | |
| // link main executable | |
| gLinkContext.linkingMainExecutable = true; | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( mainExcutableAlreadyRebased ) { | |
| // previous link() on main executable has already adjusted its internal pointers for ASLR | |
| // work around that by rebasing by inverse amount | |
| sMainExecutable->rebase(gLinkContext, -mainExecutableSlide); | |
| } | |
| #endif | |
| link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1); | |
| sMainExecutable->setNeverUnloadRecursive(); | |
| if ( sMainExecutable->forceFlat() ) { | |
| gLinkContext.bindFlat = true; | |
| gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding; | |
| } | |
| // link any inserted libraries | |
| // do this after linking main executable so that any dylibs pulled in by inserted | |
| // dylibs (e.g. libSystem) will not be in front of dylibs the program uses | |
| if ( sInsertedDylibCount > 0 ) { | |
| for(unsigned int i=0; i < sInsertedDylibCount; ++i) { | |
| ImageLoader* image = sAllImages[i+1]; | |
| link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1); | |
| image->setNeverUnloadRecursive(); | |
| } | |
| // only INSERTED libraries can interpose | |
| // register interposing info after all inserted libraries are bound so chaining works | |
| for(unsigned int i=0; i < sInsertedDylibCount; ++i) { | |
| ImageLoader* image = sAllImages[i+1]; | |
| image->registerInterposing(); | |
| } | |
| } | |
| // <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES | |
| for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) { | |
| ImageLoader* image = sAllImages[i]; | |
| if ( image->inSharedCache() ) | |
| continue; | |
| image->registerInterposing(); | |
| } | |
| #if SUPPORT_ACCELERATE_TABLES | |
| if ( (sAllCacheImagesProxy != NULL) && ImageLoader::haveInterposingTuples() ) { | |
| // Accelerator tables cannot be used with implicit interposing, so relaunch with accelerator tables disabled | |
| ImageLoader::clearInterposingTuples(); | |
| // unmap all loaded dylibs (but not main executable) | |
| for (long i=1; i < sAllImages.size(); ++i) { | |
| ImageLoader* image = sAllImages[i]; | |
| if ( image == sMainExecutable ) | |
| continue; | |
| if ( image == sAllCacheImagesProxy ) | |
| continue; | |
| image->setCanUnload(); | |
| ImageLoader::deleteImage(image); | |
| } | |
| // note: we don't need to worry about inserted images because if DYLD_INSERT_LIBRARIES was set we would not be using the accelerator table | |
| sAllImages.clear(); | |
| sImageRoots.clear(); | |
| sImageFilesNeedingTermination.clear(); | |
| sImageFilesNeedingDOFUnregistration.clear(); | |
| sAddImageCallbacks.clear(); | |
| sRemoveImageCallbacks.clear(); | |
| sDisableAcceleratorTables = true; | |
| sAllCacheImagesProxy = NULL; | |
| sMappedRangesStart = NULL; | |
| mainExcutableAlreadyRebased = true; | |
| gLinkContext.linkingMainExecutable = false; | |
| resetAllImages(); | |
| goto reloadAllImages; | |
| } | |
| #endif | |
| // apply interposing to initial set of images | |
| for(int i=0; i < sImageRoots.size(); ++i) { | |
| sImageRoots[i]->applyInterposing(gLinkContext); | |
| } | |
| gLinkContext.linkingMainExecutable = false; | |
| // <rdar://problem/12186933> do weak binding only after all inserted images linked | |
| sMainExecutable->weakBind(gLinkContext); | |
| #if DYLD_SHARED_CACHE_SUPPORT | |
| // If cache has branch island dylibs, tell debugger about them | |
| if ( (sSharedCache != NULL) && (sSharedCache->mappingOffset >= 0x78) && (sSharedCache->branchPoolsOffset != 0) ) { | |
| uint32_t count = sSharedCache->branchPoolsCount; | |
| dyld_image_info info[count]; | |
| const uint64_t* poolAddress = (uint64_t*)((char*)sSharedCache + sSharedCache->branchPoolsOffset); | |
| // <rdar://problem/20799203> empty branch pools can be in development cache | |
| if ( ((mach_header*)poolAddress)->magic == sMainExecutableMachHeader->magic ) { | |
| for (int poolIndex=0; poolIndex < count; ++poolIndex) { | |
| uint64_t poolAddr = poolAddress[poolIndex] + sSharedCacheSlide; | |
| info[poolIndex].imageLoadAddress = (mach_header*)(long)poolAddr; | |
| info[poolIndex].imageFilePath = "dyld_shared_cache_branch_islands"; | |
| info[poolIndex].imageFileModDate = 0; | |
| } | |
| // add to all_images list | |
| addImagesToAllImages(count, info); | |
| // tell gdb about new branch island images | |
| gProcessInfo->notification(dyld_image_adding, count, info); | |
| } | |
| } | |
| #endif | |
| CRSetCrashLogMessage("dyld: launch, running initializers"); | |
| #if SUPPORT_OLD_CRT_INITIALIZATION | |
| // Old way is to run initializers via a callback from crt1.o | |
| if ( ! gRunInitializersOldWay ) | |
| initializeMainExecutable(); | |
| #else | |
| // run all initializers | |
| initializeMainExecutable(); | |
| #endif | |
| // find entry point for main executable | |
| result = (uintptr_t)sMainExecutable->getThreadPC(); | |
| if ( result != 0 ) { | |
| // main executable uses LC_MAIN, needs to return to glue in libdyld.dylib | |
| if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) ) | |
| *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit; | |
| else | |
| halt("libdyld.dylib support not present for LC_MAIN"); | |
| } | |
| else { | |
| // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main() | |
| result = (uintptr_t)sMainExecutable->getMain(); | |
| *startGlue = 0; | |
| } | |
| } | |
| catch(const char* message) { | |
| syncAllImages(); | |
| halt(message); | |
| } | |
| catch(...) { | |
| dyld::log("dyld: launch failed\n"); | |
| } | |
| CRSetCrashLogMessage(NULL); | |
| return result; | |
| } | |
| } // namespace | |