X-Git-Url: https://git.saurik.com/apple/dyld.git/blobdiff_plain/d6f5f96dd73a9fc1307a46119d767ff3261f0f9c..4a1021af038d99f6d416c0003e1a3f2c6027faac:/src/ImageLoaderMachO.cpp diff --git a/src/ImageLoaderMachO.cpp b/src/ImageLoaderMachO.cpp index 3328e89..d552ef3 100644 --- a/src/ImageLoaderMachO.cpp +++ b/src/ImageLoaderMachO.cpp @@ -1,6 +1,6 @@ /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- * - * Copyright (c) 2004-2005 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2004-2010 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -22,133 +22,311 @@ * @APPLE_LICENSE_HEADER_END@ */ +// work around until conformance work is complete rdar://problem/4508801 +#define __srr0 srr0 +#define __eip eip +#define __rip rip + +#define __STDC_LIMIT_MACROS #include #include +#include #include +#include #include #include -#include #include #include #include -#include #include #include -#if __ppc__ || __ppc64__ - #include -#endif +#include +#include +#include +#include +#include #include "ImageLoaderMachO.h" -#include "mach-o/dyld_gdb.h" +#include "ImageLoaderMachOCompressed.h" +#if SUPPORT_CLASSIC_MACHO +#include "ImageLoaderMachOClassic.h" +#endif +#include "mach-o/dyld_images.h" -// no header for this yet, rdar://problem/3850825 -extern "C" void sys_icache_invalidate(void *, size_t); +// use stack guard random value to add padding between dylibs +extern "C" long __stack_chk_guard; -// optimize strcmp for ppc -#if __ppc__ - #include -#else - #define astrcmp(a,b) strcmp(a,b) +#ifndef LC_LOAD_UPWARD_DYLIB + #define LC_LOAD_UPWARD_DYLIB (0x23|LC_REQ_DYLD) /* load of dylib whose initializers run later */ +#endif + +#ifndef LC_VERSION_MIN_TVOS + #define LC_VERSION_MIN_TVOS 0x2F +#endif + +#ifndef LC_VERSION_MIN_WATCHOS + #define LC_VERSION_MIN_WATCHOS 0x30 #endif + // relocation_info.r_length field has value 3 for 64-bit executables and value 2 for 32-bit executables #if __LP64__ - #define RELOC_SIZE 3 #define LC_SEGMENT_COMMAND LC_SEGMENT_64 #define LC_ROUTINES_COMMAND LC_ROUTINES_64 - struct macho_header : public mach_header_64 {}; + #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT struct macho_segment_command : public segment_command_64 {}; struct macho_section : public section_64 {}; - struct macho_nlist : public nlist_64 {}; struct macho_routines_command : public routines_command_64 {}; #else - #define RELOC_SIZE 2 #define LC_SEGMENT_COMMAND LC_SEGMENT #define LC_ROUTINES_COMMAND LC_ROUTINES - struct macho_header : public mach_header {}; + #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64 struct macho_segment_command : public segment_command {}; struct macho_section : public section {}; - struct macho_nlist : public nlist {}; struct macho_routines_command : public routines_command {}; #endif +uint32_t ImageLoaderMachO::fgSymbolTableBinarySearchs = 0; +uint32_t ImageLoaderMachO::fgSymbolTrieSearchs = 0; + -uint32_t ImageLoaderMachO::fgHintedBinaryTreeSearchs = 0; -uint32_t ImageLoaderMachO::fgUnhintedBinaryTreeSearchs = 0; +ImageLoaderMachO::ImageLoaderMachO(const macho_header* mh, const char* path, unsigned int segCount, + uint32_t segOffsets[], unsigned int libCount) + : ImageLoader(path, libCount), fCoveredCodeLength(0), fMachOData((uint8_t*)mh), fLinkEditBase(NULL), fSlide(0), + fEHFrameSectionOffset(0), fUnwindInfoSectionOffset(0), fDylibIDOffset(0), +fSegmentsCount(segCount), fIsSplitSeg(false), fInSharedCache(false), +#if TEXT_RELOC_SUPPORT + fTextSegmentRebases(false), + fTextSegmentBinds(false), +#endif +#if __i386__ + fReadOnlyImportSegment(false), +#endif + fHasSubLibraries(false), fHasSubUmbrella(false), fInUmbrella(false), fHasDOFSections(false), fHasDashInit(false), + fHasInitializers(false), fHasTerminators(false), fRegisteredAsRequiresCoalescing(false) +{ + fIsSplitSeg = ((mh->flags & MH_SPLIT_SEGS) != 0); + // construct SegmentMachO object for each LC_SEGMENT cmd using "placement new" to put + // each SegmentMachO object in array at end of ImageLoaderMachO object + const uint32_t cmd_count = mh->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + for (uint32_t i = 0, segIndex=0; i < cmd_count; ++i) { + if ( cmd->cmd == LC_SEGMENT_COMMAND ) { + const struct macho_segment_command* segCmd = (struct macho_segment_command*)cmd; + // ignore zero-sized segments + if ( segCmd->vmsize != 0 ) { + // record offset of load command + segOffsets[segIndex++] = (uint32_t)((uint8_t*)segCmd - fMachOData); + } + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } -//#define LINKEDIT_USAGE_DEBUG 1 +} -#if LINKEDIT_USAGE_DEBUG - #include - static std::set sLinkEditPageBuckets; - namespace dyld { - extern ImageLoader* findImageContainingAddress(const void* addr); - }; +// determine if this mach-o file has classic or compressed LINKEDIT and number of segments it has +void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool inCache, bool* compressed, + unsigned int* segCount, unsigned int* libCount, const LinkContext& context, + const linkedit_data_command** codeSigCmd, + const encryption_info_command** encryptCmd) +{ + *compressed = false; + *segCount = 0; + *libCount = 0; + *codeSigCmd = NULL; + *encryptCmd = NULL; - static void noteAccessedLinkEditAddress(const void* addr) - { - uintptr_t page = ((uintptr_t)addr) & (-4096); - sLinkEditPageBuckets.insert(page); - fprintf(stderr, "dyld: accessing page 0x%08lX in __LINKEDIT of %s\n", page, dyld::findImageContainingAddress(addr)->getPath()); - } + const uint32_t cmd_count = mh->ncmds; + const struct load_command* const startCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header)); + const struct load_command* const endCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header) + mh->sizeofcmds); + const struct load_command* cmd = startCmds; + bool foundLoadCommandSegment = false; + for (uint32_t i = 0; i < cmd_count; ++i) { + uint32_t cmdLength = cmd->cmdsize; + struct macho_segment_command* segCmd; + if ( cmdLength < 8 ) { + dyld::throwf("malformed mach-o image: load command #%d length (%u) too small in %s", + i, cmdLength, path); + } + const struct load_command* const nextCmd = (const struct load_command*)(((char*)cmd)+cmdLength); + if ( (nextCmd > endCmds) || (nextCmd < cmd) ) { + dyld::throwf("malformed mach-o image: load command #%d length (%u) would exceed sizeofcmds (%u) in %s", + i, cmdLength, mh->sizeofcmds, path); + } + switch (cmd->cmd) { + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + *compressed = true; + break; + case LC_SEGMENT_COMMAND: + segCmd = (struct macho_segment_command*)cmd; +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // rdar://problem/19617624 allow unmapped segments on OSX (but not iOS) + if ( (segCmd->filesize > segCmd->vmsize) && (segCmd->vmsize != 0) ) +#else + if ( segCmd->filesize > segCmd->vmsize ) #endif + dyld::throwf("malformed mach-o image: segment load command %s filesize is larger than vmsize", segCmd->segname); + // ignore zero-sized segments + if ( segCmd->vmsize != 0 ) + *segCount += 1; + if ( context.codeSigningEnforced ) { + uintptr_t vmStart = segCmd->vmaddr; + uintptr_t vmSize = segCmd->vmsize; + uintptr_t vmEnd = vmStart + vmSize; + uintptr_t fileStart = segCmd->fileoff; + uintptr_t fileSize = segCmd->filesize; + if ( (intptr_t)(vmEnd) < 0) + dyld::throwf("malformed mach-o image: segment load command %s vmsize too large", segCmd->segname); + if ( vmStart > vmEnd ) + dyld::throwf("malformed mach-o image: segment load command %s wraps around address space", segCmd->segname); + if ( vmSize != fileSize ) { + if ( (segCmd->initprot == 0) && (fileSize != 0) ) + dyld::throwf("malformed mach-o image: unaccessable segment %s has filesize != 0", segCmd->segname); + else if ( vmSize < fileSize ) + dyld::throwf("malformed mach-o image: segment %s has vmsize < filesize", segCmd->segname); + } + if ( inCache ) { + if ( (fileSize != 0) && (segCmd->initprot == (VM_PROT_READ | VM_PROT_EXECUTE)) ) { + if ( foundLoadCommandSegment ) + throw "load commands in multiple segments"; + foundLoadCommandSegment = true; + } + } + else if ( (fileStart < mh->sizeofcmds) && (fileSize != 0) ) { + // all load commands must be in an executable segment + if ( (fileStart != 0) || (fileSize < (mh->sizeofcmds+sizeof(macho_header))) ) + dyld::throwf("malformed mach-o image: segment %s does not span all load commands", segCmd->segname); + if ( segCmd->initprot != (VM_PROT_READ | VM_PROT_EXECUTE) ) + dyld::throwf("malformed mach-o image: load commands found in segment %s with wrong permissions", segCmd->segname); + if ( foundLoadCommandSegment ) + throw "load commands in multiple segments"; + foundLoadCommandSegment = true; + } + + const struct macho_section* const sectionsStart = (struct macho_section*)((char*)segCmd + sizeof(struct macho_segment_command)); + const struct macho_section* const sectionsEnd = §ionsStart[segCmd->nsects]; + for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { + if (!inCache && sect->offset != 0 && ((sect->offset + sect->size) > (segCmd->fileoff + segCmd->filesize))) + dyld::throwf("malformed mach-o image: section %s,%s of '%s' exceeds segment %s booundary", sect->segname, sect->sectname, path, segCmd->segname); + } + } + break; + case LC_SEGMENT_COMMAND_WRONG: + dyld::throwf("malformed mach-o image: wrong LC_SEGMENT[_64] for architecture"); + break; + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + *libCount += 1; + break; + case LC_CODE_SIGNATURE: + *codeSigCmd = (struct linkedit_data_command*)cmd; // only support one LC_CODE_SIGNATURE per image + break; + case LC_ENCRYPTION_INFO: + case LC_ENCRYPTION_INFO_64: + *encryptCmd = (struct encryption_info_command*)cmd; // only support one LC_ENCRYPTION_INFO[_64] per image + break; + } + cmd = nextCmd; + } + + if ( context.codeSigningEnforced && !foundLoadCommandSegment ) + throw "load commands not in a segment"; + + // verify every segment does not overlap another segment + if ( context.codeSigningEnforced ) { + uintptr_t lastFileStart = 0; + uintptr_t linkeditFileStart = 0; + const struct load_command* cmd1 = startCmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd1->cmd == LC_SEGMENT_COMMAND ) { + struct macho_segment_command* segCmd1 = (struct macho_segment_command*)cmd1; + uintptr_t vmStart1 = segCmd1->vmaddr; + uintptr_t vmEnd1 = segCmd1->vmaddr + segCmd1->vmsize; + uintptr_t fileStart1 = segCmd1->fileoff; + uintptr_t fileEnd1 = segCmd1->fileoff + segCmd1->filesize; + + if (fileStart1 > lastFileStart) + lastFileStart = fileStart1; + + if ( strcmp(&segCmd1->segname[0], "__LINKEDIT") == 0 ) { + linkeditFileStart = fileStart1; + } + + const struct load_command* cmd2 = startCmds; + for (uint32_t j = 0; j < cmd_count; ++j) { + if ( cmd2 == cmd1 ) + continue; + if ( cmd2->cmd == LC_SEGMENT_COMMAND ) { + struct macho_segment_command* segCmd2 = (struct macho_segment_command*)cmd2; + uintptr_t vmStart2 = segCmd2->vmaddr; + uintptr_t vmEnd2 = segCmd2->vmaddr + segCmd2->vmsize; + uintptr_t fileStart2 = segCmd2->fileoff; + uintptr_t fileEnd2 = segCmd2->fileoff + segCmd2->filesize; + if ( ((vmStart2 <= vmStart1) && (vmEnd2 > vmStart1) && (vmEnd1 > vmStart1)) + || ((vmStart2 >= vmStart1) && (vmStart2 < vmEnd1) && (vmEnd2 > vmStart2)) ) + dyld::throwf("malformed mach-o image: segment %s vm overlaps segment %s", segCmd1->segname, segCmd2->segname); + if ( ((fileStart2 <= fileStart1) && (fileEnd2 > fileStart1) && (fileEnd1 > fileStart1)) + || ((fileStart2 >= fileStart1) && (fileStart2 < fileEnd1) && (fileEnd2 > fileStart2)) ) + dyld::throwf("malformed mach-o image: segment %s file content overlaps segment %s", segCmd1->segname, segCmd2->segname); + } + cmd2 = (const struct load_command*)(((char*)cmd2)+cmd2->cmdsize); + } + } + cmd1 = (const struct load_command*)(((char*)cmd1)+cmd1->cmdsize); + } + + if (lastFileStart != linkeditFileStart) + dyld::throwf("malformed mach-o image: __LINKEDIT must be last segment"); + } + + // fSegmentsArrayCount is only 8-bits + if ( *segCount > 255 ) + dyld::throwf("malformed mach-o image: more than 255 segments in %s", path); -// only way to share initialization in C++ -void ImageLoaderMachO::init() -{ - fMachOData = NULL; - fLinkEditBase = NULL; - fSymbolTable = NULL; - fStrings = NULL; - fDynamicInfo = NULL; - fSlide = 0; - fIsSplitSeg = false; - fHasSubLibraries= false; - fHasSubUmbrella = false; - fDashInit = NULL; - fModInitSection = NULL; - fModTermSection = NULL; - fDATAdyld = NULL; - fImageNotifySection = NULL; - fTwoLevelHints = NULL; - fDylibID = NULL; - fReExportThruFramework = NULL; - fTextSegmentWithFixups = NULL; + // fSegmentsArrayCount is only 8-bits + if ( *libCount > 4095 ) + dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path); + + if ( needsAddedLibSystemDepency(*libCount, mh) ) + *libCount = 1; } -// create image by copying an in-memory mach-o file -ImageLoaderMachO::ImageLoaderMachO(const char* moduleName, const struct mach_header* mh, uint64_t len, const LinkContext& context) - : ImageLoader(moduleName) -{ - // clean slate - this->init(); - // temporary use this buffer until TEXT is mapped in - fMachOData = (const uint8_t*)mh; - // create segments - this->instantiateSegments((const uint8_t*)mh); - - // map segments - if ( mh->filetype != MH_EXECUTE ) - ImageLoader::mapSegments((const void*)mh, len, context); - - // get pointers to interesting things - this->parseLoadCmds(); +// create image for main executable +ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context) +{ + //dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n", + // sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed)); + bool compressed; + unsigned int segCount; + unsigned int libCount; + const linkedit_data_command* codeSigCmd; + const encryption_info_command* encryptCmd; + sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd); + // instantiate concrete class based on content of load commands + if ( compressed ) + return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context); + else +#if SUPPORT_CLASSIC_MACHO + return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context); +#else + throw "missing LC_DYLD_INFO load command"; +#endif } // create image by mapping in a mach-o file -ImageLoaderMachO::ImageLoaderMachO(const char* path, int fd, const uint8_t firstPage[4096], uint64_t offsetInFat, +ImageLoader* ImageLoaderMachO::instantiateFromFile(const char* path, int fd, const uint8_t firstPage[4096], uint64_t offsetInFat, uint64_t lenInFat, const struct stat& info, const LinkContext& context) - : ImageLoader(path, offsetInFat, info) -{ - // clean slate - this->init(); - - // read load commands +{ + // get load commands const unsigned int dataSize = sizeof(macho_header) + ((macho_header*)firstPage)->sizeofcmds; uint8_t buffer[dataSize]; const uint8_t* fileData = firstPage; @@ -158,42 +336,430 @@ ImageLoaderMachO::ImageLoaderMachO(const char* path, int fd, const uint8_t first memcpy(buffer, firstPage, 4096); pread(fd, &buffer[4096], dataSize-4096, offsetInFat+4096); } + + bool compressed; + unsigned int segCount; + unsigned int libCount; + const linkedit_data_command* codeSigCmd; + const encryption_info_command* encryptCmd; + sniffLoadCommands((const macho_header*)fileData, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd); + // instantiate concrete class based on content of load commands + if ( compressed ) + return ImageLoaderMachOCompressed::instantiateFromFile(path, fd, fileData, dataSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, encryptCmd, context); + else +#if SUPPORT_CLASSIC_MACHO + return ImageLoaderMachOClassic::instantiateFromFile(path, fd, fileData, dataSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, context); +#else + throw "missing LC_DYLD_INFO load command"; +#endif +} + +// create image by using cached mach-o file +ImageLoader* ImageLoaderMachO::instantiateFromCache(const macho_header* mh, const char* path, long slide, const struct stat& info, const LinkContext& context) +{ + // instantiate right concrete class + bool compressed; + unsigned int segCount; + unsigned int libCount; + const linkedit_data_command* codeSigCmd; + const encryption_info_command* encryptCmd; + sniffLoadCommands(mh, path, true, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd); + // instantiate concrete class based on content of load commands + if ( compressed ) + return ImageLoaderMachOCompressed::instantiateFromCache(mh, path, slide, info, segCount, libCount, context); + else +#if SUPPORT_CLASSIC_MACHO + return ImageLoaderMachOClassic::instantiateFromCache(mh, path, slide, info, segCount, libCount, context); +#else + throw "missing LC_DYLD_INFO load command"; +#endif +} + +// create image by copying an in-memory mach-o file +ImageLoader* ImageLoaderMachO::instantiateFromMemory(const char* moduleName, const macho_header* mh, uint64_t len, const LinkContext& context) +{ + bool compressed; + unsigned int segCount; + unsigned int libCount; + const linkedit_data_command* sigcmd; + const encryption_info_command* encryptCmd; + sniffLoadCommands(mh, moduleName, false, &compressed, &segCount, &libCount, context, &sigcmd, &encryptCmd); + // instantiate concrete class based on content of load commands + if ( compressed ) + return ImageLoaderMachOCompressed::instantiateFromMemory(moduleName, mh, len, segCount, libCount, context); + else +#if SUPPORT_CLASSIC_MACHO + return ImageLoaderMachOClassic::instantiateFromMemory(moduleName, mh, len, segCount, libCount, context); +#else + throw "missing LC_DYLD_INFO load command"; +#endif +} + + +int ImageLoaderMachO::crashIfInvalidCodeSignature() +{ + // Now that segments are mapped in, try reading from first executable segment. + // If code signing is enabled the kernel will validate the code signature + // when paging in, and kill the process if invalid. + for(unsigned int i=0; i < fSegmentsCount; ++i) { + if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) { + // return read value to ensure compiler does not optimize away load + int* p = (int*)segActualLoadAddress(i); + return *p; + } + } + return 0; +} + + +void ImageLoaderMachO::parseLoadCmds(const LinkContext& context) +{ + // now that segments are mapped in, get real fMachOData, fLinkEditBase, and fSlide + for(unsigned int i=0; i < fSegmentsCount; ++i) { + // set up pointer to __LINKEDIT segment + if ( strcmp(segName(i),"__LINKEDIT") == 0 ) { + if ( context.codeSigningEnforced && (segFileOffset(i) > fCoveredCodeLength)) + dyld::throwf("cannot load '%s' (segment outside of code signature)", this->getShortName()); + fLinkEditBase = (uint8_t*)(segActualLoadAddress(i) - segFileOffset(i)); + } +#if TEXT_RELOC_SUPPORT + // __TEXT segment always starts at beginning of file and contains mach_header and load commands + if ( segExecutable(i) ) { + if ( segHasRebaseFixUps(i) && (fSlide != 0) ) + fTextSegmentRebases = true; + if ( segHasBindFixUps(i) ) + fTextSegmentBinds = true; + } +#endif +#if __i386__ + if ( segIsReadOnlyImport(i) ) + fReadOnlyImportSegment = true; +#endif + // some segment always starts at beginning of file and contains mach_header and load commands + if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) { + fMachOData = (uint8_t*)(segActualLoadAddress(i)); + } + } - // temporary use this buffer until TEXT is mapped in - fMachOData = fileData; + // keep count of prebound images with weak exports + if ( this->participatesInCoalescing() ) { + ++fgImagesRequiringCoalescing; + fRegisteredAsRequiresCoalescing = true; + if ( this->hasCoalescedExports() ) + ++fgImagesHasWeakDefinitions; + } + + // keep count of images used in shared cache + if ( fInSharedCache ) + ++fgImagesUsedFromSharedCache; + + // walk load commands (mapped in at start of __TEXT segment) + const dyld_info_command* dyldInfo = NULL; + const macho_nlist* symbolTable = NULL; + const char* symbolTableStrings = NULL; + const struct load_command* firstUnknownCmd = NULL; + const struct version_min_command* minOSVersionCmd = NULL; + const dysymtab_command* dynSymbolTable = NULL; + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SYMTAB: + { + const struct symtab_command* symtab = (struct symtab_command*)cmd; + symbolTableStrings = (const char*)&fLinkEditBase[symtab->stroff]; + symbolTable = (macho_nlist*)(&fLinkEditBase[symtab->symoff]); + } + break; + case LC_DYSYMTAB: + dynSymbolTable = (struct dysymtab_command*)cmd; + break; + case LC_SUB_UMBRELLA: + fHasSubUmbrella = true; + break; + case LC_SUB_FRAMEWORK: + fInUmbrella = true; + break; + case LC_SUB_LIBRARY: + fHasSubLibraries = true; + break; + case LC_ROUTINES_COMMAND: + fHasDashInit = true; + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + dyldInfo = (struct dyld_info_command*)cmd; + break; + case LC_SEGMENT_COMMAND: + { + const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; + const bool isTextSeg = (strcmp(seg->segname, "__TEXT") == 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) { + const uint8_t type = sect->flags & SECTION_TYPE; + if ( type == S_MOD_INIT_FUNC_POINTERS ) + fHasInitializers = true; + else if ( type == S_MOD_TERM_FUNC_POINTERS ) + fHasTerminators = true; + else if ( type == S_DTRACE_DOF ) + fHasDOFSections = true; + else if ( isTextSeg && (strcmp(sect->sectname, "__eh_frame") == 0) ) + fEHFrameSectionOffset = (uint32_t)((uint8_t*)sect - fMachOData); + else if ( isTextSeg && (strcmp(sect->sectname, "__unwind_info") == 0) ) + fUnwindInfoSectionOffset = (uint32_t)((uint8_t*)sect - fMachOData); + } + } + break; + case LC_TWOLEVEL_HINTS: + // no longer supported + break; + case LC_ID_DYLIB: + { + fDylibIDOffset = (uint32_t)((uint8_t*)cmd - fMachOData); + } + break; + case LC_RPATH: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + case LC_MAIN: + // do nothing, just prevent LC_REQ_DYLD exception from occuring + break; + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: + minOSVersionCmd = (version_min_command*)cmd; + break; + default: + if ( (cmd->cmd & LC_REQ_DYLD) != 0 ) { + if ( firstUnknownCmd == NULL ) + firstUnknownCmd = cmd; + } + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + if ( firstUnknownCmd != NULL ) { + if ( minOSVersionCmd != NULL ) { + dyld::throwf("cannot load '%s' because it was built for OS version %u.%u (load command 0x%08X is unknown)", + this->getShortName(), + minOSVersionCmd->version >> 16, ((minOSVersionCmd->version >> 8) & 0xff), + firstUnknownCmd->cmd); + } + else { + dyld::throwf("cannot load '%s' (load command 0x%08X is unknown)", this->getShortName(), firstUnknownCmd->cmd); + } + } - // the meaning of many fields changes in split seg mach-o files - fIsSplitSeg = ((((macho_header*)fileData)->flags & MH_SPLIT_SEGS) != 0) && (((macho_header*)fileData)->filetype == MH_DYLIB); - // create segments - this->instantiateSegments(fileData); + if ( dyldInfo != NULL ) + this->setDyldInfo(dyldInfo); + if ( symbolTable != NULL) + this->setSymbolTableInfo(symbolTable, symbolTableStrings, dynSymbolTable); - // map segments, except for main executable which is already mapped in by kernel - if ( ((macho_header*)fileData)->filetype != MH_EXECUTE ) - this->mapSegments(fd, offsetInFat, lenInFat, info.st_size, context); - - // get pointers to interesting things - this->parseLoadCmds(); } +// don't do this work in destructor because we need object to be full subclass +// for UnmapSegments() to work +void ImageLoaderMachO::destroy() +{ + // update count of images with weak exports + if ( fRegisteredAsRequiresCoalescing ) { + --fgImagesRequiringCoalescing; + if ( this->hasCoalescedExports() ) + --fgImagesHasWeakDefinitions; + } + // keep count of images used in shared cache + if ( fInSharedCache ) + --fgImagesUsedFromSharedCache; + + // unmap image when done + UnmapSegments(); +} -void ImageLoaderMachO::instantiateSegments(const uint8_t* fileData) +unsigned int ImageLoaderMachO::segmentCount() const { - const uint32_t cmd_count = ((macho_header*)fileData)->ncmds; - const struct load_command* const cmds = (struct load_command*)&fileData[sizeof(macho_header)]; + return fSegmentsCount; +} - // construct Segment object for each LC_SEGMENT cmd and add to list - const struct load_command* cmd = cmds; - for (unsigned long i = 0; i < cmd_count; ++i) { - if ( cmd->cmd == LC_SEGMENT_COMMAND ) { - fSegments.push_back(new SegmentMachO((struct macho_segment_command*)cmd, this, fileData)); - } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + +const macho_segment_command* ImageLoaderMachO::segLoadCommand(unsigned int segIndex) const +{ + uint32_t* lcOffsets = this->segmentCommandOffsets(); + uint32_t lcOffset = lcOffsets[segIndex]; + return (macho_segment_command*)(&fMachOData[lcOffset]); +} + +const char* ImageLoaderMachO::segName(unsigned int segIndex) const +{ + return segLoadCommand(segIndex)->segname; +} + + +uintptr_t ImageLoaderMachO::segSize(unsigned int segIndex) const +{ + return segLoadCommand(segIndex)->vmsize; +} + + +uintptr_t ImageLoaderMachO::segFileSize(unsigned int segIndex) const +{ + return segLoadCommand(segIndex)->filesize; +} + + +bool ImageLoaderMachO::segHasTrailingZeroFill(unsigned int segIndex) +{ + return ( segWriteable(segIndex) && (segSize(segIndex) > segFileSize(segIndex)) ); +} + + +uintptr_t ImageLoaderMachO::segFileOffset(unsigned int segIndex) const +{ + return segLoadCommand(segIndex)->fileoff; +} + + +bool ImageLoaderMachO::segReadable(unsigned int segIndex) const +{ + return ( (segLoadCommand(segIndex)->initprot & VM_PROT_READ) != 0); +} + + +bool ImageLoaderMachO::segWriteable(unsigned int segIndex) const +{ + return ( (segLoadCommand(segIndex)->initprot & VM_PROT_WRITE) != 0); +} + + +bool ImageLoaderMachO::segExecutable(unsigned int segIndex) const +{ + return ( (segLoadCommand(segIndex)->initprot & VM_PROT_EXECUTE) != 0); +} + + +bool ImageLoaderMachO::segUnaccessible(unsigned int segIndex) const +{ + return (segLoadCommand(segIndex)->initprot == 0); +} + +bool ImageLoaderMachO::segHasPreferredLoadAddress(unsigned int segIndex) const +{ + return (segLoadCommand(segIndex)->vmaddr != 0); +} + +uintptr_t ImageLoaderMachO::segPreferredLoadAddress(unsigned int segIndex) const +{ + return segLoadCommand(segIndex)->vmaddr; +} + +uintptr_t ImageLoaderMachO::segActualLoadAddress(unsigned int segIndex) const +{ + return segLoadCommand(segIndex)->vmaddr + fSlide; +} + + +uintptr_t ImageLoaderMachO::segActualEndAddress(unsigned int segIndex) const +{ + return segActualLoadAddress(segIndex) + segSize(segIndex); +} + +bool ImageLoaderMachO::segHasRebaseFixUps(unsigned int segIndex) const +{ +#if TEXT_RELOC_SUPPORT + // scan sections for fix-up bit + const macho_segment_command* segCmd = segLoadCommand(segIndex); + const struct macho_section* const sectionsStart = (struct macho_section*)((char*)segCmd + sizeof(struct macho_segment_command)); + const struct macho_section* const sectionsEnd = §ionsStart[segCmd->nsects]; + for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { + if ( (sect->flags & S_ATTR_LOC_RELOC) != 0 ) + return true; } +#endif + return false; } +bool ImageLoaderMachO::segHasBindFixUps(unsigned int segIndex) const +{ +#if TEXT_RELOC_SUPPORT + // scan sections for fix-up bit + const macho_segment_command* segCmd = segLoadCommand(segIndex); + const struct macho_section* const sectionsStart = (struct macho_section*)((char*)segCmd + sizeof(struct macho_segment_command)); + const struct macho_section* const sectionsEnd = §ionsStart[segCmd->nsects]; + for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { + if ( (sect->flags & S_ATTR_EXT_RELOC) != 0 ) + return true; + } +#endif + return false; +} + +#if __i386__ +bool ImageLoaderMachO::segIsReadOnlyImport(unsigned int segIndex) const +{ + const macho_segment_command* segCmd = segLoadCommand(segIndex); + return ( (segCmd->initprot & VM_PROT_EXECUTE) + && ((segCmd->initprot & VM_PROT_WRITE) == 0) + && (strcmp(segCmd->segname, "__IMPORT") == 0) ); +} +#endif + + +void ImageLoaderMachO::UnmapSegments() +{ + // usually unmap image when done + if ( ! this->leaveMapped() && (this->getState() >= dyld_image_state_mapped) ) { + // unmap TEXT segment last because it contains load command being inspected + unsigned int textSegmentIndex = 0; + for(unsigned int i=0; i < fSegmentsCount; ++i) { + //dyld::log("unmap %s at 0x%08lX\n", seg->getName(), seg->getActualLoadAddress(this)); + if ( strcmp(segName(i), "__TEXT") == 0 ) { + textSegmentIndex = i; + } + else { + // update stats + --ImageLoader::fgTotalSegmentsMapped; + ImageLoader::fgTotalBytesMapped -= segSize(i); + munmap((void*)segActualLoadAddress(i), segSize(i)); + } + } + // now unmap TEXT + --ImageLoader::fgTotalSegmentsMapped; + ImageLoader::fgTotalBytesMapped -= segSize(textSegmentIndex); + munmap((void*)segActualLoadAddress(textSegmentIndex), segSize(textSegmentIndex)); + } +} + + +// prefetch __DATA/__OBJC pages during launch, but not for dynamically loaded code +void ImageLoaderMachO::preFetchDATA(int fd, uint64_t offsetInFat, const LinkContext& context) +{ + if ( context.linkingMainExecutable ) { + for(unsigned int i=0, e=segmentCount(); i < e; ++i) { + if ( segWriteable(i) && (segFileSize(i) > 0) ) { + // prefetch writable segment that have mmap'ed regions + radvisory advice; + advice.ra_offset = offsetInFat + segFileOffset(i); + advice.ra_count = (int)segFileSize(i); + // limit prefetch to 1MB (256 pages) + if ( advice.ra_count > 1024*1024 ) + advice.ra_count = 1024*1024; + // don't prefetch single pages, let them fault in + fgTotalBytesPreFetched += advice.ra_count; + fcntl(fd, F_RDADVISE, &advice); + if ( context.verboseMapping ) { + dyld::log("%18s prefetching 0x%0lX -> 0x%0lX\n", + segName(i), segActualLoadAddress(i), segActualLoadAddress(i)+advice.ra_count-1); + } + } + } + } +} bool ImageLoaderMachO::segmentsMustSlideTogether() const @@ -203,8 +769,7 @@ bool ImageLoaderMachO::segmentsMustSlideTogether() const bool ImageLoaderMachO::segmentsCanSlide() const { - const macho_header* mh = (macho_header*)fMachOData; - return ( (mh->filetype == MH_DYLIB) || (mh->filetype == MH_BUNDLE) ); + return (this->isDylib() || this->isBundle() || this->isPositionIndependentExecutable()); } bool ImageLoaderMachO::isBundle() const @@ -219,6 +784,19 @@ bool ImageLoaderMachO::isDylib() const return ( mh->filetype == MH_DYLIB ); } +bool ImageLoaderMachO::isExecutable() const +{ + const macho_header* mh = (macho_header*)fMachOData; + return ( mh->filetype == MH_EXECUTE ); +} + +bool ImageLoaderMachO::isPositionIndependentExecutable() const +{ + const macho_header* mh = (macho_header*)fMachOData; + return ( (mh->filetype == MH_EXECUTE) && ((mh->flags & MH_PIE) != 0) ); +} + + bool ImageLoaderMachO::forceFlat() const { const macho_header* mh = (macho_header*)fMachOData; @@ -243,930 +821,367 @@ bool ImageLoaderMachO::hasCoalescedExports() const return ( (mh->flags & MH_WEAK_DEFINES) != 0 ); } -bool ImageLoaderMachO::needsCoalescing() const +bool ImageLoaderMachO::hasReferencesToWeakSymbols() const { const macho_header* mh = (macho_header*)fMachOData; return ( (mh->flags & MH_BINDS_TO_WEAK) != 0 ); } -#if !__LP64__ // split segs not supported for 64-bits - -#if 1 // hack until kernel headers and glue are in system -struct _shared_region_mapping_np { - mach_vm_address_t address; - mach_vm_size_t size; - mach_vm_offset_t file_offset; - vm_prot_t max_prot; /* read/write/execute/COW/ZF */ - vm_prot_t init_prot; /* read/write/execute/COW/ZF */ -}; -struct _shared_region_range_np { - mach_vm_address_t address; - mach_vm_size_t size; -}; - -// Called by dyld. -// Requests the kernel to map a number of regions from the fd into the -// shared sections address range (0x90000000-0xAFFFFFFF). -// If shared_region_make_private_np() has not been called by this process, -// the file mapped in is seen in the address space of all processes that -// participate in using the shared region. -// If shared_region_make_private_np() _has_ been called by this process, -// the file mapped in is only seen by this process. -// If the slide parameter is not NULL and then regions cannot be mapped -// as requested, the kernel will try to map the file in at a different -// address in the shared region and return the distance slid. -// If the mapping requesting cannot be fulfilled, returns non-zero. -static int -_shared_region_map_file_np( - int fd, // file descriptor to map into shared region - unsigned int regionCount, // number of entres in array of regions - const _shared_region_mapping_np regions[], // the array of regions to map - uint64_t* slide) // the amount all regions were slid, NULL means don't attempt to slide -{ - //fprintf(stderr, "%s(%i, %u, %8p, %8p)\n", __func__, fd, regionCount, regions, slide); - //for ( unsigned int i=0; i < regionCount; ++i) { - // fprintf(stderr, "\taddress=0x%08llX, size=0x%08llX\n", regions[i].address, regions[i].size); - //} - int r = syscall(299, fd, regionCount, regions, slide); -// if(0 != r) -// fprintf(stderr, "%s(%i, %u, %8p, %8p) errno=%i (%s)\n", __func__, fd, regionCount, regions, slide, errno, strerror(errno)); - return r; -} -// Called by dyld if shared_region_map_file() fails. -// Requests the kernel to take this process out of using the shared region. -// The specified ranges are created as private copies from the shared region for this process. -static int -_shared_region_make_private_np( - unsigned int rangeCount, // number of entres in array of msrp_range - const _shared_region_range_np ranges[]) // the array of shared regions to make private -{ - //fprintf(stderr, "%s(%u, %8p)\n", __func__, rangeCount, ranges); - int r = syscall(300, rangeCount, ranges); -// if(0 != r) -// fprintf(stderr, "%s(%u, %8p) errno=%i (%s)\n", __func__, rangeCount, ranges, errno, strerror(errno)); - return r; -} -#define KERN_SHREG_PRIVATIZABLE 54 -#endif // hack until kernel headers and glue are in system - -static uintptr_t sNextAltLoadAddress -#if __ppc_ - = 0xC0000000; -#else - = 0; -#endif - -static int -_shared_region_map_file_with_mmap( - int fd, // file descriptor to map into shared region - unsigned int regionCount, // number of entres in array of regions - const _shared_region_mapping_np regions[]) // the array of regions to map -{ - // map in each region - for(unsigned int i=0; i < regionCount; ++i) { - void* mmapAddress = (void*)(uintptr_t)(regions[i].address); - size_t size = regions[i].size; - if ( (regions[i].init_prot & VM_PROT_ZF) != 0 ) { - // do nothing already vm_allocate() which zero fills - } - else { - int protection = 0; - if ( regions[i].init_prot & VM_PROT_EXECUTE ) - protection |= PROT_EXEC; - if ( regions[i].init_prot & VM_PROT_READ ) - protection |= PROT_READ; - if ( regions[i].init_prot & VM_PROT_WRITE ) - protection |= PROT_WRITE; - off_t offset = regions[i].file_offset; - //fprintf(stderr, "mmap(%p, 0x%08lX, block=0x%08X, %s\n", mmapAddress, size, biggestDiff, fPath); - mmapAddress = mmap(mmapAddress, size, protection, MAP_FILE | MAP_FIXED | MAP_PRIVATE, fd, offset); - if ( mmapAddress == ((void*)(-1)) ) - throw "mmap error"; - } - } - - return 0; -} - - -static -bool -hasSharedRegionMapFile(void) +bool ImageLoaderMachO::participatesInCoalescing() const { - int mib[CTL_MAXNAME]; - int value = 0; - size_t size; - - mib[0] = CTL_KERN; - mib[1] = KERN_SHREG_PRIVATIZABLE; - size = sizeof (int); - if (sysctl(mib, 2, &value, &size, NULL, 0) != 0) { - value = 0; - } - - return 0 != value; -} - -int -ImageLoaderMachO::sharedRegionMapFilePrivateOutside(int fd, - uint64_t offsetInFat, - uint64_t lenInFat, - uint64_t fileLen, - const LinkContext& context) -{ - const unsigned int segmentCount = fSegments.size(); - const unsigned int extraZeroFillEntries = getExtraZeroFillEntriesCount(); - const unsigned int regionCount = segmentCount+extraZeroFillEntries; - _shared_region_mapping_np regions[regionCount]; - initMappingTable(offsetInFat, regions); - int r = -1; - // find space somewhere to allocate split seg - bool foundRoom = false; - vm_size_t biggestDiff = 0; - while ( ! foundRoom ) { - foundRoom = true; - for(unsigned int i=0; i < regionCount; ++i) { - vm_address_t addr = sNextAltLoadAddress + regions[i].address - regions[0].address; - vm_size_t size = regions[i].size ; - r = vm_allocate(mach_task_self(), &addr, size, false /*only this range*/); - if ( 0 != r ) { - // no room here, deallocate what has succeeded so far - for(unsigned int j=0; j < i; ++j) { - vm_address_t addr = sNextAltLoadAddress + regions[j].address - regions[0].address; - vm_size_t size = regions[j].size ; - (void)vm_deallocate(mach_task_self(), addr, size); - } - sNextAltLoadAddress += 0x00100000; // skip ahead 1MB and try again - if ( (sNextAltLoadAddress & 0xF0000000) == 0x90000000 ) - throw "can't map split seg anywhere"; - foundRoom = false; - break; - } - vm_size_t high = (regions[i].address + size - regions[0].address) & 0x0FFFFFFF; - if ( high > biggestDiff ) - biggestDiff = high; - } - } - - // map in each region - uintptr_t slide = sNextAltLoadAddress - regions[0].address; - this->setSlide(slide); - for(unsigned int i=0; i < regionCount; ++i) { - if ( (regions[i].init_prot & VM_PROT_ZF) != 0 ) { - // do nothing vm_allocate() zero-fills by default - } - else { - void* mmapAddress = (void*)(uintptr_t)(regions[i].address + slide); - size_t size = regions[i].size; - int protection = 0; - if ( regions[i].init_prot & VM_PROT_EXECUTE ) - protection |= PROT_EXEC; - if ( regions[i].init_prot & VM_PROT_READ ) - protection |= PROT_READ; - if ( regions[i].init_prot & VM_PROT_WRITE ) - protection |= PROT_WRITE; - off_t offset = regions[i].file_offset; - //fprintf(stderr, "mmap(%p, 0x%08lX, block=0x%08X, %s\n", mmapAddress, size, biggestDiff, fPath); - mmapAddress = mmap(mmapAddress, size, protection, MAP_FILE | MAP_FIXED | MAP_PRIVATE, fd, offset); - if ( mmapAddress == ((void*)(-1)) ) - throw "mmap error"; - } - } - // set so next maps right after this one - sNextAltLoadAddress += biggestDiff; - sNextAltLoadAddress = (sNextAltLoadAddress + 4095) & (-4096); - - // logging - if ( context.verboseMapping ) { - fprintf(stderr, "dyld: Mapping split-seg outside shared region, slid by 0x%08lX %s\n", this->fSlide, this->getPath()); - for(unsigned int segIndex=0,entryIndex=0; segIndex < segmentCount; ++segIndex, ++entryIndex){ - Segment* seg = fSegments[segIndex]; - const _shared_region_mapping_np* entry = ®ions[entryIndex]; - if ( (entry->init_prot & VM_PROT_ZF) == 0 ) - fprintf(stderr, "%18s at 0x%08lX->0x%08lX\n", - seg->getName(), seg->getActualLoadAddress(), seg->getActualLoadAddress()+seg->getFileSize()-1); - if ( entryIndex < (regionCount-1) ) { - const _shared_region_mapping_np* nextEntry = ®ions[entryIndex+1]; - if ( (nextEntry->init_prot & VM_PROT_ZF) != 0 ) { - uint64_t segOffset = nextEntry->address - entry->address; - fprintf(stderr, "%18s at 0x%08lX->0x%08lX (zerofill)\n", - seg->getName(), (uintptr_t)(seg->getActualLoadAddress() + segOffset), (uintptr_t)(seg->getActualLoadAddress() + segOffset + nextEntry->size - 1)); - ++entryIndex; - } - } - } - } - - return r; + const macho_header* mh = (macho_header*)fMachOData; + // if image is loaded with RTLD_LOCAL, then its symbols' visibility + // is reduced and it can't coalesce with other images + if ( this->hasHiddenExports() ) + return false; + return ( (mh->flags & (MH_WEAK_DEFINES|MH_BINDS_TO_WEAK)) != 0 ); } -void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInFat, uint64_t fileLen, const LinkContext& context) -{ - enum SharedRegionState - { - kSharedRegionStartState = 0, - kSharedRegionLoadFileState, - kSharedRegionMapFileState, - kSharedRegionMapFilePrivateState, - kSharedRegionMapFilePrivateMMapState, - kSharedRegionMapFilePrivateOutsideState, - }; - static SharedRegionState sSharedRegionState = kSharedRegionStartState; - - // non-split segment libraries handled by super class - if ( !fIsSplitSeg ) - return ImageLoader::mapSegments(fd, offsetInFat, lenInFat, fileLen, context); - - if ( kSharedRegionStartState == sSharedRegionState ) { - if ( hasSharedRegionMapFile() ) { - if ( context.slideAndPackDylibs ) { - sharedRegionMakePrivate(context); - // remove underlying submap and block out 0x90000000 to 0xAFFFFFFF - vm_address_t addr = (vm_address_t)0x90000000; - vm_deallocate(mach_task_self(), addr, 0x20000000); - vm_allocate(mach_task_self(), &addr, 0x20000000, false); - sSharedRegionState = kSharedRegionMapFilePrivateMMapState; - } - else if ( context.sharedRegionMode == kUsePrivateSharedRegion ) { - sharedRegionMakePrivate(context); - sSharedRegionState = kSharedRegionMapFilePrivateState; - } - else if ( context.sharedRegionMode == kDontUseSharedRegion ) { - sSharedRegionState = kSharedRegionMapFilePrivateOutsideState; - } - else { - sSharedRegionState = kSharedRegionMapFileState; - } - } - else { - sSharedRegionState = kSharedRegionLoadFileState; - } - } - - if ( kSharedRegionLoadFileState == sSharedRegionState ) { - if ( 0 != sharedRegionLoadFile(fd, offsetInFat, lenInFat, fileLen, context) ) { - sSharedRegionState = kSharedRegionMapFilePrivateOutsideState; - } - } - else - if ( kSharedRegionMapFileState == sSharedRegionState ) { - if ( 0 != sharedRegionMapFile(fd, offsetInFat, lenInFat, fileLen, context) ) { - sharedRegionMakePrivate(context); - sSharedRegionState = kSharedRegionMapFilePrivateState; - } - } - - if ( (kSharedRegionMapFilePrivateState == sSharedRegionState) || (kSharedRegionMapFilePrivateMMapState == sSharedRegionState) ) { - if ( 0 != sharedRegionMapFilePrivate(fd, offsetInFat, lenInFat, fileLen, context, (kSharedRegionMapFilePrivateMMapState == sSharedRegionState)) ) { - sSharedRegionState = kSharedRegionMapFilePrivateOutsideState; - } - } - - if ( kSharedRegionMapFilePrivateOutsideState == sSharedRegionState ) { - if ( 0 != sharedRegionMapFilePrivateOutside(fd, offsetInFat, lenInFat, fileLen, context) ) { - throw "mapping error"; - } - } -} -unsigned int -ImageLoaderMachO::getExtraZeroFillEntriesCount() -{ - // calculate mapping entries - const unsigned int segmentCount = fSegments.size(); - unsigned int extraZeroFillEntries = 0; - for(unsigned int i=0; i < segmentCount; ++i){ - Segment* seg = fSegments[i]; - if ( seg->hasTrailingZeroFill() ) - ++extraZeroFillEntries; - } - - return extraZeroFillEntries; -} - -void -ImageLoaderMachO::initMappingTable(uint64_t offsetInFat, - _shared_region_mapping_np *mappingTable) -{ - unsigned int segmentCount = fSegments.size(); - for(unsigned int segIndex=0,entryIndex=0; segIndex < segmentCount; ++segIndex, ++entryIndex){ - Segment* seg = fSegments[segIndex]; - _shared_region_mapping_np* entry = &mappingTable[entryIndex]; - entry->address = seg->getActualLoadAddress(); - entry->size = seg->getFileSize(); - entry->file_offset = seg->getFileOffset() + offsetInFat; - entry->init_prot = VM_PROT_NONE; - if ( !seg->unaccessible() ) { - if ( seg->executable() ) - entry->init_prot |= VM_PROT_EXECUTE; - if ( seg->readable() ) - entry->init_prot |= VM_PROT_READ; - if ( seg->writeable() ) - entry->init_prot |= VM_PROT_WRITE | VM_PROT_COW; - } - entry->max_prot = entry->init_prot; - if ( seg->hasTrailingZeroFill() ) { - _shared_region_mapping_np* zfentry = &mappingTable[++entryIndex]; - zfentry->address = entry->address + seg->getFileSize(); - zfentry->size = seg->getSize() - seg->getFileSize(); - zfentry->file_offset = 0; - zfentry->init_prot = entry->init_prot | VM_PROT_COW | VM_PROT_ZF; - zfentry->max_prot = zfentry->init_prot; - } - } -} - -int -ImageLoaderMachO::sharedRegionMakePrivate(const LinkContext& context) +void ImageLoaderMachO::setSlide(intptr_t slide) { - if ( context.verboseMapping ) - fprintf(stderr, "dyld: making shared regions private\n"); - - // shared mapping failed, so make private copy of shared region and try mapping private - RegionsVector allRegions; - context.getAllMappedRegions(allRegions); - std::vector<_shared_region_range_np> splitSegRegions; - const unsigned int allRegiontCount = allRegions.size(); - for(unsigned int i=0; i < allRegiontCount; ++i){ - MappedRegion region = allRegions[i]; - uint8_t highByte = region.address >> 28; - if ( (highByte == 9) || (highByte == 0xA) ) { - _shared_region_range_np splitRegion; - splitRegion.address = region.address; - splitRegion.size = region.size; - splitSegRegions.push_back(splitRegion); - } - } - int result = _shared_region_make_private_np(splitSegRegions.size(), &splitSegRegions[0]); - // notify gdb or other lurkers that this process is no longer using the shared region - dyld_all_image_infos.processDetachedFromSharedRegion = true; - return result; + fSlide = slide; } -int -ImageLoaderMachO::sharedRegionMapFile(int fd, - uint64_t offsetInFat, - uint64_t lenInFat, - uint64_t fileLen, - const LinkContext& context) -{ - // build table of segments to map - const unsigned int segmentCount = fSegments.size(); - const unsigned int extraZeroFillEntries = getExtraZeroFillEntriesCount(); - const unsigned int mappingTableCount = segmentCount+extraZeroFillEntries; - _shared_region_mapping_np mappingTable[mappingTableCount]; - initMappingTable(offsetInFat, mappingTable); -// uint64_t slide; - uint64_t *slidep = NULL; - - // try to map it in shared - int r = _shared_region_map_file_np(fd, mappingTableCount, mappingTable, slidep); - if ( 0 == r ) { - if(NULL != slidep && 0 != *slidep) { - // update with actual load addresses - } - if ( context.verboseMapping ) { - fprintf(stderr, "dyld: Mapping split-seg shared %s\n", this->getPath()); - for(unsigned int segIndex=0,entryIndex=0; segIndex < segmentCount; ++segIndex, ++entryIndex){ - Segment* seg = fSegments[segIndex]; - const _shared_region_mapping_np* entry = &mappingTable[entryIndex]; - if ( (entry->init_prot & VM_PROT_ZF) == 0 ) - fprintf(stderr, "%18s at 0x%08lX->0x%08lX\n", - seg->getName(), seg->getActualLoadAddress(), seg->getActualLoadAddress()+seg->getFileSize()-1); - if ( entryIndex < (mappingTableCount-1) ) { - const _shared_region_mapping_np* nextEntry = &mappingTable[entryIndex+1]; - if ( (nextEntry->init_prot & VM_PROT_ZF) != 0 ) { - uint64_t segOffset = nextEntry->address - entry->address; - fprintf(stderr, "%18s at 0x%08lX->0x%08lX\n", - seg->getName(), (uintptr_t)(seg->getActualLoadAddress() + segOffset), (uintptr_t)(seg->getActualLoadAddress() + segOffset + nextEntry->size - 1)); - ++entryIndex; +void ImageLoaderMachO::loadCodeSignature(const struct linkedit_data_command* codeSigCmd, int fd, uint64_t offsetInFatFile, const LinkContext& context) +{ + // if dylib being loaded has no code signature load command + if ( codeSigCmd == NULL ) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED + bool codeSigningEnforced = context.codeSigningEnforced; + if ( context.mainExecutableCodeSigned && !codeSigningEnforced ) { + static bool codeSignEnforcementDynamicallyEnabled = false; + if ( !codeSignEnforcementDynamicallyEnabled ) { + uint32_t flags; + if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) { + if ( flags & CS_ENFORCEMENT ) { + codeSignEnforcementDynamicallyEnabled = true; } } } + codeSigningEnforced = codeSignEnforcementDynamicallyEnabled; } - } - return r; -} - - -int -ImageLoaderMachO::sharedRegionMapFilePrivate(int fd, - uint64_t offsetInFat, - uint64_t lenInFat, - uint64_t fileLen, - const LinkContext& context, - bool usemmap) -{ - const unsigned int segmentCount = fSegments.size(); - - // adjust base address of segments to pack next to last dylib - if ( context.slideAndPackDylibs ) { - uintptr_t lowestReadOnly = (uintptr_t)(-1); - uintptr_t lowestWritable = (uintptr_t)(-1); - for(unsigned int segIndex=0; segIndex < segmentCount; ++segIndex){ - Segment* seg = fSegments[segIndex]; - uintptr_t segEnd = seg->getActualLoadAddress(); - if ( seg->writeable() ) { - if ( segEnd < lowestWritable ) - lowestWritable = segEnd; - } - else { - if ( segEnd < lowestReadOnly ) - lowestReadOnly = segEnd; - } + // if we require dylibs to be code signed + if ( codeSigningEnforced ) { + // if there is a non-load command based code signature, use it + off_t offset = (off_t)offsetInFatFile; + if ( fcntl(fd, F_FINDSIGS, &offset, sizeof(offset)) != -1 ) + return; + // otherwise gracefully return from dlopen() + dyld::throwf("required code signature missing for '%s'\n", this->getPath()); } - uintptr_t baseAddress; - if ( lowestWritable - 256*1024*1024 < lowestReadOnly ) - baseAddress = lowestWritable - 256*1024*1024; - else - baseAddress = lowestReadOnly; - // record that we want dylb slid to fgNextSplitSegAddress - this->setSlide(fgNextSplitSegAddress - baseAddress); +#endif + //Since we don't have a range for the signature we have to assume full coverage + fCoveredCodeLength = UINT64_MAX; } - - // build table of segments to map - const unsigned int extraZeroFillEntries = getExtraZeroFillEntriesCount(); - const unsigned int mappingTableCount = segmentCount+extraZeroFillEntries; - _shared_region_mapping_np mappingTable[mappingTableCount]; - initMappingTable(offsetInFat, mappingTable); - uint64_t slide = 0; - - // try map it in privately (don't allow sliding if we pre-calculated the load address to pack dylibs) - int r; - if ( usemmap ) - r = _shared_region_map_file_with_mmap(fd, mappingTableCount, mappingTable); - else - r = _shared_region_map_file_np(fd, mappingTableCount, mappingTable, context.slideAndPackDylibs ? NULL : &slide); - if ( 0 == r ) { - if ( 0 != slide ) { - slide = (slide) & (-4096); // round down to page boundary - this->setSlide(slide); + else { +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // ignore code signatures in binaries built with pre-10.9 tools + if ( this->sdkVersion() < DYLD_MACOSX_VERSION_10_9 ) { + return; } - if ( context.verboseMapping ) { - if ( slide == 0 ) - fprintf(stderr, "dyld: Mapping split-seg un-shared %s\n", this->getPath()); - else - fprintf(stderr, "dyld: Mapping split-seg un-shared slid by 0x%08llX %s\n", slide, this->getPath()); - for(unsigned int segIndex=0,entryIndex=0; segIndex < segmentCount; ++segIndex, ++entryIndex){ - Segment* seg = fSegments[segIndex]; - const _shared_region_mapping_np* entry = &mappingTable[entryIndex]; - if ( (entry->init_prot & VM_PROT_ZF) == 0 ) - fprintf(stderr, "%18s at 0x%08lX->0x%08lX\n", - seg->getName(), seg->getActualLoadAddress(), seg->getActualLoadAddress()+seg->getFileSize()-1); - if ( entryIndex < (mappingTableCount-1) ) { - const _shared_region_mapping_np* nextEntry = &mappingTable[entryIndex+1]; - if ( (nextEntry->init_prot & VM_PROT_ZF) != 0 ) { - uint64_t segOffset = nextEntry->address - entry->address; - fprintf(stderr, "%18s at 0x%08lX->0x%08lX (zerofill)\n", - seg->getName(), (uintptr_t)(seg->getActualLoadAddress() + segOffset), (uintptr_t)(seg->getActualLoadAddress() + segOffset + nextEntry->size - 1)); - ++entryIndex; - } - } - } +#endif + fsignatures_t siginfo; + siginfo.fs_file_start=offsetInFatFile; // start of mach-o slice in fat file + siginfo.fs_blob_start=(void*)(long)(codeSigCmd->dataoff); // start of CD in mach-o file + siginfo.fs_blob_size=codeSigCmd->datasize; // size of CD + int result = fcntl(fd, F_ADDFILESIGS_RETURN, &siginfo); + +#if TARGET_IPHONE_SIMULATOR + // rdar://problem/18759224> check range covered by the code directory after loading + // Attempt to fallback only if we are in the simulator + + if ( result == -1 ) { + result = fcntl(fd, F_ADDFILESIGS, &siginfo); + siginfo.fs_file_start = codeSigCmd->dataoff; } - if ( context.slideAndPackDylibs ) { - // calculate where next split-seg dylib can load - uintptr_t largestReadOnly = 0; - uintptr_t largestWritable = 0; - for (unsigned int segIndex=0; segIndex < segmentCount; ++segIndex) { - Segment* seg = fSegments[segIndex]; - uintptr_t segEnd = seg->getActualLoadAddress()+seg->getSize(); - segEnd = (segEnd+4095) & (-4096); // page align - if ( seg->writeable() ) { - if ( segEnd > largestWritable ) - largestWritable = segEnd; - } - else { - if ( segEnd > largestReadOnly ) - largestReadOnly = segEnd; - } - } - if ( largestWritable - 256*1024*1024 > largestReadOnly ) - fgNextSplitSegAddress = largestWritable - 256*1024*1024; - else - fgNextSplitSegAddress = largestReadOnly; +#endif + + if ( result == -1 ) { + if ( (errno == EPERM) || (errno == EBADEXEC) ) + dyld::throwf("code signature invalid for '%s'\n", this->getPath()); + if ( context.verboseCodeSignatures ) + dyld::log("dyld: Failed registering code signature for %s, errno=%d\n", this->getPath(), errno); + siginfo.fs_file_start = UINT64_MAX; + } else if ( context.verboseCodeSignatures ) { + dyld::log("dyld: Registered code signature for %s\n", this->getPath()); } + fCoveredCodeLength = siginfo.fs_file_start; } - if ( context.slideAndPackDylibs && (r != 0) ) - throwf("can't rebase split-seg dylib %s because shared_region_map_file_np() returned %d", this->getPath(), r); - - return r; } - -int -ImageLoaderMachO::sharedRegionLoadFile(int fd, uint64_t offsetInFat, uint64_t lenInFat, uint64_t fileLen, const LinkContext& context) +void ImageLoaderMachO::validateFirstPages(const struct linkedit_data_command* codeSigCmd, int fd, const uint8_t *fileData, size_t lenFileData, off_t offsetInFat, const LinkContext& context) { - - // map in split segment file at random address, then tell kernel to share it - void* loadAddress = 0; - loadAddress = mmap(NULL, fileLen, PROT_READ, MAP_FILE, fd, 0); - if ( loadAddress == ((void*)(-1)) ) - throw "mmap error"; - - // calculate mapping entries - const unsigned int segmentCount = fSegments.size(); - unsigned int extraZeroFillEntries = getExtraZeroFillEntriesCount(); - - // build table of segments to map - const unsigned int mappingTableCount = segmentCount+extraZeroFillEntries; - const uintptr_t baseAddress = fSegments[0]->getPreferredLoadAddress(); - sf_mapping mappingTable[mappingTableCount]; - initMappingTable(offsetInFat, mappingTable, baseAddress); - - - // use load_shared_file() to map all segments at once - int flags = 0; // might need to set NEW_LOCAL_SHARED_REGIONS on first use - static bool firstTime = true; - if ( firstTime ) { - // when NEW_LOCAL_SHARED_REGIONS bit is set, this process will get is own shared region - // this is used by Xcode to prevent development libraries from polluting the global shared segment - if ( context.sharedRegionMode == kUsePrivateSharedRegion ) - flags |= NEW_LOCAL_SHARED_REGIONS; - firstTime = false; - } - - caddr_t base_address = (caddr_t)baseAddress; - kern_return_t r; - r = load_shared_file( (char*)fPath, // path of file to map shared - (char*)loadAddress, // beginning of local copy of sharable pages in file - fileLen, // end of shareable pages in file - &base_address, // beginning of address range to map - mappingTableCount, // number of entres in array of sf_mapping - mappingTable, // the array of sf_mapping - &flags); // in/out flags - if ( 0 != r ) { - // try again but tell kernel it is ok to slide - flags |= ALTERNATE_LOAD_SITE; - r = load_shared_file((char*)fPath,(char*)loadAddress, fileLen, &base_address, - mappingTableCount, mappingTable, &flags); +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // rdar://problem/21839703> 15A226d: dyld crashes in mageLoaderMachO::validateFirstPages during dlopen() after encountering an mmap failure + // We need to ignore older code signatures because they will be bad. + if ( this->sdkVersion() < DYLD_MACOSX_VERSION_10_9 ) { + return; } - - // unmap file from random address now that they are (hopefully) mapped into the shared region - munmap(loadAddress, fileLen); - - if ( 0 == r ) { - if ( base_address != (caddr_t)baseAddress ) - this->setSlide((uintptr_t)base_address - baseAddress); - if ( context.verboseMapping ) { - if ( base_address != (caddr_t)baseAddress ) - fprintf(stderr, "dyld: Mapping split-seg load_shared_alt_region %s\n", this->getPath()); +#endif + if (codeSigCmd != NULL) { + if ( context.verboseMapping ) + dyld::log("dyld: validate pages: %llu\n", (unsigned long long)offsetInFat); + + void *fdata = xmmap(NULL, lenFileData, PROT_READ | PROT_EXEC, MAP_SHARED, fd, offsetInFat); + if ( fdata == MAP_FAILED ) { + if ( context.processRequiresLibraryValidation ) + dyld::throwf("cannot load image with wrong team ID in process using Library Validation"); else - fprintf(stderr, "dyld: Mapping split-seg load_shared %s\n", this->getPath()); - for(unsigned int segIndex=0,entryIndex=0; segIndex < segmentCount; ++segIndex, ++entryIndex){ - Segment* seg = fSegments[segIndex]; - const sf_mapping* entry = &mappingTable[entryIndex]; - if ( (entry->protection & VM_PROT_ZF) == 0 ) - fprintf(stderr, "%18s at 0x%08lX->0x%08lX\n", - seg->getName(), seg->getActualLoadAddress(), seg->getActualLoadAddress()+seg->getFileSize()-1); - if ( entryIndex < (mappingTableCount-1) ) { - const sf_mapping* nextEntry = &mappingTable[entryIndex+1]; - if ( (nextEntry->protection & VM_PROT_ZF) != 0 ) { - fprintf(stderr, "%18s at 0x%08lX->0x%08lX\n", - seg->getName(), (uintptr_t)(nextEntry->mapping_offset + base_address), (uintptr_t)(nextEntry->mapping_offset + base_address + nextEntry->size - 1)); - ++entryIndex; - } - } - } - } - } - return r; -} -void -ImageLoaderMachO::initMappingTable(uint64_t offsetInFat, - sf_mapping *mappingTable, - uintptr_t baseAddress) -{ - unsigned int segmentCount = fSegments.size(); - for(unsigned int segIndex=0,entryIndex=0; segIndex < segmentCount; ++segIndex, ++entryIndex){ - Segment* seg = fSegments[segIndex]; - sf_mapping* entry = &mappingTable[entryIndex]; - entry->mapping_offset = seg->getPreferredLoadAddress() - baseAddress; - entry->size = seg->getFileSize(); - entry->file_offset = seg->getFileOffset() + offsetInFat; - entry->protection = VM_PROT_NONE; - if ( !seg->unaccessible() ) { - if ( seg->executable() ) - entry->protection |= VM_PROT_EXECUTE; - if ( seg->readable() ) - entry->protection |= VM_PROT_READ; - if ( seg->writeable() ) - entry->protection |= VM_PROT_WRITE | VM_PROT_COW; - } - - entry->cksum = 0; - if ( seg->hasTrailingZeroFill() ) { - sf_mapping* zfentry = &mappingTable[++entryIndex]; - zfentry->mapping_offset = entry->mapping_offset + seg->getFileSize(); - zfentry->size = seg->getSize() - seg->getFileSize(); - zfentry->file_offset = 0; - zfentry->protection = entry->protection | VM_PROT_COW | VM_PROT_ZF; - zfentry->cksum = 0; + dyld::throwf("mmap() errno=%d validating first page of '%s'", errno, getInstallPath()); } + if ( memcmp(fdata, fileData, lenFileData) != 0 ) + dyld::throwf("mmap() page compare failed for '%s'", getInstallPath()); + munmap(fdata, lenFileData); } } -#endif // !__LP64__ split segs not supported for 64-bits - -void ImageLoaderMachO::setSlide(intptr_t slide) +const char* ImageLoaderMachO::getInstallPath() const { - fSlide = slide; + if ( fDylibIDOffset != 0 ) { + const dylib_command* dylibID = (dylib_command*)(&fMachOData[fDylibIDOffset]); + return (char*)dylibID + dylibID->dylib.name.offset; + } + return NULL; } -void ImageLoaderMachO::parseLoadCmds() +void ImageLoaderMachO::registerInterposing() { - // now that segments are mapped in, get real fMachOData, fLinkEditBase, and fSlide - const unsigned int segmentCount = fSegments.size(); - for(unsigned int i=0; i < segmentCount; ++i){ - Segment* seg = fSegments[i]; - // set up pointer to __LINKEDIT segment - if ( strcmp(seg->getName(),"__LINKEDIT") == 0 ) - fLinkEditBase = (uint8_t*)(seg->getActualLoadAddress() - seg->getFileOffset()); - // __TEXT segment always starts at beginning of file and contains mach_header and load commands - if ( strcmp(seg->getName(),"__TEXT") == 0 ) { - if ( seg->hasFixUps() ) - fTextSegmentWithFixups = (SegmentMachO*)seg; - } - // some segment always starts at beginning of file and contains mach_header and load commands - if ( (seg->getFileOffset() == 0) && (seg->getFileSize() != 0) ) { - fMachOData = (uint8_t*)(seg->getActualLoadAddress()); - } - } - - // walk load commands (mapped in at start of __TEXT segment) + // mach-o files advertise interposing by having a __DATA __interpose section + struct InterposeData { uintptr_t replacement; uintptr_t replacee; }; const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; const struct load_command* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { - case LC_SYMTAB: - { - const struct symtab_command* symtab = (struct symtab_command*)cmd; - fStrings = (const char*)&fLinkEditBase[symtab->stroff]; - fSymbolTable = (struct macho_nlist*)(&fLinkEditBase[symtab->symoff]); - } - break; - case LC_DYSYMTAB: - fDynamicInfo = (struct dysymtab_command*)cmd; - break; - case LC_SUB_UMBRELLA: - fHasSubUmbrella = true; - break; - case LC_SUB_FRAMEWORK: - { - const struct sub_framework_command* subf = (struct sub_framework_command*)cmd; - fReExportThruFramework = (char*)cmd + subf->umbrella.offset; - } - break; - case LC_SUB_LIBRARY: - fHasSubLibraries = true; - break; - case LC_ROUTINES_COMMAND: - fDashInit = (struct macho_routines_command*)cmd; - break; case LC_SEGMENT_COMMAND: { const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; - const bool isDataSeg = (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) { - const uint8_t type = sect->flags & SECTION_TYPE; - if ( type == S_MOD_INIT_FUNC_POINTERS ) - fModInitSection = sect; - else if ( type == S_MOD_TERM_FUNC_POINTERS ) - fModTermSection = sect; - else if ( isDataSeg && (strcmp(sect->sectname, "__dyld") == 0) ) { - fDATAdyld = sect; + if ( ((sect->flags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(sect->sectname, "__interpose") == 0) && (strcmp(seg->segname, "__DATA") == 0)) ) { + const InterposeData* interposeArray = (InterposeData*)(sect->addr + fSlide); + const size_t count = sect->size / sizeof(InterposeData); + for (size_t i=0; i < count; ++i) { + ImageLoader::InterposeTuple tuple; + tuple.replacement = interposeArray[i].replacement; + tuple.neverImage = this; + tuple.onlyImage = NULL; + tuple.replacee = interposeArray[i].replacee; + // verify that replacement is in this image + if ( this->containsAddress((void*)tuple.replacement) ) { + // chain to any existing interpositions + for (std::vector::iterator it=fgInterposingTuples.begin(); it != fgInterposingTuples.end(); it++) { + if ( it->replacee == tuple.replacee ) { + tuple.replacee = it->replacement; + } + } + ImageLoader::fgInterposingTuples.push_back(tuple); + } + } } - else if ( isDataSeg && (strcmp(sect->sectname, "__image_notify") == 0) ) - fImageNotifySection = sect; } } break; - case LC_TWOLEVEL_HINTS: - fTwoLevelHints = (struct twolevel_hints_command*)cmd; - break; - case LC_ID_DYLIB: - { - fDylibID = (struct dylib_command*)cmd; - } - break; - case LC_LOAD_WEAK_DYLIB: - // do nothing, just prevent LC_REQ_DYLD exception from occuring - break; - default: - if ( (cmd->cmd & LC_REQ_DYLD) != 0 ) - throwf("unknown required load command 0x%08X", cmd->cmd); } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } } - - - -const char* ImageLoaderMachO::getInstallPath() const +uint32_t ImageLoaderMachO::sdkVersion() const { - if ( fDylibID != NULL ) { - return (char*)fDylibID + fDylibID->dylib.name.offset; + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + const struct version_min_command* versCmd; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch ( cmd->cmd ) { + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: + versCmd = (version_min_command*)cmd; + return versCmd->sdk; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - return NULL; + return 0; } -// test if this image is re-exported through parent (the image that loaded this one) -bool ImageLoaderMachO::isSubframeworkOf(const LinkContext& context, const ImageLoader* parent) const -{ - if ( fReExportThruFramework != NULL ) { - // need to match LC_SUB_FRAMEWORK string against the leaf name of the install location of parent... - const char* parentInstallPath = parent->getInstallPath(); - if ( parentInstallPath != NULL ) { - const char* lastSlash = strrchr(parentInstallPath, '/'); - if ( lastSlash != NULL ) { - if ( strcmp(&lastSlash[1], fReExportThruFramework) == 0 ) - return true; - if ( context.imageSuffix != NULL ) { - // when DYLD_IMAGE_SUFFIX is used, lastSlash string needs imageSuffix removed from end - char reexportAndSuffix[strlen(context.imageSuffix)+strlen(fReExportThruFramework)+1]; - strcpy(reexportAndSuffix, fReExportThruFramework); - strcat(reexportAndSuffix, context.imageSuffix); - if ( strcmp(&lastSlash[1], reexportAndSuffix) == 0 ) - return true; - } - } +uint32_t ImageLoaderMachO::minOSVersion(const mach_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; + const struct version_min_command* versCmd; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch ( cmd->cmd ) { + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: + versCmd = (version_min_command*)cmd; + return versCmd->version; } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - return false; + return 0; } -// test if child is re-exported -bool ImageLoaderMachO::hasSubLibrary(const LinkContext& context, const ImageLoader* child) const +uint32_t ImageLoaderMachO::minOSVersion() const { - if ( fHasSubLibraries ) { - // need to match LC_SUB_LIBRARY string against the leaf name (without extension) of the install location of child... - const char* childInstallPath = child->getInstallPath(); - if ( childInstallPath != NULL ) { - const char* lastSlash = strrchr(childInstallPath, '/'); - if ( lastSlash != NULL ) { - const char* firstDot = strchr(lastSlash, '.'); - int len; - if ( firstDot == NULL ) - len = strlen(lastSlash); - else - len = firstDot-lastSlash-1; - char childLeafName[len+1]; - strncpy(childLeafName, &lastSlash[1], len); - childLeafName[len] = '\0'; - const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; - const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; - const struct load_command* cmd = cmds; - for (uint32_t i = 0; i < cmd_count; ++i) { - switch (cmd->cmd) { - case LC_SUB_LIBRARY: - { - const struct sub_library_command* lib = (struct sub_library_command*)cmd; - const char* aSubLibName = (char*)cmd + lib->sub_library.offset; - if ( strcmp(aSubLibName, childLeafName) == 0 ) - return true; - if ( context.imageSuffix != NULL ) { - // when DYLD_IMAGE_SUFFIX is used, childLeafName string needs imageSuffix removed from end - char aSubLibNameAndSuffix[strlen(context.imageSuffix)+strlen(aSubLibName)+1]; - strcpy(aSubLibNameAndSuffix, aSubLibName); - strcat(aSubLibNameAndSuffix, context.imageSuffix); - if ( strcmp(aSubLibNameAndSuffix, childLeafName) == 0 ) - return true; - } - } - break; - } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); - } - } - } - } - if ( fHasSubUmbrella ) { - // need to match LC_SUB_UMBRELLA string against the leaf name of install location of child... - const char* childInstallPath = child->getInstallPath(); - if ( childInstallPath != NULL ) { - const char* lastSlash = strrchr(childInstallPath, '/'); - if ( lastSlash != NULL ) { - const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; - const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; - const struct load_command* cmd = cmds; - for (uint32_t i = 0; i < cmd_count; ++i) { - switch (cmd->cmd) { - case LC_SUB_UMBRELLA: - { - const struct sub_umbrella_command* um = (struct sub_umbrella_command*)cmd; - const char* aSubUmbrellaName = (char*)cmd + um->sub_umbrella.offset; - if ( strcmp(aSubUmbrellaName, &lastSlash[1]) == 0 ) - return true; - if ( context.imageSuffix != NULL ) { - // when DYLD_IMAGE_SUFFIX is used, lastSlash string needs imageSuffix removed from end - char umbrellaAndSuffix[strlen(context.imageSuffix)+strlen(aSubUmbrellaName)+1]; - strcpy(umbrellaAndSuffix, aSubUmbrellaName); - strcat(umbrellaAndSuffix, context.imageSuffix); - if ( strcmp(umbrellaAndSuffix, &lastSlash[1]) == 0 ) - return true; - } - } - break; - } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); - } - } + return ImageLoaderMachO::minOSVersion(machHeader()); +} + + +void* ImageLoaderMachO::getThreadPC() const +{ + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd == LC_MAIN ) { + entry_point_command* mainCmd = (entry_point_command*)cmd; + void* entry = (void*)(mainCmd->entryoff + (char*)fMachOData); + // verify entry point is in image + if ( this->containsAddress(entry) ) + return entry; + else + throw "LC_MAIN entryoff is out of range"; } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - return false; + return NULL; } - + void* ImageLoaderMachO::getMain() const { const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; const struct load_command* cmd = cmds; - for (unsigned long i = 0; i < cmd_count; ++i) { + for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { case LC_UNIXTHREAD: { - #if __ppc__ - const ppc_thread_state_t* registers = (ppc_thread_state_t*)(((char*)cmd) + 16); - return (void*)registers->srr0; - #elif __ppc64__ - const ppc_thread_state64_t* registers = (ppc_thread_state64_t*)(((char*)cmd) + 16); - return (void*)registers->srr0; - #elif __i386__ + #if __i386__ const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16); - return (void*)registers->eip; + void* entry = (void*)(registers->eip + fSlide); + #elif __x86_64__ + const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16); + void* entry = (void*)(registers->rip + fSlide); + #elif __arm__ + const arm_thread_state_t* registers = (arm_thread_state_t*)(((char*)cmd) + 16); + void* entry = (void*)(registers->__pc + fSlide); + #elif __arm64__ + const arm_thread_state64_t* registers = (arm_thread_state64_t*)(((char*)cmd) + 16); + void* entry = (void*)(registers->__pc + fSlide); #else #warning need processor specific code #endif + // verify entry point is in image + if ( this->containsAddress(entry) ) { + return entry; + } } break; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - return NULL; + throw "no valid entry point"; } - -uint32_t ImageLoaderMachO::doGetDependentLibraryCount() +bool ImageLoaderMachO::needsAddedLibSystemDepency(unsigned int libCount, const macho_header* mh) { - const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; - const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; - uint32_t count = 0; + // ensure that every image depends on something which depends on libSystem + if ( libCount > 1 ) + return false; + + // dyld implicit-libSystem breaks valgrind + if ( mh->filetype == MH_EXECUTE ) + return false; + + bool isNonOSdylib = false; + const uint32_t cmd_count = mh->ncmds; + const struct load_command* const cmds = (struct load_command*)((uint8_t*)mh+sizeof(macho_header)); const struct load_command* cmd = cmds; - for (unsigned long i = 0; i < cmd_count; ++i) { + for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { case LC_LOAD_DYLIB: case LC_LOAD_WEAK_DYLIB: - ++count; + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + return false; + case LC_ID_DYLIB: + { + const dylib_command* dylibID = (dylib_command*)cmd; + const char* installPath = (char*)cmd + dylibID->dylib.name.offset; + // It is OK for OS dylibs (libSystem or libmath or Rosetta shims) to have no dependents + // but all other dylibs must depend on libSystem for initialization to initialize libSystem first + // rosetta circular dependency spew + isNonOSdylib = ( (strncmp(installPath, "/usr/lib/", 9) != 0) && (strncmp(installPath, "/usr/libexec/oah/Shims", 9) != 0) ); + } break; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - return count; + return isNonOSdylib; } -void ImageLoaderMachO::doGetDependentLibraries(DependentLibrary libs[]) + +void ImageLoaderMachO::doGetDependentLibraries(DependentLibraryInfo libs[]) { - uint32_t index = 0; - const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; - const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; - const struct load_command* cmd = cmds; - for (unsigned long i = 0; i < cmd_count; ++i) { - switch (cmd->cmd) { - case LC_LOAD_DYLIB: - case LC_LOAD_WEAK_DYLIB: - { - const struct dylib_command* dylib = (struct dylib_command*)cmd; - DependentLibrary* lib = &libs[index++]; - lib->name = (char*)cmd + dylib->dylib.name.offset; - //lib->name = strdup((char*)cmd + dylib->dylib.name.offset); - lib->image = NULL; - lib->info.checksum = dylib->dylib.timestamp; - lib->info.minVersion = dylib->dylib.compatibility_version; - lib->info.maxVersion = dylib->dylib.current_version; - lib->required = (cmd->cmd == LC_LOAD_DYLIB); - lib->checksumMatches = false; - lib->isReExported = false; + if ( needsAddedLibSystemDepency(libraryCount(), (macho_header*)fMachOData) ) { + DependentLibraryInfo* lib = &libs[0]; + lib->name = "/usr/lib/libSystem.B.dylib"; + lib->info.checksum = 0; + lib->info.minVersion = 0; + lib->info.maxVersion = 0; + lib->required = false; + lib->reExported = false; + lib->upward = false; + } + else { + uint32_t index = 0; + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + { + const struct dylib_command* dylib = (struct dylib_command*)cmd; + DependentLibraryInfo* lib = &libs[index++]; + lib->name = (char*)cmd + dylib->dylib.name.offset; + //lib->name = strdup((char*)cmd + dylib->dylib.name.offset); + lib->info.checksum = dylib->dylib.timestamp; + lib->info.minVersion = dylib->dylib.compatibility_version; + lib->info.maxVersion = dylib->dylib.current_version; + lib->required = (cmd->cmd != LC_LOAD_WEAK_DYLIB); + lib->reExported = (cmd->cmd == LC_REEXPORT_DYLIB); + lib->upward = (cmd->cmd == LC_LOAD_UPWARD_DYLIB); + } + break; } - break; + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } } ImageLoader::LibraryInfo ImageLoaderMachO::doGetLibraryInfo() { LibraryInfo info; - if ( fDylibID != NULL ) { - info.minVersion = fDylibID->dylib.compatibility_version; - info.maxVersion = fDylibID->dylib.current_version; - info.checksum = fDylibID->dylib.timestamp; + if ( fDylibIDOffset != 0 ) { + const dylib_command* dylibID = (dylib_command*)(&fMachOData[fDylibIDOffset]); + info.minVersion = dylibID->dylib.compatibility_version; + info.maxVersion = dylibID->dylib.current_version; + info.checksum = dylibID->dylib.timestamp; } else { info.minVersion = 0; @@ -1176,399 +1191,271 @@ ImageLoader::LibraryInfo ImageLoaderMachO::doGetLibraryInfo() return info; } - -uintptr_t ImageLoaderMachO::getRelocBase() +void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vector& paths) const { - if ( fIsSplitSeg ) { - // in split segment libraries r_address is offset from first writable segment - const unsigned int segmentCount = fSegments.size(); - for(unsigned int i=0; i < segmentCount; ++i){ - Segment* seg = fSegments[i]; - if ( seg->writeable() ) { - return seg->getActualLoadAddress(); - } + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_RPATH: + const char* pathToAdd = NULL; + const char* path = (char*)cmd + ((struct rpath_command*)cmd)->path.offset; + if ( (strncmp(path, "@loader_path", 12) == 0) && ((path[12] == '/') || (path[12] == '\0')) ) { + if ( context.processIsRestricted && !context.processRequiresLibraryValidation && (context.mainExecutable == this) ) { + dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @loader_path\n", path, this->getPath()); + break; + } + char resolvedPath[PATH_MAX]; + if ( realpath(this->getPath(), resolvedPath) != NULL ) { + char newRealPath[strlen(resolvedPath) + strlen(path)]; + strcpy(newRealPath, resolvedPath); + char* addPoint = strrchr(newRealPath,'/'); + if ( addPoint != NULL ) { + strcpy(addPoint, &path[12]); + pathToAdd = strdup(newRealPath); + } + } + } + else if ( (strncmp(path, "@executable_path", 16) == 0) && ((path[16] == '/') || (path[16] == '\0')) ) { + if ( context.processIsRestricted && !context.processRequiresLibraryValidation ) { + dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @executable_path\n", path, this->getPath()); + break; + } + char resolvedPath[PATH_MAX]; + if ( realpath(context.mainExecutable->getPath(), resolvedPath) != NULL ) { + char newRealPath[strlen(resolvedPath) + strlen(path)]; + strcpy(newRealPath, resolvedPath); + char* addPoint = strrchr(newRealPath,'/'); + if ( addPoint != NULL ) { + strcpy(addPoint, &path[16]); + pathToAdd = strdup(newRealPath); + } + } + } + else if ( (path[0] != '/') && context.processIsRestricted && !context.processRequiresLibraryValidation ) { + dyld::warn("LC_RPATH %s in %s being ignored in restricted program because it is a relative path\n", path, this->getPath()); + break; + } + else if ( (path[0] == '/') && (context.rootPaths != NULL) ) { + // DYLD_ROOT_PATH should apply to LC_RPATH rpaths + // DYLD_ROOT_PATH can be a list of paths, but at this point we can only support one, so use first combination that exists + bool found = false; + for(const char** rp = context.rootPaths; *rp != NULL; ++rp) { + char newPath[PATH_MAX]; + strlcpy(newPath, *rp, PATH_MAX); + strlcat(newPath, path, PATH_MAX); + struct stat stat_buf; + if ( stat(newPath, &stat_buf) != -1 ) { + //dyld::log("combined DYLD_ROOT_PATH and LC_RPATH: %s\n", newPath); + pathToAdd = strdup(newPath); + found = true; + break; + } + } + if ( ! found ) { + // make copy so that all elements of 'paths' can be freed + pathToAdd = strdup(path); + } + } + else { + // make copy so that all elements of 'paths' can be freed + pathToAdd = strdup(path); + } + if ( pathToAdd != NULL ) + paths.push_back(pathToAdd); + break; } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - - // in non-split segment libraries r_address is offset from first segment - return fSegments[0]->getActualLoadAddress(); } -#if __ppc__ -static inline void otherRelocsPPC(uintptr_t* locationToFix, uint8_t relocationType, uint16_t otherHalf, uintptr_t slide) + +bool ImageLoaderMachO::getUUID(uuid_t uuid) const { - // low 16 bits of 32-bit ppc instructions need fixing - struct ppcInstruction { uint16_t opcode; int16_t immediateValue; }; - ppcInstruction* instruction = (ppcInstruction*)locationToFix; - //uint32_t before = *((uint32_t*)locationToFix); - switch ( relocationType ) - { - case PPC_RELOC_LO16: - instruction->immediateValue = ((otherHalf << 16) | instruction->immediateValue) + slide; - break; - case PPC_RELOC_HI16: - instruction->immediateValue = ((((instruction->immediateValue << 16) | otherHalf) + slide) >> 16); - break; - case PPC_RELOC_HA16: - int16_t signedOtherHalf = (int16_t)(otherHalf & 0xffff); - uint32_t temp = (instruction->immediateValue << 16) + signedOtherHalf + slide; - if ( (temp & 0x00008000) != 0 ) - temp += 0x00008000; - instruction->immediateValue = temp >> 16; + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[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; + memcpy(uuid, uc->uuid, 16); + return true; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - //uint32_t after = *((uint32_t*)locationToFix); - //fprintf(stderr, "dyld: ppc fixup %0p type %d from 0x%08X to 0x%08X\n", locationToFix, relocationType, before, after); + bzero(uuid, 16); + return false; } -#endif void ImageLoaderMachO::doRebase(const LinkContext& context) { // if prebound and loaded at prebound address, then no need to rebase - // Note: you might think that the check for allDependentLibrariesAsWhenPreBound() is not needed - // but it is. If a dependent library changed, this image's lazy pointers into that library - // need to be updated (reset back to lazy binding handler). That work is done most easily - // here because there is a PPC_RELOC_PB_LA_PTR reloc record for each lazy pointer. - if ( this->usablePrebinding(context) && this->usesTwoLevelNameSpace() ) { - // skip rebasing cause prebound and prebinding not disabled + if ( this->usablePrebinding(context) ) { + // skip rebasing because prebinding is valid ++fgImagesWithUsedPrebinding; // bump totals for statistics return; } - + // print why prebinding was not used if ( context.verbosePrebinding ) { if ( !this->isPrebindable() ) { - fprintf(stderr, "dyld: image not prebound, so could not use prebinding in %s\n", this->getPath()); + dyld::log("dyld: image not prebound, so could not use prebinding in %s\n", this->getPath()); } else if ( fSlide != 0 ) { - fprintf(stderr, "dyld: image slid, so could not use prebinding in %s\n", this->getPath()); + dyld::log("dyld: image slid, so could not use prebinding in %s\n", this->getPath()); } else if ( !this->allDependentLibrariesAsWhenPreBound() ) { - fprintf(stderr, "dyld: dependent libraries changed, so could not use prebinding in %s\n", this->getPath()); + dyld::log("dyld: dependent libraries changed, so could not use prebinding in %s\n", this->getPath()); } else if ( !this->usesTwoLevelNameSpace() ){ - fprintf(stderr, "dyld: image uses flat-namespace so, parts of prebinding ignored %s\n", this->getPath()); + dyld::log("dyld: image uses flat-namespace so, parts of prebinding ignored %s\n", this->getPath()); } else { - fprintf(stderr, "dyld: environment variable disabled use of prebinding in %s\n", this->getPath()); + dyld::log("dyld: environment variable disabled use of prebinding in %s\n", this->getPath()); } } - // if there are __TEXT fixups, temporarily make __TEXT writable - if ( fTextSegmentWithFixups != NULL ) - fTextSegmentWithFixups->tempWritable(); - - // cache this value that is used in the following loop - register const uintptr_t slide = this->fSlide; - - // loop through all local (internal) relocation records - const uintptr_t relocBase = this->getRelocBase(); - const relocation_info* const relocsStart = (struct relocation_info*)(&fLinkEditBase[fDynamicInfo->locreloff]); - const relocation_info* const relocsEnd = &relocsStart[fDynamicInfo->nlocrel]; - for (const relocation_info* reloc=relocsStart; reloc < relocsEnd; ++reloc) { - if ( (reloc->r_address & R_SCATTERED) == 0 ) { - if ( reloc->r_symbolnum == R_ABS ) { - // ignore absolute relocations - } - else if (reloc->r_length == RELOC_SIZE) { - switch(reloc->r_type) { - case GENERIC_RELOC_VANILLA: - *((uintptr_t*)(reloc->r_address + relocBase)) += slide; - break; - #if __ppc__ - case PPC_RELOC_HI16: - case PPC_RELOC_LO16: - case PPC_RELOC_HA16: - // some tools leave object file relocations in linked images - otherRelocsPPC((uintptr_t*)(reloc->r_address + relocBase), reloc->r_type, reloc[1].r_address, slide); - ++reloc; // these relocations come in pairs, skip next - break; - #endif - default: - throw "unknown local relocation type"; - } - } - else { - throw "bad local relocation length"; - } - } - else { - const struct scattered_relocation_info* sreloc = (struct scattered_relocation_info*)reloc; - if (sreloc->r_length == RELOC_SIZE) { - uintptr_t* locationToFix = (uintptr_t*)(sreloc->r_address + relocBase); - switch(sreloc->r_type) { - case GENERIC_RELOC_VANILLA: - *locationToFix += slide; - break; - #if __ppc__ || __ppc64__ - case PPC_RELOC_PB_LA_PTR: - // should only see these in prebound images, and we got here so prebinding is being ignored - *locationToFix = sreloc->r_value + slide; - break; - #endif - #if __ppc__ - case PPC_RELOC_HI16: - case PPC_RELOC_LO16: - case PPC_RELOC_HA16: - // Metrowerks compiler sometimes leaves object file relocations in linked images??? - ++reloc; // these relocations come in pairs, get next one - otherRelocsPPC(locationToFix, sreloc->r_type, reloc->r_address, slide); - break; - #endif - #if __i386__ - case GENERIC_RELOC_PB_LA_PTR: - // should only see these in prebound images, and we got here so prebinding is being ignored - *locationToFix = sreloc->r_value + slide; - break; - #endif - default: - throw "unknown local scattered relocation type"; - } - } - else { - throw "bad local scattered relocation length"; - } - } - } - - // if there were __TEXT fixups, restore write protection - if ( fTextSegmentWithFixups != NULL ) { - fTextSegmentWithFixups->setPermissions(); - sys_icache_invalidate((void*)fTextSegmentWithFixups->getActualLoadAddress(), fTextSegmentWithFixups->getSize()); - } - - // update stats - fgTotalRebaseFixups += fDynamicInfo->nlocrel; -} + //dyld::log("slide=0x%08lX for %s\n", slide, this->getPath()); +#if PREBOUND_IMAGE_SUPPORT + // if prebound and we got here, then prebinding is not valid, so reset all lazy pointers + // if this image is in the shared cache, do not reset, they will be bound in doBind() + if ( this->isPrebindable() && !fInSharedCache ) + this->resetPreboundLazyPointers(context); +#endif -const struct macho_nlist* ImageLoaderMachO::binarySearchWithToc(const char* key, const char stringPool[], const struct macho_nlist symbols[], - const struct dylib_table_of_contents toc[], uint32_t symbolCount, uint32_t hintIndex) -{ - int32_t high = symbolCount-1; - int32_t mid = hintIndex; - - // handle out of range hint - if ( mid >= (int32_t)symbolCount ) { - mid = symbolCount/2; - ++ImageLoaderMachO::fgUnhintedBinaryTreeSearchs; - } - else { - ++ImageLoaderMachO::fgHintedBinaryTreeSearchs; - } + // if loaded at preferred address, no rebasing necessary + if ( this->fSlide == 0 ) + return; - for (int32_t low = 0; low <= high; mid = (low+high)/2) { - const uint32_t index = toc[mid].symbol_index; - const struct macho_nlist* pivot = &symbols[index]; - const char* pivotStr = &stringPool[pivot->n_un.n_strx]; -#if LINKEDIT_USAGE_DEBUG - noteAccessedLinkEditAddress(&toc[mid]); - noteAccessedLinkEditAddress(pivot); - noteAccessedLinkEditAddress(pivotStr); +#if TEXT_RELOC_SUPPORT + // if there are __TEXT fixups, temporarily make __TEXT writable + if ( fTextSegmentRebases ) + this->makeTextSegmentWritable(context, true); #endif - int cmp = astrcmp(key, pivotStr); - if ( cmp == 0 ) - return pivot; - if ( cmp > 0 ) { - // key > pivot - low = mid + 1; - } - else { - // key < pivot - high = mid - 1; - } - } - return NULL; + + // do actual rebasing + this->rebase(context); + +#if TEXT_RELOC_SUPPORT + // if there were __TEXT fixups, restore write protection + if ( fTextSegmentRebases ) + this->makeTextSegmentWritable(context, false); + +#endif } -const struct macho_nlist* ImageLoaderMachO::binarySearch(const char* key, const char stringPool[], const struct macho_nlist symbols[], uint32_t symbolCount) +#if TEXT_RELOC_SUPPORT +void ImageLoaderMachO::makeTextSegmentWritable(const LinkContext& context, bool writeable) { - ++ImageLoaderMachO::fgUnhintedBinaryTreeSearchs; - const struct macho_nlist* base = symbols; - for (uint32_t n = symbolCount; n > 0; n /= 2) { - const struct macho_nlist* pivot = &base[n/2]; - const char* pivotStr = &stringPool[pivot->n_un.n_strx]; -#if LINKEDIT_USAGE_DEBUG - noteAccessedLinkEditAddress(pivot); - noteAccessedLinkEditAddress(pivotStr); -#endif - int cmp = astrcmp(key, pivotStr); - if ( cmp == 0 ) - return pivot; - if ( cmp > 0 ) { - // key > pivot - // move base to symbol after pivot - base = &pivot[1]; - --n; - } - else { - // key < pivot - // keep same base + for(unsigned int i=0; i < fSegmentsCount; ++i) { + if ( segExecutable(i) ) { + if ( writeable ) { + segMakeWritable(i, context); + } + else { + #if !__i386__ && !__x86_64__ + // some processors require range to be invalidated before it is made executable + sys_icache_invalidate((void*)segActualLoadAddress(i), segSize(textSegmentIndex)); + #endif + segProtect(i, context); + } } } - return NULL; + } +#endif -const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name, const void* hint, bool searchReExports, ImageLoader** foundIn) const +const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name, bool searchReExports, const ImageLoader** foundIn) const { - const struct macho_nlist* sym = NULL; - const struct twolevel_hint* theHint = (struct twolevel_hint*)hint; - if ( fDynamicInfo->tocoff == 0 ) - sym = binarySearch(name, fStrings, &fSymbolTable[fDynamicInfo->iextdefsym], fDynamicInfo->nextdefsym); - else { - uint32_t start = fDynamicInfo->nextdefsym; - if ( theHint != NULL ) - start = theHint->itoc; - if ( (theHint == NULL) || (theHint->isub_image == 0) ) { - sym = binarySearchWithToc(name, fStrings, fSymbolTable, (dylib_table_of_contents*)&fLinkEditBase[fDynamicInfo->tocoff], - fDynamicInfo->ntoc, start); - } - } - if ( sym != NULL ) { - if ( foundIn != NULL ) - *foundIn = (ImageLoader*)this; - - return (const Symbol*)sym; - } + // look in this image first + const ImageLoader::Symbol* result = this->findExportedSymbol(name, foundIn); + if ( result != NULL ) + return result; if ( searchReExports ) { - // hint might tell us to try a particular subimage - if ( (theHint != NULL) && (theHint->isub_image > 0) && (theHint->isub_image <= fLibrariesCount) ) { - // isub_image is an index into a list that is sorted non-rexported images first - uint32_t index = 0; - ImageLoader* target = NULL; - // pass one, only look at sub-frameworks - for (uint32_t i=0; i < fLibrariesCount; ++i) { - DependentLibrary& libInfo = fLibraries[i]; - if ( libInfo.isSubFramework && (libInfo.image != NULL)) { - if ( ++index == theHint->isub_image ) { - target = libInfo.image; - break; - } - } - } - if (target != NULL) { - // pass two, only look at non-sub-framework-reexports - for (uint32_t i=0; i < fLibrariesCount; ++i) { - DependentLibrary& libInfo = fLibraries[i]; - if ( libInfo.isReExported && !libInfo.isSubFramework && (libInfo.image != NULL) ) { - if ( ++index == theHint->isub_image ) { - target = libInfo.image; - break; - } - } + for(unsigned int i=0; i < libraryCount(); ++i){ + if ( libReExported(i) ) { + ImageLoader* image = libImage(i); + if ( image != NULL ) { + const Symbol* result = image->findExportedSymbol(name, searchReExports, foundIn); + if ( result != NULL ) + return result; } } - if (target != NULL) { - const Symbol* result = target->findExportedSymbol(name, NULL, searchReExports, foundIn); - if ( result != NULL ) - return result; - } - } - - // hint failed, try all sub images - // pass one, only look at sub-frameworks - for(unsigned int i=0; i < fLibrariesCount; ++i){ - DependentLibrary& libInfo = fLibraries[i]; - if ( (libInfo.image != NULL) && libInfo.isSubFramework ) { - const Symbol* result = libInfo.image->findExportedSymbol(name, NULL, searchReExports, foundIn); - if ( result != NULL ) - return result; - } - } - // pass two, only look at non-sub-framework-reexports - for(unsigned int i=0; i < fLibrariesCount; ++i){ - DependentLibrary& libInfo = fLibraries[i]; - if ( (libInfo.image != NULL) && libInfo.isReExported && !libInfo.isSubFramework ) { - const Symbol* result = libInfo.image->findExportedSymbol(name, NULL, searchReExports, foundIn); - if ( result != NULL ) - return result; - } } } - // last change: the hint is wrong (non-zero but actually in this image) - if ( (theHint != NULL) && (theHint->isub_image != 0) ) { - sym = binarySearchWithToc(name, fStrings, fSymbolTable, (dylib_table_of_contents*)&fLinkEditBase[fDynamicInfo->tocoff], - fDynamicInfo->ntoc, fDynamicInfo->nextdefsym); - if ( sym != NULL ) { - if ( foundIn != NULL ) - *foundIn = (ImageLoader*)this; - return (const Symbol*)sym; - } - } return NULL; } -uintptr_t ImageLoaderMachO::getExportedSymbolAddress(const Symbol* sym) const + +uintptr_t ImageLoaderMachO::getExportedSymbolAddress(const Symbol* sym, const LinkContext& context, + const ImageLoader* requestor, bool runResolver) const +{ + return this->getSymbolAddress(sym, requestor, context, runResolver); +} + +uintptr_t ImageLoaderMachO::getSymbolAddress(const Symbol* sym, const ImageLoader* requestor, + const LinkContext& context, bool runResolver) const { - const struct macho_nlist* nlistSym = (const struct macho_nlist*)sym; - return nlistSym->n_value + fSlide; + uintptr_t result = exportedSymbolAddress(context, sym, requestor, runResolver); + // check for interposing overrides + result = interposedAddress(context, result, requestor); + return result; } ImageLoader::DefinitionFlags ImageLoaderMachO::getExportedSymbolInfo(const Symbol* sym) const { - const struct macho_nlist* nlistSym = (const struct macho_nlist*)sym; - if ( (nlistSym->n_desc & N_WEAK_DEF) != 0 ) + if ( exportedSymbolIsWeakDefintion(sym) ) return kWeakDefinition; - return kNoDefinitionOptions; + else + return kNoDefinitionOptions; } const char* ImageLoaderMachO::getExportedSymbolName(const Symbol* sym) const { - const struct macho_nlist* nlistSym = (const struct macho_nlist*)sym; - return &fStrings[nlistSym->n_un.n_strx]; + return exportedSymbolName(sym); } uint32_t ImageLoaderMachO::getExportedSymbolCount() const { - return fDynamicInfo->nextdefsym; + return exportedSymbolCount(); } const ImageLoader::Symbol* ImageLoaderMachO::getIndexedExportedSymbol(uint32_t index) const { - if ( index < fDynamicInfo->nextdefsym ) { - const struct macho_nlist* sym = &fSymbolTable[fDynamicInfo->iextdefsym + index]; - return (const ImageLoader::Symbol*)sym; - } - return NULL; + return exportedSymbolIndexed(index); } uint32_t ImageLoaderMachO::getImportedSymbolCount() const { - return fDynamicInfo->nundefsym; + return importedSymbolCount(); } const ImageLoader::Symbol* ImageLoaderMachO::getIndexedImportedSymbol(uint32_t index) const { - if ( index < fDynamicInfo->nundefsym ) { - const struct macho_nlist* sym = &fSymbolTable[fDynamicInfo->iundefsym + index]; - return (const ImageLoader::Symbol*)sym; - } - return NULL; + return importedSymbolIndexed(index); } -ImageLoader::ReferenceFlags ImageLoaderMachO::geImportedSymbolInfo(const ImageLoader::Symbol* sym) const +ImageLoader::ReferenceFlags ImageLoaderMachO::getImportedSymbolInfo(const ImageLoader::Symbol* sym) const { - const struct macho_nlist* nlistSym = (const struct macho_nlist*)sym; ImageLoader::ReferenceFlags flags = kNoReferenceOptions; - if ( ((nlistSym->n_type & N_TYPE) == N_UNDF) && (nlistSym->n_value != 0) ) - flags |= ImageLoader::kTentativeDefinition; - if ( (nlistSym->n_desc & N_WEAK_REF) != 0 ) - flags |= ImageLoader::kWeakReference; return flags; } const char* ImageLoaderMachO::getImportedSymbolName(const ImageLoader::Symbol* sym) const { - const struct macho_nlist* nlistSym = (const struct macho_nlist*)sym; - return &fStrings[nlistSym->n_un.n_strx]; + return importedSymbolName(sym); } @@ -1596,9 +1483,30 @@ bool ImageLoaderMachO::getSectionContent(const char* segmentName, const char* se } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } + *start = NULL; + *length = 0; return false; } +void ImageLoaderMachO::getUnwindInfo(dyld_unwind_sections* info) +{ + info->mh = this->machHeader(); + info->dwarf_section = 0; + info->dwarf_section_length = 0; + info->compact_unwind_section = 0; + info->compact_unwind_section_length = 0; + if ( fEHFrameSectionOffset != 0 ) { + const macho_section* sect = (macho_section*)&fMachOData[fEHFrameSectionOffset]; + info->dwarf_section = (void*)(sect->addr + fSlide); + info->dwarf_section_length = sect->size; + } + if ( fUnwindInfoSectionOffset != 0 ) { + const macho_section* sect = (macho_section*)&fMachOData[fUnwindInfoSectionOffset]; + info->compact_unwind_section = (void*)(sect->addr + fSlide); + info->compact_unwind_section_length = sect->size; + } +} + bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset) { @@ -1635,278 +1543,14 @@ bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segme } -bool ImageLoaderMachO::symbolRequiresCoalescing(const struct macho_nlist* symbol) -{ - // if a define and weak ==> coalesced - if ( ((symbol->n_type & N_TYPE) == N_SECT) && ((symbol->n_desc & N_WEAK_DEF) != 0) ) - return true; - // if an undefine and not referencing a weak symbol ==> coalesced - if ( ((symbol->n_type & N_TYPE) != N_SECT) && ((symbol->n_desc & N_REF_TO_WEAK) != 0) ) - return true; - - // regular symbol - return false; -} - - -static void __attribute__((noreturn)) throwSymbolNotFound(const char* symbol, const char* referencedFrom, const char* expectedIn) -{ - const char* formatString = "Symbol not found: %s\n Referenced from: %s\n Expected in: %s\n"; - char buf[strlen(symbol)+strlen(referencedFrom)+strlen(expectedIn)+strlen(formatString)]; - sprintf(buf, formatString, symbol, referencedFrom, expectedIn); - throw strdup(buf); // this is a leak if exception doesn't halt program -} - -uintptr_t ImageLoaderMachO::resolveUndefined(const LinkContext& context, const struct macho_nlist* undefinedSymbol, bool twoLevel, ImageLoader** foundIn) -{ - const char* symbolName = &fStrings[undefinedSymbol->n_un.n_strx]; - - if ( context.bindFlat || !twoLevel ) { - // flat lookup - const Symbol* sym; - if ( context.flatExportFinder(symbolName, &sym, foundIn) ) - return (*foundIn)->getExportedSymbolAddress(sym); - // if a bundle is loaded privately the above will not find its exports - if ( this->isBundle() && this->hasHiddenExports() ) { - // look in self for needed symbol - sym = this->findExportedSymbol(symbolName, NULL, false, foundIn); - if ( sym != NULL ) - return (*foundIn)->getExportedSymbolAddress(sym); - } - if ( ((undefinedSymbol->n_type & N_PEXT) != 0) || ((undefinedSymbol->n_type & N_TYPE) == N_SECT) ) { - // could be a multi-module private_extern internal reference - // the static linker squirrels away the target address in n_value - uintptr_t addr = undefinedSymbol->n_value + this->fSlide; - *foundIn = this; - return addr; - } - if ( (undefinedSymbol->n_desc & N_WEAK_REF) != 0 ) { - // definition can't be found anywhere - // if reference is weak_import, then it is ok, just return 0 - return 0; - } - throwSymbolNotFound(symbolName, this->getPath(), "flat namespace"); - } - else { - // symbol requires searching images with coalesced symbols - if ( this->needsCoalescing() && symbolRequiresCoalescing(undefinedSymbol) ) { - const Symbol* sym; - if ( context.coalescedExportFinder(symbolName, &sym, foundIn) ) - return (*foundIn)->getExportedSymbolAddress(sym); - //throwSymbolNotFound(symbolName, this->getPath(), "coalesced namespace"); - //fprintf(stderr, "dyld: coalesced symbol %s not found in any coalesced image, falling back to two-level lookup", symbolName); - } - - // two level lookup - void* hint = NULL; - ImageLoader* target = NULL; - uint8_t ord = GET_LIBRARY_ORDINAL(undefinedSymbol->n_desc); - if ( ord == EXECUTABLE_ORDINAL ) { - target = context.mainExecutable; - } - else if ( ord == SELF_LIBRARY_ORDINAL ) { - target = this; - } - else if ( ord == DYNAMIC_LOOKUP_ORDINAL ) { - // rnielsen: HACKHACK - // flat lookup - const Symbol* sym; - if ( context.flatExportFinder(symbolName, &sym, foundIn) ) - return (*foundIn)->getExportedSymbolAddress(sym); - // no image has exports this symbol - // either report error or hope ZeroLink can just-in-time load an image - context.undefinedHandler(symbolName); - // try looking again - if ( context.flatExportFinder(symbolName, &sym, foundIn) ) - return (*foundIn)->getExportedSymbolAddress(sym); - - throwSymbolNotFound(symbolName, this->getPath(), "dynamic lookup"); - } - else if ( ord <= fLibrariesCount ) { - DependentLibrary& libInfo = fLibraries[ord-1]; - target = libInfo.image; - if ( (target == NULL) && (((undefinedSymbol->n_desc & N_WEAK_REF) != 0) || !libInfo.required) ) { - // if target library not loaded and reference is weak or library is weak return 0 - return 0; - } - } - else { - throw "corrupt binary, library ordinal too big"; - } - - if ( target == NULL ) { - fprintf(stderr, "resolveUndefined(%s) in %s\n", symbolName, this->getPath()); - throw "symbol not found"; - } - - // interpret hint - if ( fTwoLevelHints != NULL ) { - uint32_t symIndex = undefinedSymbol - fSymbolTable; - int32_t undefinedIndex = symIndex - fDynamicInfo->iundefsym; - if ( (undefinedIndex >= 0) && ((uint32_t)undefinedIndex < fDynamicInfo->nundefsym) ) { - const struct twolevel_hint* hints = (struct twolevel_hint*)(&fLinkEditBase[fTwoLevelHints->offset]); - const struct twolevel_hint* theHint = &hints[undefinedIndex]; - hint = (void*)theHint; - } - } - - const Symbol* sym = target->findExportedSymbol(symbolName, hint, true, foundIn); - if ( sym!= NULL ) { - return (*foundIn)->getExportedSymbolAddress(sym); - } - else if ( (undefinedSymbol->n_type & N_PEXT) != 0 ) { - // don't know why the static linker did not eliminate the internal reference to a private extern definition - *foundIn = this; - return undefinedSymbol->n_value + fSlide; - } - else if ( (undefinedSymbol->n_desc & N_WEAK_REF) != 0 ) { - // if definition not found and reference is weak return 0 - return 0; - } - - // nowhere to be found - throwSymbolNotFound(symbolName, this->getPath(), target->getPath()); - } -} - -// returns if 'addr' is within the address range of section 'sectionIndex' -// fSlide is not used. 'addr' is assumed to be a prebound address in this image -bool ImageLoaderMachO::isAddrInSection(uintptr_t addr, uint8_t sectionIndex) -{ - uint8_t currentSectionIndex = 1; - const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; - const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; - const struct load_command* cmd = cmds; - for (unsigned long i = 0; i < cmd_count; ++i) { - if ( cmd->cmd == LC_SEGMENT_COMMAND ) { - const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; - if ( (currentSectionIndex <= sectionIndex) && (sectionIndex < currentSectionIndex+seg->nsects) ) { - // 'sectionIndex' is in this segment, get section info - const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); - const struct macho_section* const section = §ionsStart[sectionIndex-currentSectionIndex]; - return ( (section->addr <= addr) && (addr < section->addr+section->size) ); - } - else { - // 'sectionIndex' not in this segment, skip to next segment - currentSectionIndex += seg->nsects; - } - } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); - } - - return false; -} - -void ImageLoaderMachO::doBindExternalRelocations(const LinkContext& context, bool onlyCoalescedSymbols) +void __attribute__((noreturn)) ImageLoaderMachO::throwSymbolNotFound(const LinkContext& context, const char* symbol, + const char* referencedFrom, const char* fromVersMismatch, + const char* expectedIn) { - const uintptr_t relocBase = this->getRelocBase(); - const bool twoLevel = this->usesTwoLevelNameSpace(); - const bool prebound = this->isPrebindable(); - - // if there are __TEXT fixups, temporarily make __TEXT writable - if ( fTextSegmentWithFixups != NULL ) - fTextSegmentWithFixups->tempWritable(); - - // cache last lookup - const struct macho_nlist* lastUndefinedSymbol = 0; - uintptr_t symbolAddr = 0; - ImageLoader* image = NULL; - - // loop through all external relocation records and bind each - const relocation_info* const relocsStart = (struct relocation_info*)(&fLinkEditBase[fDynamicInfo->extreloff]); - const relocation_info* const relocsEnd = &relocsStart[fDynamicInfo->nextrel]; - for (const relocation_info* reloc=relocsStart; reloc < relocsEnd; ++reloc) { - if (reloc->r_length == RELOC_SIZE) { - switch(reloc->r_type) { - case GENERIC_RELOC_VANILLA: - { - const struct macho_nlist* undefinedSymbol = &fSymbolTable[reloc->r_symbolnum]; - // if only processing coalesced symbols and this one does not require coalesceing, skip to next - if ( onlyCoalescedSymbols && !symbolRequiresCoalescing(undefinedSymbol) ) - continue; - uintptr_t* location = ((uintptr_t*)(reloc->r_address + relocBase)); - uintptr_t value = *location; - #if __i386__ - if ( reloc->r_pcrel ) { - value += (uintptr_t)location + 4 - fSlide; - } - #endif - if ( prebound ) { - // we are doing relocations, so prebinding was not usable - // in a prebound executable, the n_value field of an undefined symbol is set to the address where the symbol was found when prebound - // so, subtracting that gives the initial displacement which we need to add to the newly found symbol address - // if mach-o relocation structs had an "addend" field this complication would not be necessary. - if ( ((undefinedSymbol->n_type & N_TYPE) == N_SECT) && ((undefinedSymbol->n_desc & N_WEAK_DEF) != 0) ) { - // weak symbols need special casing, since *location may have been prebound to a definition in another image. - // If *location is currently prebound to somewhere in the same section as the weak definition, we assume - // that we can subtract off the weak symbol address to get the addend. - // If prebound elsewhere, we've lost the addend and have to assume it is zero. - // The prebinding to elsewhere only happens with 10.4+ update_prebinding which only operates on a small set of Apple dylibs - if ( (value == undefinedSymbol->n_value) || this->isAddrInSection(value, undefinedSymbol->n_sect) ) - value -= undefinedSymbol->n_value; - else - value = 0; - } - else { - // is undefined or non-weak symbol, so do subtraction to get addend - value -= undefinedSymbol->n_value; - } - } - // if undefinedSymbol is same as last time, then symbolAddr and image will resolve to the same too - if ( undefinedSymbol != lastUndefinedSymbol ) { - symbolAddr = this->resolveUndefined(context, undefinedSymbol, twoLevel, &image); - lastUndefinedSymbol = undefinedSymbol; - } - if ( context.verboseBind ) { - const char *path = NULL; - if(NULL != image) { - path = image->getShortName(); - } - if(0 == value) { - fprintf(stderr, "dyld: bind: %s:0x%08lx = %s:%s, *0x%08lx = 0x%08lx\n", - this->getShortName(), (uintptr_t)location, - path, &fStrings[undefinedSymbol->n_un.n_strx], (uintptr_t)location, symbolAddr); - } - else { - fprintf(stderr, "dyld: bind: %s:0x%08lx = %s:%s, *0x%08lx = 0x%08lx + %ld\n", - this->getShortName(), (uintptr_t)location, - path, &fStrings[undefinedSymbol->n_un.n_strx], (uintptr_t)location, symbolAddr, value); - } - } - value += symbolAddr; - #if __i386__ - if ( reloc->r_pcrel ) { - *location = value - ((uintptr_t)location + 4); - } - else { - // don't dirty page if prebound value was correct - if ( !prebound || (*location != value) ) - *location = value; - } - #else - // don't dirty page if prebound value was correct - if ( !prebound || (*location != value) ) - *location = value; - #endif - } - break; - default: - throw "unknown external relocation type"; - } - } - else { - throw "bad external relocation length"; - } - } - - // if there were __TEXT fixups, restore write protection - if ( fTextSegmentWithFixups != NULL ) { - fTextSegmentWithFixups->setPermissions(); - sys_icache_invalidate((void*)fTextSegmentWithFixups->getActualLoadAddress(), fTextSegmentWithFixups->getSize()); - } - - // update stats - fgTotalBindFixups += fDynamicInfo->nextrel; + // record values for possible use by CrashReporter or Finder + (*context.setErrorStrings)(dyld_error_kind_symbol_missing, referencedFrom, expectedIn, symbol); + dyld::throwf("Symbol not found: %s\n Referenced from: %s%s\n Expected in: %s\n", + symbol, referencedFrom, fromVersMismatch, expectedIn); } const mach_header* ImageLoaderMachO::machHeader() const @@ -1920,233 +1564,212 @@ uintptr_t ImageLoaderMachO::getSlide() const } // hmm. maybe this should be up in ImageLoader?? -const void* ImageLoaderMachO::getBaseAddress() const +const void* ImageLoaderMachO::getEnd() const { - Segment* seg = fSegments[0]; - return (const void*)seg->getActualLoadAddress(); + uintptr_t lastAddress = 0; + for(unsigned int i=0; i < fSegmentsCount; ++i) { + uintptr_t segEnd = segActualEndAddress(i); + if ( strcmp(segName(i), "__UNIXSTACK") != 0 ) { + if ( segEnd > lastAddress ) + lastAddress = segEnd; + } + } + return (const void*)lastAddress; } -uintptr_t ImageLoaderMachO::doBindLazySymbol(uintptr_t* lazyPointer, const LinkContext& context) -{ - // scan for all non-lazy-pointer sections - const bool twoLevel = this->usesTwoLevelNameSpace(); - const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; - const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; - const struct load_command* cmd = cmds; - const uint32_t* const indirectTable = (uint32_t*)&fLinkEditBase[fDynamicInfo->indirectsymoff]; - 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; - 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) { - const uint8_t type = sect->flags & SECTION_TYPE; - if ( type == S_LAZY_SYMBOL_POINTERS ) { - const uint32_t pointerCount = sect->size / sizeof(uintptr_t); - uintptr_t* const symbolPointers = (uintptr_t*)(sect->addr + fSlide); - if ( (lazyPointer >= symbolPointers) && (lazyPointer < &symbolPointers[pointerCount]) ) { - const uint32_t indirectTableOffset = sect->reserved1; - const uint32_t lazyIndex = lazyPointer - symbolPointers; - uint32_t symbolIndex = indirectTable[indirectTableOffset + lazyIndex]; - if ( symbolIndex != INDIRECT_SYMBOL_ABS && symbolIndex != INDIRECT_SYMBOL_LOCAL ) { - ImageLoader *image = NULL; - const char *path = NULL; - uintptr_t symbolAddr = this->resolveUndefined(context, &fSymbolTable[symbolIndex], twoLevel, &image); - if ( context.verboseBind ) { - if(NULL == path && NULL != image) { - path = image->getShortName(); - } - fprintf(stderr, "dyld: bind: %s:%s$%s = %s:%s, *0x%08lx = 0x%08lx\n", - this->getShortName(), &fStrings[fSymbolTable[symbolIndex].n_un.n_strx], "lazy_ptr", - path, &fStrings[fSymbolTable[symbolIndex].n_un.n_strx], (uintptr_t)&symbolPointers[lazyIndex], symbolAddr); - } - if ( NULL != context.bindingHandler ) { - if(NULL == path && NULL != image) { - path = image->getPath(); - } - symbolAddr = (uintptr_t)context.bindingHandler(path, &fStrings[fSymbolTable[symbolIndex].n_un.n_strx], (void *)symbolAddr); - } - symbolPointers[lazyIndex] = symbolAddr; - // update stats - fgTotalLazyBindFixups++; - return symbolPointers[lazyIndex]; - } - } - } - } - } - break; - } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); +uintptr_t ImageLoaderMachO::bindLocation(const LinkContext& context, uintptr_t location, uintptr_t value, + const ImageLoader* targetImage, uint8_t type, const char* symbolName, + intptr_t addend, const char* msg) +{ + // log + if ( context.verboseBind ) { + if ( addend != 0 ) + dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX + %ld\n", + msg, this->getShortName(), (uintptr_t)location, + ((targetImage != NULL) ? targetImage->getShortName() : ""), + symbolName, (uintptr_t)location, value, addend); + else + dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX\n", + msg, this->getShortName(), (uintptr_t)location, + ((targetImage != NULL) ? targetImage->getShortName() : "import-missing>"), + symbolName, (uintptr_t)location, value); + } +#if LOG_BINDINGS +// dyld::logBindings("%s: %s\n", targetImage->getShortName(), symbolName); +#endif + + // do actual update + uintptr_t* locationToFix = (uintptr_t*)location; + uint32_t* loc32; + uintptr_t newValue = value+addend; + uint32_t value32; + switch (type) { + case BIND_TYPE_POINTER: + // test first so we don't needless dirty pages + if ( *locationToFix != newValue ) + *locationToFix = newValue; + break; + case BIND_TYPE_TEXT_ABSOLUTE32: + loc32 = (uint32_t*)locationToFix; + value32 = (uint32_t)newValue; + if ( *loc32 != value32 ) + *loc32 = value32; + break; + case BIND_TYPE_TEXT_PCREL32: + loc32 = (uint32_t*)locationToFix; + value32 = (uint32_t)(newValue - (((uintptr_t)locationToFix) + 4)); + if ( *loc32 != value32 ) + *loc32 = value32; + break; + default: + dyld::throwf("bad bind type %d", type); } - throw "lazy pointer not found"; + + // update statistics + ++fgTotalBindFixups; + + return newValue; } -void ImageLoaderMachO::doBindIndirectSymbolPointers(const LinkContext& context, BindingLaziness bindness, bool onlyCoalescedSymbols) + + +#if SUPPORT_OLD_CRT_INITIALIZATION +// first 16 bytes of "start" in crt1.o +#if __i386__ + static uint8_t sStandardEntryPointInstructions[16] = { 0x6a, 0x00, 0x89, 0xe5, 0x83, 0xe4, 0xf0, 0x83, 0xec, 0x10, 0x8b, 0x5d, 0x04, 0x89, 0x5c, 0x24 }; +#endif +#endif + +struct DATAdyld { + void* dyldLazyBinder; // filled in at launch by dyld to point into dyld to &stub_binding_helper + void* dyldFuncLookup; // filled in at launch by dyld to point into dyld to &_dyld_func_lookup + // the following only exist in main executables built for 10.5 or later + ProgramVars vars; +}; + +// These are defined in dyldStartup.s +extern "C" void stub_binding_helper(); + + +void ImageLoaderMachO::setupLazyPointerHandler(const LinkContext& context) { - // scan for all non-lazy-pointer sections - const bool twoLevel = this->usesTwoLevelNameSpace(); - const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const macho_header* mh = (macho_header*)fMachOData; + const uint32_t cmd_count = mh->ncmds; const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; - const struct load_command* cmd = cmds; - const uint32_t* const indirectTable = (uint32_t*)&fLinkEditBase[fDynamicInfo->indirectsymoff]; - 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; + const struct load_command* cmd; + // There used to be some optimizations to skip this section scan, but we need to handle the + // __dyld section in libdyld.dylib, so everything needs to be scanned for now. + // CrashTracer: 1,295 crashes in bash at bash: getenv + if ( true ) { + cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd == LC_SEGMENT_COMMAND ) { + const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; + if ( strncmp(seg->segname, "__DATA", 6) == 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) { - const uint8_t type = sect->flags & SECTION_TYPE; - const uint32_t pointerCount = sect->size / sizeof(uintptr_t); - if ( type == S_NON_LAZY_SYMBOL_POINTERS ) { - if ( (bindness == kLazyOnly) || (bindness == kLazyOnlyNoDependents) ) - continue; - } - else if ( type == S_LAZY_SYMBOL_POINTERS ) { - // process each symbol pointer in this section - fgTotalPossibleLazyBindFixups += pointerCount; - if ( bindness == kNonLazyOnly ) - continue; - } - else { - continue; - } - const uint32_t indirectTableOffset = sect->reserved1; - uintptr_t* const symbolPointers = (uintptr_t*)(sect->addr + fSlide); - for (uint32_t j=0; j < pointerCount; ++j) { - uint32_t symbolIndex = indirectTable[indirectTableOffset + j]; - if ( symbolIndex == INDIRECT_SYMBOL_LOCAL) { - symbolPointers[j] += this->fSlide; + if ( strcmp(sect->sectname, "__dyld" ) == 0 ) { + struct DATAdyld* dd = (struct DATAdyld*)(sect->addr + fSlide); + #if !__arm64__ && !__ARM_ARCH_7K__ + if ( sect->size > offsetof(DATAdyld, dyldLazyBinder) ) { + if ( dd->dyldLazyBinder != (void*)&stub_binding_helper ) + dd->dyldLazyBinder = (void*)&stub_binding_helper; } - else if ( symbolIndex == INDIRECT_SYMBOL_ABS) { - // do nothing since already has absolute address + #endif // !__arm64__ + if ( sect->size > offsetof(DATAdyld, dyldFuncLookup) ) { + if ( dd->dyldFuncLookup != (void*)&_dyld_func_lookup ) + dd->dyldFuncLookup = (void*)&_dyld_func_lookup; } - else { - const struct macho_nlist* sym = &fSymbolTable[symbolIndex]; - if ( symbolIndex == 0 ) { - // This could be rdar://problem/3534709 - if ( ((const macho_header*)fMachOData)->filetype == MH_EXECUTE ) { - static bool alreadyWarned = false; - if ( (sym->n_type & N_TYPE) != N_UNDF ) { - // The indirect table parallels the (non)lazy pointer sections. For - // instance, to find info about the fifth lazy pointer you look at the - // fifth entry in the indirect table. (try otool -Iv on a file). - // The entry in the indirect table contains an index into the symbol table. - - // The bug in ld caused the entry in the indirect table to be zero - // (instead of a magic value that means a local symbol). So, if the - // symbolIndex == 0, we may be encountering the bug, or 0 may be a valid - // symbol table index. The check I put in place is to see if the zero'th - // symbol table entry is an import entry (usually it is a local symbol - // definition). - if ( context.verboseWarnings && !alreadyWarned ) { - fprintf(stderr, "dyld: malformed executable '%s', skipping indirect symbol to %s\n", - this->getPath(), &fStrings[sym->n_un.n_strx]); - alreadyWarned = true; - } - continue; - } - } + if ( mh->filetype == MH_EXECUTE ) { + // there are two ways to get the program variables + if ( (sect->size > offsetof(DATAdyld, vars)) && (dd->vars.mh == mh) ) { + // some really old binaries have space for vars, but it is zero filled + // main executable has 10.5 style __dyld section that has program variable pointers + context.setNewProgramVars(dd->vars); } - ImageLoader *image = NULL; - // if only processing coalesced symbols and this one does not require coalesceing, skip to next - if ( onlyCoalescedSymbols && !symbolRequiresCoalescing(sym) ) - continue; - uintptr_t symbolAddr; - symbolAddr = resolveUndefined(context, sym, twoLevel, &image); - if ( context.verboseBind ) { - const char *path = NULL; - if(NULL != image) { - path = image->getShortName(); - } - const char *typeName; - if ( type == S_LAZY_SYMBOL_POINTERS ) { - typeName = "lazy_ptr"; + else { + // main executable is pre-10.5 and requires the symbols names to be looked up + this->lookupProgramVars(context); + #if SUPPORT_OLD_CRT_INITIALIZATION + // If the first 16 bytes of the entry point's instructions do not + // match what crt1.o supplies, then the program has a custom entry point. + // This means it might be doing something that needs to be executed before + // initializers are run. + if ( memcmp(this->getMain(), sStandardEntryPointInstructions, 16) != 0 ) { + if ( context.verboseInit ) + dyld::log("dyld: program uses non-standard entry point so delaying running of initializers\n"); + context.setRunInitialzersOldWay(); } - else { - typeName = "non_lazy_ptr"; + #endif + } + } + else if ( mh->filetype == MH_DYLIB ) { + const char* installPath = this->getInstallPath(); + if ( (installPath != NULL) && (strncmp(installPath, "/usr/lib/", 9) == 0) ) { + if ( sect->size > offsetof(DATAdyld, vars) ) { + // use ProgramVars from libdyld.dylib but tweak mh field to correct value + dd->vars.mh = context.mainExecutable->machHeader(); + context.setNewProgramVars(dd->vars); } - fprintf(stderr, "dyld: bind: %s:%s$%s = %s:%s, *0x%08lx = 0x%08lx\n", - this->getShortName(), &fStrings[sym->n_un.n_strx], typeName, - path, &fStrings[sym->n_un.n_strx], (uintptr_t)&symbolPointers[j], symbolAddr); } - symbolPointers[j] = symbolAddr; } } - // update stats - fgTotalBindFixups += pointerCount; + else if ( (strcmp(sect->sectname, "__program_vars" ) == 0) && (mh->filetype == MH_EXECUTE) ) { + // this is a Mac OS X 10.6 or later main executable + struct ProgramVars* pv = (struct ProgramVars*)(sect->addr + fSlide); + context.setNewProgramVars(*pv); + } } } - break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } } -/* - * The address of these symbols are written in to the (__DATA,__dyld) section - * at the following offsets: - * at offset 0 stub_binding_helper_interface - * at offset 4 _dyld_func_lookup - * at offset 8 start_debug_thread - * The 'C' types (if any) for these symbols are ignored here and all are - * declared as longs so the assignment of their address in to the section will - * not require a cast. stub_binding_helper_interface is really a label in the - * assembly code interface for the stub binding. It does not have a meaningful - * 'C' type. _dyld_func_lookup is the routine in dyld_libfuncs.c. - * start_debug_thread is the routine in debug.c. - * - * For ppc the image's stub_binding_binding_helper is read from: - * at offset 20 the image's stub_binding_binding_helper address - * and saved into to the image structure. - */ -struct DATAdyld { - void* dyldLazyBinder; // filled in at launch by dyld to point into dyld to &stub_binding_helper_interface - void* dyldFuncLookup; // filled in at launch by dyld to point into dyld to &_dyld_func_lookup - void* startDebugThread; // debugger interface ??? - void* debugPort; // debugger interface ??? - void* debugThread; // debugger interface ??? - void* stubBindHelper; // filled in at static link time to point to stub helper in image - void* coreDebug; // ??? -}; - -// These are defined in dyldStartup.s -extern "C" void stub_binding_helper(); -extern "C" bool dyld_func_lookup(const char* name, uintptr_t* address); - - -void ImageLoaderMachO::setupLazyPointerHandler() + +void ImageLoaderMachO::lookupProgramVars(const LinkContext& context) const { - if ( fDATAdyld != NULL ) { - struct DATAdyld* dd = (struct DATAdyld*)(fDATAdyld->addr + fSlide); - if ( fDATAdyld->size > offsetof(DATAdyld, dyldLazyBinder) ) { - if ( dd->dyldLazyBinder != (void*)&stub_binding_helper ) - dd->dyldLazyBinder = (void*)&stub_binding_helper; - } - if ( fDATAdyld->size > offsetof(DATAdyld, dyldFuncLookup) ) { - if ( dd->dyldFuncLookup != (void*)&dyld_func_lookup ) - dd->dyldFuncLookup = (void*)&dyld_func_lookup; - } - //if ( fDATAdyld->size > offsetof(DATAdyld, startDebugThread) ) - // dd->startDebugThread = &start_debug_thread; -#ifdef __ppc__ - //if ( fDATAdyld->size > offsetof(DATAdyld, stubBindHelper) ) - // save = dd->stubBindHelper; -#endif - } + ProgramVars vars = context.programVars; + const ImageLoader::Symbol* sym; + + // get mach header directly + vars.mh = (macho_header*)fMachOData; + + // lookup _NXArgc + sym = this->findExportedSymbol("_NXArgc", false, NULL); + if ( sym != NULL ) + vars.NXArgcPtr = (int*)this->getExportedSymbolAddress(sym, context, this, false); + + // lookup _NXArgv + sym = this->findExportedSymbol("_NXArgv", false, NULL); + if ( sym != NULL ) + vars.NXArgvPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false); + + // lookup _environ + sym = this->findExportedSymbol("_environ", false, NULL); + if ( sym != NULL ) + vars.environPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false); + + // lookup __progname + sym = this->findExportedSymbol("___progname", false, NULL); + if ( sym != NULL ) + vars.__prognamePtr = (const char**)this->getExportedSymbolAddress(sym, context, this, false); + + context.setNewProgramVars(vars); } + bool ImageLoaderMachO::usablePrebinding(const LinkContext& context) const { // if prebound and loaded at prebound address, and all libraries are same as when this was prebound, then no need to bind - if ( this->isPrebindable() && this->allDependentLibrariesAsWhenPreBound() && (this->getSlide() == 0) ) { + if ( ((this->isPrebindable() && (this->getSlide() == 0)) || fInSharedCache) + && this->usesTwoLevelNameSpace() + && this->allDependentLibrariesAsWhenPreBound() ) { // allow environment variables to disable prebinding if ( context.bindFlat ) return false; @@ -2164,474 +1787,378 @@ bool ImageLoaderMachO::usablePrebinding(const LinkContext& context) const return false; } -void ImageLoaderMachO::doBind(const LinkContext& context, BindingLaziness bindness) -{ - // set dyld entry points in image - this->setupLazyPointerHandler(); - - // if prebound and loaded at prebound address, and all libraries are same as when this was prebound, then no need to bind - // note: flat-namespace binaries need to be imports rebound (even if correctly prebound) - if ( this->usablePrebinding(context) && this->usesTwoLevelNameSpace() ) { - // if image has coalesced symbols, then these need to be rebound - if ( this->needsCoalescing() ) { - this->doBindExternalRelocations(context, true); - this->doBindIndirectSymbolPointers(context, kLazyAndNonLazy, true); - } - // skip binding because prebound and prebinding not disabled - return; - } - - // values bound by name are stored two different ways in mach-o - switch (bindness) { - case kNonLazyOnly: - case kLazyAndNonLazy: - // external relocations are used for data initialized to external symbols - this->doBindExternalRelocations(context, false); - break; - case kLazyOnly: - case kLazyOnlyNoDependents: - break; - } - // "indirect symbols" are used for code references to external symbols - this->doBindIndirectSymbolPointers(context, bindness, false); -} - - void ImageLoaderMachO::doImageInit(const LinkContext& context) { - if ( fDashInit != NULL ) { - Initializer func = (Initializer)(fDashInit->init_address + fSlide); - if ( context.verboseInit ) - fprintf(stderr, "dyld: calling -init function 0x%p in %s\n", func, this->getPath()); - func(context.argc, context.argv, context.envp, context.apple); + if ( fHasDashInit ) { + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_ROUTINES_COMMAND: + Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide); + // verify initializers are in image + if ( ! this->containsAddress((void*)func) ) { + dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath()); + } + if ( context.verboseInit ) + dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath()); + func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } } } void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) { - if ( fModInitSection != NULL ) { - Initializer* inits = (Initializer*)(fModInitSection->addr + fSlide); - const uint32_t count = fModInitSection->size / sizeof(uintptr_t); - for (uint32_t i=0; i < count; ++i) { - Initializer func = inits[i]; - if ( context.verboseInit ) - fprintf(stderr, "dyld: calling initializer function %p in %s\n", func, this->getPath()); - func(context.argc, context.argv, context.envp, context.apple); + if ( fHasInitializers ) { + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd == LC_SEGMENT_COMMAND ) { + const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; + 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) { + const uint8_t type = sect->flags & SECTION_TYPE; + if ( type == S_MOD_INIT_FUNC_POINTERS ) { + Initializer* inits = (Initializer*)(sect->addr + fSlide); + const size_t count = sect->size / sizeof(uintptr_t); + for (size_t i=0; i < count; ++i) { + Initializer func = inits[i]; + // verify initializers are in image + if ( ! this->containsAddress((void*)func) ) { + dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath()); + } + if ( context.verboseInit ) + dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath()); + func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + } + } + } + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } } } -void ImageLoaderMachO::doInitialization(const LinkContext& context) -{ - // mach-o has -init and static initializers - doImageInit(context); - doModInitFunctions(context); -} - -bool ImageLoaderMachO::needsInitialization() -{ - return ( (fDashInit != NULL) || (fModInitSection != NULL) ); -} - -bool ImageLoaderMachO::needsTermination() -{ - return ( fModTermSection != NULL ); -} -bool ImageLoaderMachO::hasImageNotification() -{ - return ( fImageNotifySection != NULL ); -} -void ImageLoaderMachO::doTermination(const LinkContext& context) +void ImageLoaderMachO::doGetDOFSections(const LinkContext& context, std::vector& dofs) { - if ( fModTermSection != NULL ) { - Terminator* terms = (Terminator*)(fModTermSection->addr + fSlide); - const uint32_t count = fModTermSection->size / sizeof(uintptr_t); - for (uint32_t i=count; i > 0; --i) { - Terminator func = terms[i-1]; - if ( context.verboseInit ) - fprintf(stderr, "dyld: calling terminaton function %p in %s\n", func, this->getPath()); - func(); + if ( fHasDOFSections ) { + // walk load commands (mapped in at start of __TEXT segment) + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[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; + 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 ( (sect->flags & SECTION_TYPE) == S_DTRACE_DOF ) { + ImageLoader::DOFInfo info; + info.dof = (void*)(sect->addr + fSlide); + info.imageHeader = this->machHeader(); + info.imageShortName = this->getShortName(); + dofs.push_back(info); + } + } + } + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } } -} +} -void ImageLoaderMachO::doNotification(enum dyld_image_mode mode, uint32_t infoCount, const struct dyld_image_info info[]) -{ - if ( fImageNotifySection != NULL ) { - dyld_image_notifier* notes = (dyld_image_notifier*)(fImageNotifySection->addr + fSlide); - const uint32_t count = fImageNotifySection->size / sizeof(uintptr_t); - for (uint32_t i=count; i > 0; --i) { - dyld_image_notifier func = notes[i-1]; - func(mode, infoCount, info); - } - } -} -void ImageLoaderMachO::printStatistics(unsigned int imageCount) +bool ImageLoaderMachO::doInitialization(const LinkContext& context) { - ImageLoader::printStatistics(imageCount); - fprintf(stderr, "total hinted binary tree searches: %d\n", fgHintedBinaryTreeSearchs); - fprintf(stderr, "total unhinted binary tree searches: %d\n", fgUnhintedBinaryTreeSearchs); + CRSetCrashLogMessage2(this->getPath()); + + // mach-o has -init and static initializers + doImageInit(context); + doModInitFunctions(context); -#if LINKEDIT_USAGE_DEBUG - fprintf(stderr, "linkedit pages accessed (%lu):\n", sLinkEditPageBuckets.size()); -#endif + CRSetCrashLogMessage2(NULL); + + return (fHasDashInit || fHasInitializers); } -void ImageLoaderMachO::doPrebinding(const LinkContext& context, time_t timestamp, uint8_t* fileToPrebind) +bool ImageLoaderMachO::needsInitialization() { - // update __DATA segment - this->applyPrebindingToDATA(fileToPrebind); - - // update load commands - this->applyPrebindingToLoadCommands(context, fileToPrebind, timestamp); - - // update symbol table - this->applyPrebindingToLinkEdit(context, fileToPrebind); + return ( fHasDashInit || fHasInitializers ); } -void ImageLoaderMachO::applyPrebindingToDATA(uint8_t* fileToPrebind) + +bool ImageLoaderMachO::needsTermination() { - const unsigned int segmentCount = fSegments.size(); - for(unsigned int i=0; i < segmentCount; ++i) { - SegmentMachO* seg = (SegmentMachO*)fSegments[i]; - if ( seg->writeable() ) { - memcpy(&fileToPrebind[seg->fFileOffset], (void*)seg->getActualLoadAddress(), seg->fFileSize); - } - } + return fHasTerminators; } -void ImageLoaderMachO::applyPrebindingToLoadCommands(const LinkContext& context, uint8_t* fileToPrebind, time_t timestamp) + +void ImageLoaderMachO::doTermination(const LinkContext& context) { - macho_header* mh = (macho_header*)fileToPrebind; - const uint32_t cmd_count = mh->ncmds; - const struct load_command* const cmds = (struct load_command*)&fileToPrebind[sizeof(macho_header)]; - const struct load_command* cmd = cmds; - for (uint32_t i = 0; i < cmd_count; ++i) { - switch (cmd->cmd) { - case LC_LOAD_DYLIB: - case LC_LOAD_WEAK_DYLIB: - { - // update each dylib load command with the timestamp of the target dylib - struct dylib_command* dylib = (struct dylib_command*)cmd; - const char* name = (char*)cmd + dylib->dylib.name.offset; - for (const DependentLibrary* dl=fLibraries; dl < &fLibraries[fLibrariesCount]; dl++) { - if (strcmp(dl->name, name) == 0 ) { - // found matching DependentLibrary for this load command - ImageLoaderMachO* targetImage = (ImageLoaderMachO*)(dl->image); // !!! assume only mach-o images are prebound - if ( ! targetImage->isPrebindable() ) - throw "dependent dylib is not prebound"; - // if the target is currently being re-prebound then its timestamp will be the same as this one - if ( ! targetImage->usablePrebinding(context) ) { - dylib->dylib.timestamp = timestamp; - } - else { - // otherwise dependent library is already correctly prebound, so use its checksum - dylib->dylib.timestamp = targetImage->doGetLibraryInfo().checksum; + if ( fHasTerminators ) { + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd == LC_SEGMENT_COMMAND ) { + const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; + 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) { + const uint8_t type = sect->flags & SECTION_TYPE; + if ( type == S_MOD_TERM_FUNC_POINTERS ) { + Terminator* terms = (Terminator*)(sect->addr + fSlide); + const size_t count = sect->size / sizeof(uintptr_t); + for (size_t i=count; i > 0; --i) { + Terminator func = terms[i-1]; + // verify terminators are in image + if ( ! this->containsAddress((void*)func) ) { + dyld::throwf("termination function %p not in mapped image for %s\n", func, this->getPath()); } - break; + if ( context.verboseInit ) + dyld::log("dyld: calling termination function %p in %s\n", func, this->getPath()); + func(); } } } - break; - case LC_ID_DYLIB: - { - // update the ID of this library with the new timestamp - struct dylib_command* dylib = (struct dylib_command*)cmd; - dylib->dylib.timestamp = timestamp; - } - break; - case LC_SEGMENT_COMMAND: - // if dylib was rebased, update segment commands - if ( fSlide != 0 ) { - struct macho_segment_command* seg = (struct macho_segment_command*)cmd; - seg->vmaddr += fSlide; - struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); - struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; - for (struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { - sect->addr += fSlide; - } - } - break; - case LC_ROUTINES_COMMAND: - // if dylib was rebased, update -init command - if ( fSlide != 0 ) { - struct macho_routines_command* routines = (struct macho_routines_command*)cmd; - routines->init_address += fSlide; - } - break; - } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); - } -} - -void ImageLoaderMachO::applyPrebindingToLinkEdit(const LinkContext& context, uint8_t* fileToPrebind) -{ - // In prebound images, the n_value of the symbol table entry for is the prebound address - // This is needed when prebinding can't be used, to back solve for any possible addend in non-lazy pointers - const char* stringPool = NULL; - struct macho_nlist* symbolTable = NULL; - const struct dysymtab_command* dysymtab = NULL; - - // get symbol table info - macho_header* mh = (macho_header*)fileToPrebind; - const uint32_t cmd_count = mh->ncmds; - const struct load_command* const cmds = (struct load_command*)&fileToPrebind[sizeof(macho_header)]; - const struct load_command* cmd = cmds; - for (uint32_t i = 0; i < cmd_count; ++i) { - switch (cmd->cmd) { - case LC_SYMTAB: - { - const struct symtab_command* symtab = (struct symtab_command*)cmd; - stringPool = (const char*)&fileToPrebind[symtab->stroff]; - symbolTable = (struct macho_nlist*)(&fileToPrebind[symtab->symoff]); - } - break; - case LC_DYSYMTAB: - dysymtab = (struct dysymtab_command*)cmd; - break; - } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); - } - - // walk all imports and re-resolve their n_value (needed incase prebinding is invalid) - struct macho_nlist* lastImport = &symbolTable[dysymtab->iundefsym+dysymtab->nundefsym]; - for (struct macho_nlist* entry = &symbolTable[dysymtab->iundefsym]; entry < lastImport; ++entry) { - ImageLoader* dummy; - entry->n_value = this->resolveUndefined(context, entry, this->usesTwoLevelNameSpace(), &dummy); - } - - // walk all exports and slide their n_value - struct macho_nlist* lastExport = &symbolTable[dysymtab->iextdefsym+dysymtab->nextdefsym]; - for (struct macho_nlist* entry = &symbolTable[dysymtab->iextdefsym]; entry < lastExport; ++entry) { - if ( (entry->n_type & N_TYPE) == N_SECT ) - entry->n_value += fSlide; - } - - // walk all local symbols and slide their n_value - struct macho_nlist* lastLocal = &symbolTable[dysymtab->ilocalsym+dysymtab->nlocalsym]; - for (struct macho_nlist* entry = &symbolTable[dysymtab->ilocalsym]; entry < lastLocal; ++entry) { - if ( entry->n_sect != NO_SECT ) - entry->n_value += fSlide; - } - - // walk all local relocations and reset every PPC_RELOC_PB_LA_PTR r_value - relocation_info* const relocsStart = (struct relocation_info*)(&fileToPrebind[dysymtab->locreloff]); - relocation_info* const relocsEnd = &relocsStart[dysymtab->nlocrel]; - for (relocation_info* reloc=relocsStart; reloc < relocsEnd; ++reloc) { - if ( (reloc->r_address & R_SCATTERED) != 0 ) { - struct scattered_relocation_info* sreloc = (struct scattered_relocation_info*)reloc; - if (sreloc->r_length == RELOC_SIZE) { - switch(sreloc->r_type) { - #if __ppc__ || __ppc64__ - case PPC_RELOC_PB_LA_PTR: - #elif __i386__ - case GENERIC_RELOC_PB_LA_PTR: - #else - #error unknown architecture - #endif - sreloc->r_value += fSlide; - break; - } - } - } - } - - // if multi-module, fix up objc_addr (10.4 and later runtime does not use this, but we want to keep file checksum consistent) - if ( dysymtab->nmodtab != 0 ) { - dylib_module* const modulesStart = (struct dylib_module*)(&fileToPrebind[dysymtab->modtaboff]); - dylib_module* const modulesEnd = &modulesStart[dysymtab->nmodtab]; - for (dylib_module* module=modulesStart; module < modulesEnd; ++module) { - if ( module->objc_module_info_size != 0 ) { - module->objc_module_info_addr += fSlide; } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } } } -// file on disk has been reprebound, but we are still mapped to old file -void ImageLoaderMachO::prebindUnmap(const LinkContext& context) -{ - // this removes all mappings to the old file, so the kernel will unlink (delete) it. - // We need to leave the load commands and __LINKEDIT in place - for (std::vector::iterator it=fSegments.begin(); it != fSegments.end(); ++it) { - void* segmentAddress = (void*)((*it)->getActualLoadAddress()); - uintptr_t segmentSize = (*it)->getSize(); - //fprintf(stderr, "unmapping segment %s at %p for %s\n", (*it)->getName(), segmentAddress, this->getPath()); - // save load commands at beginning of __TEXT segment - if ( segmentAddress == fMachOData ) { - // typically load commands are one or two pages in size, so ok to alloc on stack - uint32_t loadCmdSize = sizeof(macho_header) + ((macho_header*)fMachOData)->sizeofcmds; - uint32_t loadCmdPages = (loadCmdSize+4095) & (-4096); - uint8_t loadcommands[loadCmdPages]; - memcpy(loadcommands, fMachOData, loadCmdPages); - // unmap whole __TEXT segment - munmap((void*)(fMachOData), segmentSize); - // allocate and copy back mach_header and load commands - vm_address_t addr = (vm_address_t)fMachOData; - int r2 = vm_allocate(mach_task_self(), &addr, loadCmdPages, false /*at this address*/); - if ( r2 != 0 ) - fprintf(stderr, "prebindUnmap() vm_allocate for __TEXT %d failed\n", loadCmdPages); - memcpy((void*)fMachOData, loadcommands, loadCmdPages); - //fprintf(stderr, "copying back load commands to %p size=%u for %s\n", segmentAddress, loadCmdPages, this->getPath()); - } - else if ( strcmp((*it)->getName(), "__LINKEDIT") == 0 ) { - uint32_t linkEditSize = segmentSize; - uint32_t linkEditPages = (linkEditSize+4095) & (-4096); - void* linkEditTmp = malloc(linkEditPages); - memcpy(linkEditTmp, segmentAddress, linkEditPages); - // unmap whole __LINKEDIT segment - munmap(segmentAddress, segmentSize); - vm_address_t addr = (vm_address_t)segmentAddress; - int r2 = vm_allocate(mach_task_self(), &addr, linkEditPages, false /*at this address*/); - if ( r2 != 0 ) - fprintf(stderr, "prebindUnmap() vm_allocate for __LINKEDIT %d failed\n", linkEditPages); - memcpy(segmentAddress, linkEditTmp, linkEditPages); - //fprintf(stderr, "copying back __LINKEDIT to %p size=%u for %s\n", segmentAddress, linkEditPages, this->getPath()); - free(linkEditTmp); - } - else { - // unmap any other segment - munmap((void*)(segmentAddress), (*it)->getSize()); - } - } -} - - - -SegmentMachO::SegmentMachO(const struct macho_segment_command* cmd, ImageLoaderMachO* image, const uint8_t* fileData) - : fImage(image), fSize(cmd->vmsize), fFileSize(cmd->filesize), fFileOffset(cmd->fileoff), fPreferredLoadAddress(cmd->vmaddr), - fVMProtection(cmd->initprot), fHasFixUps(false), fUnMapOnDestruction(false) -{ - strncpy(fName, cmd->segname, 16); - fName[16] = '\0'; - // scan sections for fix-up bit - const struct macho_section* const sectionsStart = (struct macho_section*)((char*)cmd + sizeof(struct macho_segment_command)); - const struct macho_section* const sectionsEnd = §ionsStart[cmd->nsects]; - for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { - if ( (sect->flags & (S_ATTR_EXT_RELOC | S_ATTR_LOC_RELOC)) != 0 ) - fHasFixUps = true; - } -} - -SegmentMachO::~SegmentMachO() -{ - if ( fUnMapOnDestruction ) { - //fprintf(stderr, "unmapping segment %s at 0x%08lX\n", getName(), getActualLoadAddress()); - munmap((void*)(this->getActualLoadAddress()), this->getSize()); - } -} - -const ImageLoader* SegmentMachO::getImage() -{ - return fImage; -} - -const char* SegmentMachO::getName() -{ - return fName; -} - -uintptr_t SegmentMachO::getSize() -{ - return fSize; -} - -uintptr_t SegmentMachO::getFileSize() -{ - return fFileSize; -} -uintptr_t SegmentMachO::getFileOffset() +void ImageLoaderMachO::printStatistics(unsigned int imageCount, const InitializerTimingList& timingInfo) { - return fFileOffset; + ImageLoader::printStatistics(imageCount, timingInfo); + dyld::log("total symbol trie searches: %d\n", fgSymbolTrieSearchs); + dyld::log("total symbol table binary searches: %d\n", fgSymbolTableBinarySearchs); + dyld::log("total images defining weak symbols: %u\n", fgImagesHasWeakDefinitions); + dyld::log("total images using weak symbols: %u\n", fgImagesRequiringCoalescing); } -bool SegmentMachO::readable() -{ - return ( (fVMProtection & VM_PROT_READ) != 0); -} - -bool SegmentMachO::writeable() -{ - return ((fVMProtection & VM_PROT_WRITE) != 0); -} - -bool SegmentMachO::executable() -{ - return ((fVMProtection & VM_PROT_EXECUTE) != 0); -} -bool SegmentMachO::unaccessible() +intptr_t ImageLoaderMachO::assignSegmentAddresses(const LinkContext& context) { - return (fVMProtection == 0); + // preflight and calculate slide if needed + const bool inPIE = (fgNextPIEDylibAddress != 0); + intptr_t slide = 0; + if ( this->segmentsCanSlide() && this->segmentsMustSlideTogether() ) { + bool needsToSlide = false; + bool imageHasPreferredLoadAddress = segHasPreferredLoadAddress(0); + uintptr_t lowAddr = (unsigned long)(-1); + uintptr_t highAddr = 0; + for(unsigned int i=0, e=segmentCount(); i < e; ++i) { + const uintptr_t segLow = segPreferredLoadAddress(i); + const uintptr_t segHigh = dyld_page_round(segLow + segSize(i)); + if ( segLow < highAddr ) { + if ( dyld_page_size > 4096 ) + dyld::throwf("can't map segments into 16KB pages"); + else + dyld::throwf("overlapping segments"); + } + if ( segLow < lowAddr ) + lowAddr = segLow; + if ( segHigh > highAddr ) + highAddr = segHigh; + + if ( needsToSlide || !imageHasPreferredLoadAddress || inPIE || !reserveAddressRange(segPreferredLoadAddress(i), segSize(i)) ) + needsToSlide = true; + } + if ( needsToSlide ) { + // find a chunk of address space to hold all segments + uintptr_t addr = reserveAnAddressRange(highAddr-lowAddr, context); + slide = addr - lowAddr; + } + } + else if ( ! this->segmentsCanSlide() ) { + for(unsigned int i=0, e=segmentCount(); i < e; ++i) { + if ( strcmp(segName(i), "__PAGEZERO") == 0 ) + continue; + if ( !reserveAddressRange(segPreferredLoadAddress(i), segSize(i)) ) + dyld::throwf("can't map unslidable segment %s to 0x%lX with size 0x%lX", segName(i), segPreferredLoadAddress(i), segSize(i)); + } + } + else { + throw "mach-o does not support independently sliding segments"; + } + return slide; } -bool SegmentMachO::hasFixUps() -{ - return fHasFixUps; -} -uintptr_t SegmentMachO::getActualLoadAddress() +uintptr_t ImageLoaderMachO::reserveAnAddressRange(size_t length, const ImageLoader::LinkContext& context) { - return fPreferredLoadAddress + fImage->fSlide; + vm_address_t addr = 0; + vm_size_t size = length; + // in PIE programs, load initial dylibs after main executable so they don't have fixed addresses either + if ( fgNextPIEDylibAddress != 0 ) { + // add small (0-3 pages) random padding between dylibs + addr = fgNextPIEDylibAddress + (__stack_chk_guard/fgNextPIEDylibAddress & (sizeof(long)-1))*dyld_page_size; + //dyld::log("padding 0x%08llX, guard=0x%08llX\n", (long long)(addr - fgNextPIEDylibAddress), (long long)(__stack_chk_guard)); + kern_return_t r = vm_alloc(&addr, size, VM_FLAGS_FIXED | VM_MAKE_TAG(VM_MEMORY_DYLIB)); + if ( r == KERN_SUCCESS ) { + fgNextPIEDylibAddress = addr + size; + return addr; + } + fgNextPIEDylibAddress = 0; + } + kern_return_t r = vm_alloc(&addr, size, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_DYLIB)); + if ( r != KERN_SUCCESS ) + throw "out of address space"; + + return addr; } -uintptr_t SegmentMachO::getPreferredLoadAddress() +bool ImageLoaderMachO::reserveAddressRange(uintptr_t start, size_t length) { - return fPreferredLoadAddress; + vm_address_t addr = start; + vm_size_t size = length; + kern_return_t r = vm_alloc(&addr, size, VM_FLAGS_FIXED | VM_MAKE_TAG(VM_MEMORY_DYLIB)); + if ( r != KERN_SUCCESS ) + return false; + return true; } -bool SegmentMachO::hasPreferredLoadAddress() -{ - return (fPreferredLoadAddress != 0); -} -void SegmentMachO::setUnMapWhenDestructed(bool unmap) -{ - fUnMapOnDestruction = unmap; -} -static uint32_t *buildCRCTable(void) +void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInFat, uint64_t fileLen, const LinkContext& context) { - uint32_t *table = new uint32_t[256]; - uint32_t p = 0xedb88320UL; // standard CRC-32 polynomial - - for (unsigned int i = 0; i < 256; i++) { - uint32_t c = i; - for (unsigned int j = 0; j < 8; j++) { - if ( c & 1 ) c = p ^ (c >> 1); - else c = c >> 1; + // find address range for image + intptr_t slide = this->assignSegmentAddresses(context); + if ( context.verboseMapping ) { + if ( offsetInFat != 0 ) + dyld::log("dyld: Mapping %s (slice offset=%llu)\n", this->getPath(), (unsigned long long)offsetInFat); + else + dyld::log("dyld: Mapping %s\n", this->getPath()); + } + // map in all segments + for(unsigned int i=0, e=segmentCount(); i < e; ++i) { + vm_offset_t fileOffset = segFileOffset(i) + offsetInFat; + vm_size_t size = segFileSize(i); + uintptr_t requestedLoadAddress = segPreferredLoadAddress(i) + slide; + int protection = 0; + if ( !segUnaccessible(i) ) { + // If has text-relocs, don't set x-bit initially. + // Instead set it later after text-relocs have been done. + if ( segExecutable(i) && !(segHasRebaseFixUps(i) && (slide != 0)) ) + protection |= PROT_EXEC; + if ( segReadable(i) ) + protection |= PROT_READ; + if ( segWriteable(i) ) + protection |= PROT_WRITE; + } + #if __i386__ + // initially map __IMPORT segments R/W so dyld can update them + if ( segIsReadOnlyImport(i) ) + protection |= PROT_WRITE; + #endif + // wholly zero-fill segments have nothing to mmap() in + if ( size > 0 ) { + if ( (fileOffset+size) > fileLen ) { + dyld::throwf("truncated mach-o error: segment %s extends to %llu which is past end of file %llu", + segName(i), (uint64_t)(fileOffset+size), fileLen); + } + void* loadAddress = xmmap((void*)requestedLoadAddress, size, protection, MAP_FIXED | MAP_PRIVATE, fd, fileOffset); + if ( loadAddress == ((void*)(-1)) ) { + dyld::throwf("mmap() error %d at address=0x%08lX, size=0x%08lX segment=%s in Segment::map() mapping %s", + errno, requestedLoadAddress, (uintptr_t)size, segName(i), getPath()); + } } - table[i] = c; + // update stats + ++ImageLoader::fgTotalSegmentsMapped; + ImageLoader::fgTotalBytesMapped += size; + if ( context.verboseMapping ) + dyld::log("%18s at 0x%08lX->0x%08lX with permissions %c%c%c\n", segName(i), requestedLoadAddress, requestedLoadAddress+size-1, + (protection & PROT_READ) ? 'r' : '.', (protection & PROT_WRITE) ? 'w' : '.', (protection & PROT_EXEC) ? 'x' : '.' ); } - return table; + // update slide to reflect load location + this->setSlide(slide); } -uint32_t SegmentMachO::crc32() +void ImageLoaderMachO::mapSegments(const void* memoryImage, uint64_t imageLen, const LinkContext& context) { - if ( !readable() ) return 0; - - static uint32_t *crcTable = NULL; - if ( !crcTable ) crcTable = buildCRCTable(); - - uint32_t crc = ~(uint32_t)0; - uint8_t *p = (uint8_t *)getActualLoadAddress(); - uint8_t *end = p + getSize(); - while ( p < end ) { - crc = crcTable[(crc & 0xff) ^ (*p++)] ^ (crc >> 8); + // find address range for image + intptr_t slide = this->assignSegmentAddresses(context); + if ( context.verboseMapping ) + dyld::log("dyld: Mapping memory %p\n", memoryImage); + // map in all segments + for(unsigned int i=0, e=segmentCount(); i < e; ++i) { + vm_address_t loadAddress = segPreferredLoadAddress(i) + slide; + vm_address_t srcAddr = (uintptr_t)memoryImage + segFileOffset(i); + vm_size_t size = segFileSize(i); + kern_return_t r = vm_copy(mach_task_self(), srcAddr, size, loadAddress); + if ( r != KERN_SUCCESS ) + throw "can't map segment"; + if ( context.verboseMapping ) + dyld::log("%18s at 0x%08lX->0x%08lX\n", segName(i), (uintptr_t)loadAddress, (uintptr_t)loadAddress+size-1); + } + // update slide to reflect load location + this->setSlide(slide); + // set R/W permissions on all segments at slide location + for(unsigned int i=0, e=segmentCount(); i < e; ++i) { + segProtect(i, context); + } +} + + +void ImageLoaderMachO::segProtect(unsigned int segIndex, const ImageLoader::LinkContext& context) +{ + vm_prot_t protection = 0; + if ( !segUnaccessible(segIndex) ) { + if ( segExecutable(segIndex) ) + protection |= PROT_EXEC; + if ( segReadable(segIndex) ) + protection |= PROT_READ; + if ( segWriteable(segIndex) ) + protection |= PROT_WRITE; + } + vm_address_t addr = segActualLoadAddress(segIndex); + vm_size_t size = segSize(segIndex); + const bool setCurrentPermissions = false; + kern_return_t r = vm_protect(mach_task_self(), addr, size, setCurrentPermissions, protection); + if ( r != KERN_SUCCESS ) { + dyld::throwf("vm_protect(0x%08llX, 0x%08llX, false, 0x%02X) failed, result=%d for segment %s in %s", + (long long)addr, (long long)size, protection, r, segName(segIndex), this->getPath()); + } + if ( context.verboseMapping ) { + dyld::log("%18s at 0x%08lX->0x%08lX altered permissions to %c%c%c\n", segName(segIndex), (uintptr_t)addr, (uintptr_t)addr+size-1, + (protection & PROT_READ) ? 'r' : '.', (protection & PROT_WRITE) ? 'w' : '.', (protection & PROT_EXEC) ? 'x' : '.' ); + } +} + +void ImageLoaderMachO::segMakeWritable(unsigned int segIndex, const ImageLoader::LinkContext& context) +{ + vm_address_t addr = segActualLoadAddress(segIndex); + vm_size_t size = segSize(segIndex); + const bool setCurrentPermissions = false; + vm_prot_t protection = VM_PROT_WRITE | VM_PROT_READ; + if ( segExecutable(segIndex) && !segHasRebaseFixUps(segIndex) ) + protection |= VM_PROT_EXECUTE; + kern_return_t r = vm_protect(mach_task_self(), addr, size, setCurrentPermissions, protection); + if ( r != KERN_SUCCESS ) { + dyld::throwf("vm_protect(0x%08llX, 0x%08llX, false, 0x%02X) failed, result=%d for segment %s in %s", + (long long)addr, (long long)size, protection, r, segName(segIndex), this->getPath()); + } + if ( context.verboseMapping ) { + dyld::log("%18s at 0x%08lX->0x%08lX altered permissions to %c%c%c\n", segName(segIndex), (uintptr_t)addr, (uintptr_t)addr+size-1, + (protection & PROT_READ) ? 'r' : '.', (protection & PROT_WRITE) ? 'w' : '.', (protection & PROT_EXEC) ? 'x' : '.' ); } - return crc ^ ~(uint32_t)0; } - - -