X-Git-Url: https://git.saurik.com/apple/dyld.git/blobdiff_plain/2fd3f4e8fd2c2f2c7d149fbea471d8b3fb56f15a..ba4c3badc27ea8c4637b4e91a49725742a02a53c:/src/ImageLoaderMachOCompressed.cpp diff --git a/src/ImageLoaderMachOCompressed.cpp b/src/ImageLoaderMachOCompressed.cpp index 5ba89ec..a908c91 100644 --- a/src/ImageLoaderMachOCompressed.cpp +++ b/src/ImageLoaderMachOCompressed.cpp @@ -23,25 +23,52 @@ */ +#if __arm__ || __arm64__ + #include +#else + #include +#endif #include #include #include #include #include #include -#include #include #include #include #include - #include "ImageLoaderMachOCompressed.h" #include "mach-o/dyld_images.h" +#include "Closure.h" +#include "Array.h" #ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE #define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 #endif + +#ifndef BIND_OPCODE_THREADED +#define BIND_OPCODE_THREADED 0xD0 +#endif + +#ifndef BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB +#define BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB 0x00 +#endif + +#ifndef BIND_SUBOPCODE_THREADED_APPLY +#define BIND_SUBOPCODE_THREADED_APPLY 0x01 +#endif + + +#ifndef BIND_SPECIAL_DYLIB_WEAK_LOOKUP +#define BIND_SPECIAL_DYLIB_WEAK_LOOKUP -3 +#endif + +#ifndef CPU_SUBTYPE_ARM64_E + #define CPU_SUBTYPE_ARM64_E 2 +#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 @@ -59,45 +86,6 @@ struct macho_routines_command : public routines_command {}; #endif - -static uintptr_t read_uleb128(const uint8_t*& p, const uint8_t* end) -{ - uint64_t result = 0; - int bit = 0; - do { - if (p == end) - dyld::throwf("malformed uleb128"); - - uint64_t slice = *p & 0x7f; - - if (bit > 63) - dyld::throwf("uleb128 too big for uint64, bit=%d, result=0x%0llX", bit, result); - else { - result |= (slice << bit); - bit += 7; - } - } while (*p++ & 0x80); - return result; -} - - -static intptr_t read_sleb128(const uint8_t*& p, const uint8_t* end) -{ - int64_t result = 0; - int bit = 0; - uint8_t byte; - do { - if (p == end) - throw "malformed sleb128"; - byte = *p++; - result |= (((int64_t)(byte & 0x7f)) << bit); - bit += 7; - } while (byte & 0x80); - // sign extend negative numbers - if ( (byte & 0x40) != 0 ) - result |= (-1LL) << bit; - return result; -} // create image for main executable @@ -112,10 +100,11 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateMainExecutabl // for PIE record end of program, to know where to start loading dylibs if ( slide != 0 ) fgNextPIEDylibAddress = (uintptr_t)image->getEnd(); - + + image->disableCoverageCheck(); image->instantiateFinish(context); image->setMapped(context); - + if ( context.verboseMapping ) { dyld::log("dyld: Main executable mapped %s\n", path); for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) { @@ -131,10 +120,12 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateMainExecutabl } // create image by mapping in a mach-o file -ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromFile(const char* path, int fd, const uint8_t* fileData, +ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromFile(const char* path, int fd, const uint8_t* fileData, size_t lenFileData, uint64_t offsetInFat, uint64_t lenInFat, const struct stat& info, unsigned int segCount, unsigned int libCount, - const struct linkedit_data_command* codeSigCmd, const LinkContext& context) + const struct linkedit_data_command* codeSigCmd, + const struct encryption_info_command* encryptCmd, + const LinkContext& context) { ImageLoaderMachOCompressed* image = ImageLoaderMachOCompressed::instantiateStart((macho_header*)fileData, path, segCount, libCount); @@ -145,9 +136,15 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromFile(cons // if this image is code signed, let kernel validate signature before mapping any pages from image image->loadCodeSignature(codeSigCmd, fd, offsetInFat, context); + // Validate that first data we read with pread actually matches with code signature + image->validateFirstPages(codeSigCmd, fd, fileData, lenFileData, offsetInFat, context); + // mmap segments image->mapSegments(fd, offsetInFat, lenInFat, info.st_size, context); + // if framework is FairPlay encrypted, register with kernel + image->registerEncryption(encryptCmd, context); + // probe to see if code signed correctly image->crashIfInvalidCodeSignature(); @@ -209,6 +206,7 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromCache(con image->fInSharedCache = true; image->setNeverUnload(); image->setSlide(slide); + image->disableCoverageCheck(); // segments already mapped in cache if ( context.verboseMapping ) { @@ -247,6 +245,8 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromMemory(co // for compatibility, never unload dylibs loaded from memory image->setNeverUnload(); + image->disableCoverageCheck(); + // bundle loads need path copied if ( moduleName != NULL ) image->setPath(moduleName); @@ -297,7 +297,7 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateStart(const m void ImageLoaderMachOCompressed::instantiateFinish(const LinkContext& context) { // now that segments are mapped in, get real fMachOData, fLinkEditBase, and fSlide - this->parseLoadCmds(); + this->parseLoadCmds(context); } uint32_t* ImageLoaderMachOCompressed::segmentCommandOffsets() const @@ -374,11 +374,11 @@ void ImageLoaderMachOCompressed::markLINKEDIT(const LinkContext& context, int ad // round to whole pages - start = start & (-4096); - end = (end + 4095) & (-4096); + start = dyld_page_trunc(start); + end = dyld_page_round(end); // do nothing if only one page of rebase/bind info - if ( (end-start) <= 4096 ) + if ( (end-start) <= dyld_page_size ) return; // tell kernel about our access to these pages @@ -415,15 +415,14 @@ void ImageLoaderMachOCompressed::rebaseAt(const LinkContext& context, uintptr_t void ImageLoaderMachOCompressed::throwBadRebaseAddress(uintptr_t address, uintptr_t segmentEndAddress, int segmentIndex, const uint8_t* startOpcodes, const uint8_t* endOpcodes, const uint8_t* pos) { - dyld::throwf("malformed rebase opcodes (%ld/%ld): address 0x%08lX is beyond end of segment %s (0x%08lX -> 0x%08lX)", + dyld::throwf("malformed rebase opcodes (%ld/%ld): address 0x%08lX is outside of segment %s (0x%08lX -> 0x%08lX)", (intptr_t)(pos-startOpcodes), (intptr_t)(endOpcodes-startOpcodes), address, segName(segmentIndex), segActualLoadAddress(segmentIndex), segmentEndAddress); } -void ImageLoaderMachOCompressed::rebase(const LinkContext& context) +void ImageLoaderMachOCompressed::rebase(const LinkContext& context, uintptr_t slide) { CRSetCrashLogMessage2(this->getPath()); - const uintptr_t slide = this->fSlide; const uint8_t* const start = fLinkEditBase + fDyldInfo->rebase_off; const uint8_t* const end = &start[fDyldInfo->rebase_size]; const uint8_t* p = start; @@ -432,9 +431,10 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) uint8_t type = 0; int segmentIndex = 0; uintptr_t address = segActualLoadAddress(0); + uintptr_t segmentStartAddress = segActualLoadAddress(0); uintptr_t segmentEndAddress = segActualEndAddress(0); - uint32_t count; - uint32_t skip; + uintptr_t count; + uintptr_t skip; bool done = false; while ( !done && (p < end) ) { uint8_t immediate = *p & REBASE_IMMEDIATE_MASK; @@ -449,11 +449,19 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) break; case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: segmentIndex = immediate; - if ( segmentIndex > fSegmentsCount ) - dyld::throwf("REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (%d)\n", - segmentIndex, fSegmentsCount); - address = segActualLoadAddress(segmentIndex) + read_uleb128(p, end); + if ( segmentIndex >= fSegmentsCount ) + dyld::throwf("REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", + segmentIndex, fSegmentsCount-1); + #if TEXT_RELOC_SUPPORT + if ( !segWriteable(segmentIndex) && !segHasRebaseFixUps(segmentIndex) && !segHasBindFixUps(segmentIndex) ) + #else + if ( !segWriteable(segmentIndex) ) + #endif + dyld::throwf("REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is not a writable segment (%s)", + segmentIndex, segName(segmentIndex)); + segmentStartAddress = segActualLoadAddress(segmentIndex); segmentEndAddress = segActualEndAddress(segmentIndex); + address = segmentStartAddress + read_uleb128(p, end); break; case REBASE_OPCODE_ADD_ADDR_ULEB: address += read_uleb128(p, end); @@ -463,7 +471,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) break; case REBASE_OPCODE_DO_REBASE_IMM_TIMES: for (int i=0; i < immediate; ++i) { - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadRebaseAddress(address, segmentEndAddress, segmentIndex, start, end, p); rebaseAt(context, address, slide, type); address += sizeof(uintptr_t); @@ -473,7 +481,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) case REBASE_OPCODE_DO_REBASE_ULEB_TIMES: count = read_uleb128(p, end); for (uint32_t i=0; i < count; ++i) { - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadRebaseAddress(address, segmentEndAddress, segmentIndex, start, end, p); rebaseAt(context, address, slide, type); address += sizeof(uintptr_t); @@ -481,7 +489,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) fgTotalRebaseFixups += count; break; case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadRebaseAddress(address, segmentEndAddress, segmentIndex, start, end, p); rebaseAt(context, address, slide, type); address += read_uleb128(p, end) + sizeof(uintptr_t); @@ -491,7 +499,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) count = read_uleb128(p, end); skip = read_uleb128(p, end); for (uint32_t i=0; i < count; ++i) { - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadRebaseAddress(address, segmentEndAddress, segmentIndex, start, end, p); rebaseAt(context, address, slide, type); address += skip + sizeof(uintptr_t); @@ -499,7 +507,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) fgTotalRebaseFixups += count; break; default: - dyld::throwf("bad rebase opcode %d", *p); + dyld::throwf("bad rebase opcode %d", *(p-1)); } } } @@ -511,74 +519,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) CRSetCrashLogMessage2(NULL); } -// -// This function is the hotspot of symbol lookup. It was pulled out of findExportedSymbol() -// to enable it to be re-written in assembler if needed. -// -const uint8_t* ImageLoaderMachOCompressed::trieWalk(const uint8_t* start, const uint8_t* end, const char* s) -{ - const uint8_t* p = start; - while ( p != NULL ) { - uint32_t terminalSize = *p++; - if ( terminalSize > 127 ) { - // except for re-export-with-rename, all terminal sizes fit in one byte - --p; - terminalSize = read_uleb128(p, end); - } - if ( (*s == '\0') && (terminalSize != 0) ) { - //dyld::log("trieWalk(%p) returning %p\n", start, p); - return p; - } - const uint8_t* children = p + terminalSize; - //dyld::log("trieWalk(%p) sym=%s, terminalSize=%d, children=%p\n", start, s, terminalSize, children); - uint8_t childrenRemaining = *children++; - p = children; - uint32_t nodeOffset = 0; - for (; childrenRemaining > 0; --childrenRemaining) { - const char* ss = s; - //dyld::log("trieWalk(%p) child str=%s\n", start, (char*)p); - bool wrongEdge = false; - // scan whole edge to get to next edge - // if edge is longer than target symbol name, don't read past end of symbol name - char c = *p; - while ( c != '\0' ) { - if ( !wrongEdge ) { - if ( c != *ss ) - wrongEdge = true; - ++ss; - } - ++p; - c = *p; - } - if ( wrongEdge ) { - // advance to next child - ++p; // skip over zero terminator - // skip over uleb128 until last byte is found - while ( (*p & 0x80) != 0 ) - ++p; - ++p; // skil over last byte of uleb128 - } - else { - // the symbol so far matches this edge (child) - // so advance to the child's node - ++p; - nodeOffset = read_uleb128(p, end); - s = ss; - //dyld::log("trieWalk() found matching edge advancing to node 0x%x\n", nodeOffset); - break; - } - } - if ( nodeOffset != 0 ) - p = &start[nodeOffset]; - else - p = NULL; - } - //dyld::log("trieWalk(%p) return NULL\n", start); - return NULL; -} - - -const ImageLoader::Symbol* ImageLoaderMachOCompressed::findExportedSymbol(const char* symbol, const ImageLoader** foundIn) const +const ImageLoader::Symbol* ImageLoaderMachOCompressed::findShallowExportedSymbol(const char* symbol, const ImageLoader** foundIn) const { //dyld::log("Compressed::findExportedSymbol(%s) in %s\n", symbol, this->getShortName()); if ( fDyldInfo->export_size == 0 ) @@ -592,18 +533,19 @@ const ImageLoader::Symbol* ImageLoaderMachOCompressed::findExportedSymbol(const const uint8_t* foundNodeStart = this->trieWalk(start, end, symbol); if ( foundNodeStart != NULL ) { const uint8_t* p = foundNodeStart; - const uint32_t flags = read_uleb128(p, end); + const uintptr_t flags = read_uleb128(p, end); // found match, return pointer to terminal part of node if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) { // re-export from another dylib, lookup there - const uint32_t ordinal = read_uleb128(p, end); + const uintptr_t ordinal = read_uleb128(p, end); const char* importedName = (char*)p; if ( importedName[0] == '\0' ) importedName = symbol; if ( (ordinal > 0) && (ordinal <= libraryCount()) ) { - const ImageLoader* reexportedFrom = libImage(ordinal-1); + const ImageLoader* reexportedFrom = libImage((unsigned int)ordinal-1); //dyld::log("Compressed::findExportedSymbol(), %s -> %s/%s\n", symbol, reexportedFrom->getShortName(), importedName); - return reexportedFrom->findExportedSymbol(importedName, true, foundIn); + const char* reExportLibPath = libPath((unsigned int)ordinal-1); + return reexportedFrom->findExportedSymbol(importedName, true, reExportLibPath, foundIn); } else { //dyld::throwf("bad mach-o binary, library ordinal (%u) invalid (max %u) for re-exported symbol %s in %s", @@ -638,23 +580,17 @@ uintptr_t ImageLoaderMachOCompressed::exportedSymbolAddress(const LinkContext& c if ( (exportNode < exportTrieStart) || (exportNode > exportTrieEnd) ) throw "symbol is not in trie"; //dyld::log("exportedSymbolAddress(): node=%p, nodeOffset=0x%04X in %s\n", symbol, (int)((uint8_t*)symbol - exportTrieStart), this->getShortName()); - uint32_t flags = read_uleb128(exportNode, exportTrieEnd); + uintptr_t flags = read_uleb128(exportNode, exportTrieEnd); switch ( flags & EXPORT_SYMBOL_FLAGS_KIND_MASK ) { case EXPORT_SYMBOL_FLAGS_KIND_REGULAR: if ( runResolver && (flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) ) { // this node has a stub and resolver, run the resolver to get target address uintptr_t stub = read_uleb128(exportNode, exportTrieEnd) + (uintptr_t)fMachOData; // skip over stub // interposing dylibs have the stub address as their replacee - for (std::vector::iterator it=fgInterposingTuples.begin(); it != fgInterposingTuples.end(); it++) { - // replace all references to 'replacee' with 'replacement' - if ( (stub == it->replacee) && (requestor != it->replacementImage) ) { - if ( context.verboseInterposing ) { - dyld::log("dyld interposing: lazy replace 0x%lX with 0x%lX from %s\n", - it->replacee, it->replacement, this->getPath()); - } - return it->replacement; - } - } + uintptr_t interposedStub = interposedAddress(context, stub, requestor); + if ( interposedStub != stub ) + return interposedStub; + // stub was not interposed, so run resolver typedef uintptr_t (*ResolverProc)(void); ResolverProc resolver = (ResolverProc)(read_uleb128(exportNode, exportTrieEnd) + (uintptr_t)fMachOData); uintptr_t result = (*resolver)(); @@ -665,14 +601,14 @@ uintptr_t ImageLoaderMachOCompressed::exportedSymbolAddress(const LinkContext& c return read_uleb128(exportNode, exportTrieEnd) + (uintptr_t)fMachOData; case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) - dyld::throwf("unsupported exported symbol kind. flags=%d at node=%p", flags, symbol); + dyld::throwf("unsupported exported symbol kind. flags=%lu at node=%p", flags, symbol); return read_uleb128(exportNode, exportTrieEnd) + (uintptr_t)fMachOData; case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) - dyld::throwf("unsupported exported symbol kind. flags=%d at node=%p", flags, symbol); + dyld::throwf("unsupported exported symbol kind. flags=%lu at node=%p", flags, symbol); return read_uleb128(exportNode, exportTrieEnd); default: - dyld::throwf("unsupported exported symbol kind. flags=%d at node=%p", flags, symbol); + dyld::throwf("unsupported exported symbol kind. flags=%lu at node=%p", flags, symbol); } } @@ -683,7 +619,7 @@ bool ImageLoaderMachOCompressed::exportedSymbolIsWeakDefintion(const Symbol* sym const uint8_t* exportTrieEnd = exportTrieStart + fDyldInfo->export_size; if ( (exportNode < exportTrieStart) || (exportNode > exportTrieEnd) ) throw "symbol is not in trie"; - uint32_t flags = read_uleb128(exportNode, exportTrieEnd); + uintptr_t flags = read_uleb128(exportNode, exportTrieEnd); return ( flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION ); } @@ -732,7 +668,7 @@ uintptr_t ImageLoaderMachOCompressed::resolveFlat(const LinkContext& context, co // 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->ImageLoaderMachO::findExportedSymbol(symbolName, false, foundIn); + sym = this->findShallowExportedSymbol(symbolName, foundIn); if ( sym != NULL ) return (*foundIn)->getExportedSymbolAddress(sym, context, this, runResolver); } @@ -740,31 +676,136 @@ uintptr_t ImageLoaderMachOCompressed::resolveFlat(const LinkContext& context, co // definition can't be found anywhere, ok because it is weak, just return 0 return 0; } - throwSymbolNotFound(context, symbolName, this->getPath(), "flat namespace"); + throwSymbolNotFound(context, symbolName, this->getPath(), "", "flat namespace"); } -uintptr_t ImageLoaderMachOCompressed::resolveTwolevel(const LinkContext& context, const ImageLoader* targetImage, bool weak_import, - const char* symbolName, bool runResolver, const ImageLoader** foundIn) +#if USES_CHAINED_BINDS +static void patchCacheUsesOf(const ImageLoader::LinkContext& context, const dyld3::closure::Image* overriddenImage, + uint32_t cacheOffsetOfImpl, const char* symbolName, uintptr_t newImpl) { - // two level lookup - const Symbol* sym = targetImage->findExportedSymbol(symbolName, true, foundIn); - if ( sym != NULL ) { + uintptr_t cacheStart = (uintptr_t)context.dyldCache; + overriddenImage->forEachPatchableUseOfExport(cacheOffsetOfImpl, ^(dyld3::closure::Image::PatchableExport::PatchLocation patchLocation) { + uintptr_t* loc = (uintptr_t*)(cacheStart+patchLocation.cacheOffset); +#if __has_feature(ptrauth_calls) + if ( patchLocation.authenticated ) { + dyld3::MachOLoaded::ChainedFixupPointerOnDisk fixupInfo; + fixupInfo.authRebase.auth = true; + fixupInfo.authRebase.addrDiv = patchLocation.usesAddressDiversity; + fixupInfo.authRebase.diversity = patchLocation.discriminator; + fixupInfo.authRebase.key = patchLocation.key; + uintptr_t newValue = fixupInfo.signPointer(loc, newImpl + patchLocation.getAddend()); + if ( *loc != newValue ) { + *loc = newValue; + if ( context.verboseBind ) + dyld::log("dyld: cache fixup: *%p = %p (JOP: diversity 0x%04X, addr-div=%d, key=%s) to %s\n", + loc, (void*)newValue, patchLocation.discriminator, patchLocation.usesAddressDiversity, patchLocation.keyName(), symbolName); + } + return; + } +#endif + uintptr_t newValue =newImpl + patchLocation.getAddend(); + if ( *loc != newValue ) { + *loc = newValue; + if ( context.verboseBind ) + dyld::log("dyld: cache fixup: *%p = %p to %s\n", loc, (void*)newValue, symbolName); + } + }); +} +#endif + + +uintptr_t ImageLoaderMachOCompressed::resolveWeak(const LinkContext& context, const char* symbolName, bool weak_import, + bool runResolver, const ImageLoader** foundIn) +{ + const Symbol* sym; +#if USES_CHAINED_BINDS + __block uintptr_t foundOutsideCache = 0; + __block uintptr_t lastFoundInCache = 0; + CoalesceNotifier notifier = ^(const Symbol* implSym, const ImageLoader* implIn, const mach_header* implMh) { + //dyld::log("notifier: found %s in %p %s\n", symbolName, implMh, implIn->getPath()); + // This block is only called in dyld2 mode when a non-cached image is search for which weak-def implementation to use + // As a side effect of that search we notice any implementations outside and inside the cache, + // and use that to trigger patching the cache to use the implementation outside the cache. + uintptr_t implAddr = implIn->getExportedSymbolAddress(implSym, context, nullptr, false, symbolName); + if ( ((dyld3::MachOLoaded*)implMh)->inDyldCache() ) { + if ( foundOutsideCache != 0 ) { + // have an implementation in cache and and earlier one not in the cache, patch cache to use earlier one + lastFoundInCache = implAddr; + uint32_t imageIndex; + if ( context.dyldCache->findMachHeaderImageIndex(implMh, imageIndex) ) { + const dyld3::closure::Image* overriddenImage = context.dyldCache->cachedDylibsImageArray()->imageForNum(imageIndex+1); + uint32_t cacheOffsetOfImpl = (uint32_t)((uintptr_t)implAddr - (uintptr_t)context.dyldCache); + patchCacheUsesOf(context, overriddenImage, cacheOffsetOfImpl, symbolName, foundOutsideCache); + } + } + } + else { + // record first non-cache implementation + if ( foundOutsideCache == 0 ) + foundOutsideCache = implAddr; + } + }; +#else + CoalesceNotifier notifier = nullptr; +#endif + if ( context.coalescedExportFinder(symbolName, &sym, foundIn, notifier) ) { + if ( *foundIn != this ) + context.addDynamicReference(this, const_cast(*foundIn)); return (*foundIn)->getExportedSymbolAddress(sym, context, this, runResolver); } - + // 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->findShallowExportedSymbol(symbolName, foundIn); + if ( sym != NULL ) + return (*foundIn)->getExportedSymbolAddress(sym, context, this, runResolver); + } if ( weak_import ) { // definition can't be found anywhere, ok because it is weak, just return 0 return 0; } + throwSymbolNotFound(context, symbolName, this->getPath(), "", "weak"); +} + - // nowhere to be found - throwSymbolNotFound(context, symbolName, this->getPath(), targetImage->getPath()); +uintptr_t ImageLoaderMachOCompressed::resolveTwolevel(const LinkContext& context, const char* symbolName, const ImageLoader* definedInImage, + const ImageLoader* requestorImage, unsigned requestorOrdinalOfDef, bool weak_import, bool runResolver, + const ImageLoader** foundIn) +{ + // two level lookup + uintptr_t address; + if ( definedInImage->findExportedSymbolAddress(context, symbolName, requestorImage, requestorOrdinalOfDef, runResolver, foundIn, &address) ) + return address; + + if ( weak_import ) { + // definition can't be found anywhere, ok because it is weak, just return 0 + return 0; + } + + // nowhere to be found, check if maybe this image is too new for this OS + char versMismatch[256]; + versMismatch[0] = '\0'; + uint32_t imageMinOS = this->minOSVersion(); + // dyld is always built for the current OS, so we can get the current OS version + // from the load command in dyld itself. + extern const mach_header __dso_handle; + uint32_t dyldMinOS = ImageLoaderMachO::minOSVersion(&__dso_handle); + if ( imageMinOS > dyldMinOS ) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED + const char* msg = dyld::mkstringf(" (which was built for Mac OS X %d.%d)", imageMinOS >> 16, (imageMinOS >> 8) & 0xFF); +#else + const char* msg = dyld::mkstringf(" (which was built for iOS %d.%d)", imageMinOS >> 16, (imageMinOS >> 8) & 0xFF); +#endif + strcpy(versMismatch, msg); + ::free((void*)msg); + } + throwSymbolNotFound(context, symbolName, this->getPath(), versMismatch, definedInImage->getPath()); } uintptr_t ImageLoaderMachOCompressed::resolve(const LinkContext& context, const char* symbolName, - uint8_t symboFlags, int libraryOrdinal, const ImageLoader** targetImage, + uint8_t symboFlags, long libraryOrdinal, const ImageLoader** targetImage, LastLookup* last, bool runResolver) { *targetImage = NULL; @@ -784,6 +825,9 @@ uintptr_t ImageLoaderMachOCompressed::resolve(const LinkContext& context, const if ( context.bindFlat || (libraryOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP) ) { symbolAddress = this->resolveFlat(context, symbolName, weak_import, runResolver, targetImage); } + else if ( libraryOrdinal == BIND_SPECIAL_DYLIB_WEAK_LOOKUP ) { + symbolAddress = this->resolveWeak(context, symbolName, false, runResolver, targetImage); + } else { if ( libraryOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE ) { *targetImage = context.mainExecutable; @@ -792,14 +836,14 @@ uintptr_t ImageLoaderMachOCompressed::resolve(const LinkContext& context, const *targetImage = this; } else if ( libraryOrdinal <= 0 ) { - dyld::throwf("bad mach-o binary, unknown special library ordinal (%u) too big for symbol %s in %s", + dyld::throwf("bad mach-o binary, unknown special library ordinal (%ld) too big for symbol %s in %s", libraryOrdinal, symbolName, this->getPath()); } else if ( (unsigned)libraryOrdinal <= libraryCount() ) { - *targetImage = libImage(libraryOrdinal-1); + *targetImage = libImage((unsigned int)libraryOrdinal-1); } else { - dyld::throwf("bad mach-o binary, library ordinal (%u) too big (max %u) for symbol %s in %s", + dyld::throwf("bad mach-o binary, library ordinal (%ld) too big (max %u) for symbol %s in %s", libraryOrdinal, libraryCount(), symbolName, this->getPath()); } if ( *targetImage == NULL ) { @@ -808,15 +852,15 @@ uintptr_t ImageLoaderMachOCompressed::resolve(const LinkContext& context, const symbolAddress = 0; } else { - dyld::throwf("can't resolve symbol %s in %s because dependent dylib #%d could not be loaded", + dyld::throwf("can't resolve symbol %s in %s because dependent dylib #%ld could not be loaded", symbolName, this->getPath(), libraryOrdinal); } } else { - symbolAddress = resolveTwolevel(context, *targetImage, weak_import, symbolName, runResolver, targetImage); + symbolAddress = resolveTwolevel(context, symbolName, *targetImage, this, (unsigned)libraryOrdinal, weak_import, runResolver, targetImage); } } - + // save off lookup results if client wants if ( last != NULL ) { last->ordinal = libraryOrdinal; @@ -829,24 +873,31 @@ uintptr_t ImageLoaderMachOCompressed::resolve(const LinkContext& context, const return symbolAddress; } -uintptr_t ImageLoaderMachOCompressed::bindAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char* symbolName, - uint8_t symboFlags, intptr_t addend, int libraryOrdinal, const char* msg, - LastLookup* last, bool runResolver) +uintptr_t ImageLoaderMachOCompressed::bindAt(const LinkContext& context, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { const ImageLoader* targetImage; uintptr_t symbolAddress; // resolve symbol - symbolAddress = this->resolve(context, symbolName, symboFlags, libraryOrdinal, &targetImage, last, runResolver); + if (type == BIND_TYPE_THREADED_REBASE) { + symbolAddress = 0; + targetImage = nullptr; + } else + symbolAddress = image->resolve(context, symbolName, symbolFlags, libraryOrdinal, &targetImage, last, runResolver); // do actual update - return this->bindLocation(context, addr, symbolAddress, targetImage, type, symbolName, addend, msg); + return image->bindLocation(context, image->imageBaseAddress(), addr, symbolAddress, type, symbolName, addend, image->getPath(), targetImage ? targetImage->getPath() : NULL, msg, extraBindData, image->fSlide); } + void ImageLoaderMachOCompressed::throwBadBindingAddress(uintptr_t address, uintptr_t segmentEndAddress, int segmentIndex, const uint8_t* startOpcodes, const uint8_t* endOpcodes, const uint8_t* pos) { - dyld::throwf("malformed binding opcodes (%ld/%ld): address 0x%08lX is beyond end of segment %s (0x%08lX -> 0x%08lX)", + dyld::throwf("malformed binding opcodes (%ld/%ld): address 0x%08lX is outside segment %s (0x%08lX -> 0x%08lX)", (intptr_t)(pos-startOpcodes), (intptr_t)(endOpcodes-startOpcodes), address, segName(segmentIndex), segActualLoadAddress(segmentIndex), segmentEndAddress); } @@ -860,17 +911,45 @@ void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLa // note: flat-namespace binaries need to have imports rebound (even if correctly prebound) if ( this->usablePrebinding(context) ) { // don't need to bind + // except weak which may now be inline with the regular binds + if (this->participatesInCoalescing()) { + // run through all binding opcodes + eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + if ( libraryOrdinal != BIND_SPECIAL_DYLIB_WEAK_LOOKUP ) + return (uintptr_t)0; + return ImageLoaderMachOCompressed::bindAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); + } } else { - + uint64_t t0 = mach_absolute_time(); + #if TEXT_RELOC_SUPPORT // if there are __TEXT fixups, temporarily make __TEXT writable if ( fTextSegmentBinds ) this->makeTextSegmentWritable(context, true); #endif - + + uint32_t ignore; + bool bindingBecauseOfRoot = ( this->overridesCachedDylib(ignore) || this->inSharedCache() ); + vmAccountingSetSuspended(context, bindingBecauseOfRoot); + // run through all binding opcodes - eachBind(context, &ImageLoaderMachOCompressed::bindAt); + eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::bindAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); #if TEXT_RELOC_SUPPORT // if there were __TEXT fixups, restore write protection @@ -888,11 +967,31 @@ void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLa // the stub may have been altered to point to a shared lazy pointer. if ( fInSharedCache ) this->updateOptimizedLazyPointers(context); - + // tell kernel we are done with chunks of LINKEDIT if ( !context.preFetchDisabled ) this->markFreeLINKEDIT(context); + + uint64_t t1 = mach_absolute_time(); + ImageLoader::fgTotalRebindCacheTime += (t1-t0); + } + +#if USES_CHAINED_BINDS + // See if this dylib overrides something in the dyld cache + uint32_t dyldCacheOverrideImageNum; + if ( context.dyldCache && overridesCachedDylib(dyldCacheOverrideImageNum) ) { + // need to patch all other places in cache that point to the overridden dylib, to point to this dylib instead + const dyld3::closure::Image* overriddenImage = context.dyldCache->cachedDylibsImageArray()->imageForNum(dyldCacheOverrideImageNum); + overriddenImage->forEachPatchableExport(^(uint32_t cacheOffsetOfImpl, const char* exportName) { + uintptr_t newImpl = 0; + const ImageLoader* foundIn; + if ( const ImageLoader::Symbol* sym = this->findShallowExportedSymbol(exportName, &foundIn) ) { + newImpl = foundIn->getExportedSymbolAddress(sym, context, this); + } + patchCacheUsesOf(context, overriddenImage, cacheOffsetOfImpl, exportName, newImpl); + }); } +#endif // set up dyld entry points in image // do last so flat main executables will have __dyld or __program_vars set up @@ -903,22 +1002,196 @@ void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLa void ImageLoaderMachOCompressed::doBindJustLazies(const LinkContext& context) { - eachLazyBind(context, &ImageLoaderMachOCompressed::bindAt); + eachLazyBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::bindAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); +} + +void ImageLoaderMachOCompressed::registerInterposing(const LinkContext& context) +{ + // 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_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_INTERPOSING) || ((strcmp(sect->sectname, "__interpose") == 0) && (strcmp(seg->segname, "__DATA") == 0)) ) { + // Ensure section is within segment + if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) ) + dyld::throwf("interpose section has malformed address range for %s\n", this->getPath()); + __block uintptr_t sectionStart = sect->addr + fSlide; + __block uintptr_t sectionEnd = sectionStart + sect->size; + const size_t count = sect->size / sizeof(InterposeData); + InterposeData interposeArray[count]; + // Note, we memcpy here as rebases may have already been applied. + memcpy(&interposeArray[0], (void*)sectionStart, sect->size); + __block InterposeData *interposeArrayStart = &interposeArray[0]; + eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, uintptr_t addr, uint8_t type, + const char* symbolName, uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + if (addr >= sectionStart && addr < sectionEnd) { + if ( context.verboseInterposing ) { + dyld::log("dyld: interposing %s at 0x%lx in range 0x%lx..0x%lx\n", + symbolName, addr, sectionStart, sectionEnd); + } + const ImageLoader* targetImage; + uintptr_t symbolAddress; + + // resolve symbol + if (type == BIND_TYPE_THREADED_REBASE) { + symbolAddress = 0; + targetImage = nullptr; + } else + symbolAddress = image->resolve(ctx, symbolName, symbolFlags, libraryOrdinal, &targetImage, last, runResolver); + + uintptr_t newValue = symbolAddress+addend; + uintptr_t index = (addr - sectionStart) / sizeof(uintptr_t); + switch (type) { + case BIND_TYPE_POINTER: + ((uintptr_t*)interposeArrayStart)[index] = newValue; + break; + case BIND_TYPE_TEXT_ABSOLUTE32: + // unreachable! + abort(); + case BIND_TYPE_TEXT_PCREL32: + // unreachable! + abort(); + case BIND_TYPE_THREADED_BIND: + ((uintptr_t*)interposeArrayStart)[index] = newValue; + break; + case BIND_TYPE_THREADED_REBASE: { + // Regular pointer which needs to fit in 51-bits of value. + // C++ RTTI uses the top bit, so we'll allow the whole top-byte + // and the signed-extended bottom 43-bits to be fit in to 51-bits. + uint64_t top8Bits = (*(uint64_t*)addr) & 0x0007F80000000000ULL; + uint64_t bottom43Bits = (*(uint64_t*)addr) & 0x000007FFFFFFFFFFULL; + uint64_t targetValue = ( top8Bits << 13 ) | (((intptr_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF); + newValue = (uintptr_t)(targetValue + fSlide); + ((uintptr_t*)interposeArrayStart)[index] = newValue; + break; + } + default: + dyld::throwf("bad bind type %d", type); + } + } + return (uintptr_t)0; + }); + for (size_t j=0; j < count; ++j) { + ImageLoader::InterposeTuple tuple; + tuple.replacement = interposeArray[j].replacement; + tuple.neverImage = this; + tuple.onlyImage = NULL; + tuple.replacee = interposeArray[j].replacee; + if ( context.verboseInterposing ) { + dyld::log("dyld: interposing index %d 0x%lx with 0x%lx\n", + (unsigned)j, interposeArray[j].replacee, interposeArray[j].replacement); + } + // ignore interposing on a weak function that does not exist + if ( tuple.replacee == 0 ) + continue; + // 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); + } + } + } + } + } + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } +} + +bool ImageLoaderMachOCompressed::usesChainedFixups() const +{ +#if __arm64e__ + return ( machHeader()->cpusubtype == CPU_SUBTYPE_ARM64_E ); +#else + return false; +#endif } +struct ThreadedBindData { + ThreadedBindData(const char* symbolName, int64_t addend, long libraryOrdinal, uint8_t symbolFlags, uint8_t type) + : symbolName(symbolName), addend(addend), libraryOrdinal(libraryOrdinal), symbolFlags(symbolFlags), type(type) { } + + std::tuple pack() const { + return std::make_tuple(symbolName, addend, libraryOrdinal, symbolFlags, type); + } + + const char* symbolName = nullptr; + int64_t addend = 0; + long libraryOrdinal = 0; + uint8_t symbolFlags = 0; + uint8_t type = 0; +}; + void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handler handler) { - try { + try { + const dysymtab_command* dynSymbolTable = NULL; + const macho_nlist* symbolTable = NULL; + const char* symbolTableStrings = NULL; + uint32_t maxStringOffset = 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_SYMTAB: + { + const struct symtab_command* symtab = (struct symtab_command*)cmd; + symbolTableStrings = (const char*)&fLinkEditBase[symtab->stroff]; + maxStringOffset = symtab->strsize; + symbolTable = (macho_nlist*)(&fLinkEditBase[symtab->symoff]); + } + break; + case LC_DYSYMTAB: + dynSymbolTable = (struct dysymtab_command*)cmd; + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + uint8_t type = 0; - int segmentIndex = 0; + int segmentIndex = -1; uintptr_t address = segActualLoadAddress(0); + uintptr_t segmentStartAddress = segActualLoadAddress(0); uintptr_t segmentEndAddress = segActualEndAddress(0); const char* symbolName = NULL; uint8_t symboFlags = 0; - int libraryOrdinal = 0; + bool libraryOrdinalSet = false; + long libraryOrdinal = 0; intptr_t addend = 0; - uint32_t count; - uint32_t skip; + uintptr_t count; + uintptr_t skip; + uintptr_t segOffset = 0; + + dyld3::OverflowSafeArray ordinalTable; + bool useThreadedRebaseBind = false; + ExtraBindData extraBindData; LastLookup last = { 0, 0, NULL, 0, NULL }; const uint8_t* const start = fLinkEditBase + fDyldInfo->bind_off; const uint8_t* const end = &start[fDyldInfo->bind_size]; @@ -934,9 +1207,11 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl break; case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: libraryOrdinal = immediate; + libraryOrdinalSet = true; break; case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: libraryOrdinal = read_uleb128(p, end); + libraryOrdinalSet = true; break; case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: // the special ordinals are negative numbers @@ -946,6 +1221,7 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl int8_t signExtended = BIND_OPCODE_MASK | immediate; libraryOrdinal = signExtended; } + libraryOrdinalSet = true; break; case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: symbolName = (char*)p; @@ -962,43 +1238,143 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl break; case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: segmentIndex = immediate; - if ( segmentIndex > fSegmentsCount ) - dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (%d)\n", - segmentIndex, fSegmentsCount); - address = segActualLoadAddress(segmentIndex) + read_uleb128(p, end); + if ( (segmentIndex >= fSegmentsCount) || (segmentIndex < 0) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is out of range (0..%d)", + segmentIndex, fSegmentsCount-1); + #if TEXT_RELOC_SUPPORT + if ( !segWriteable(segmentIndex) && !segHasRebaseFixUps(segmentIndex) && !segHasBindFixUps(segmentIndex) ) + #else + if ( !segWriteable(segmentIndex) ) + #endif + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is not writable", segmentIndex); + segOffset = read_uleb128(p, end); + if ( segOffset > segSize(segmentIndex) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(segmentIndex)); + segmentStartAddress = segActualLoadAddress(segmentIndex); + address = segmentStartAddress + segOffset; segmentEndAddress = segActualEndAddress(segmentIndex); break; case BIND_OPCODE_ADD_ADDR_ULEB: address += read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: - if ( address >= segmentEndAddress ) - throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); - (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); - address += sizeof(intptr_t); + if (!useThreadedRebaseBind) { + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) + throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); + if ( !libraryOrdinalSet ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); + handler(context, this, address, type, symbolName, symboFlags, addend, libraryOrdinal, + &extraBindData, "", &last, false); + address += sizeof(intptr_t); + } else { + ordinalTable.push_back(ThreadedBindData(symbolName, addend, libraryOrdinal, symboFlags, type)); + } break; case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); - (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); + if ( !libraryOrdinalSet ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); + handler(context, this, address, type, symbolName, symboFlags, addend, libraryOrdinal, + &extraBindData, "", &last, false); address += read_uleb128(p, end) + sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); - (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); + if ( !libraryOrdinalSet ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); + handler(context, this, address, type, symbolName, symboFlags, addend, libraryOrdinal, + &extraBindData, "", &last, false); address += immediate*sizeof(intptr_t) + sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); count = read_uleb128(p, end); + if ( !libraryOrdinalSet ) + dyld::throwf("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); skip = read_uleb128(p, end); for (uint32_t i=0; i < count; ++i) { - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); - (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); + handler(context, this, address, type, symbolName, symboFlags, addend, libraryOrdinal, + &extraBindData, "", &last, false); address += skip + sizeof(intptr_t); } - break; + break; + case BIND_OPCODE_THREADED: + if (sizeof(intptr_t) != 8) { + dyld::throwf("BIND_OPCODE_THREADED require 64-bit"); + return; + } + // Note the immediate is a sub opcode + switch (immediate) { + case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB: + count = read_uleb128(p, end); + ordinalTable.clear(); + // FIXME: ld64 wrote the wrong value here and we need to offset by 1 for now. + ordinalTable.reserve(count + 1); + useThreadedRebaseBind = true; + break; + case BIND_SUBOPCODE_THREADED_APPLY: { + uint64_t delta = 0; + do { + address = segmentStartAddress + segOffset; + uint64_t value = *(uint64_t*)address; + + + bool isRebase = (value & (1ULL << 62)) == 0; + if (isRebase) { + { + // Call the bind handler which knows about our bind type being set to rebase + handler(context, this, address, BIND_TYPE_THREADED_REBASE, nullptr, 0, 0, 0, + nullptr, "", &last, false); + } + } else { + // the ordinal is bits [0..15] + uint16_t ordinal = value & 0xFFFF; + if (ordinal >= ordinalTable.count()) { + dyld::throwf("bind ordinal is out of range\n"); + return; + } + std::tie(symbolName, addend, libraryOrdinal, symboFlags, type) = ordinalTable[ordinal].pack(); + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) + throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); + { + handler(context, this, address, BIND_TYPE_THREADED_BIND, + symbolName, symboFlags, addend, libraryOrdinal, + nullptr, "", &last, false); + } + } + + // The delta is bits [51..61] + // And bit 62 is to tell us if we are a rebase (0) or bind (1) + value &= ~(1ULL << 62); + delta = ( value & 0x3FF8000000000000 ) >> 51; + segOffset += delta * sizeof(intptr_t); + } while ( delta != 0 ); + break; + } + + default: + dyld::throwf("bad threaded bind subopcode 0x%02X", *p); + } + break; default: dyld::throwf("bad bind opcode %d in bind info", *p); } @@ -1015,12 +1391,14 @@ void ImageLoaderMachOCompressed::eachLazyBind(const LinkContext& context, bind_h { try { uint8_t type = BIND_TYPE_POINTER; - int segmentIndex = 0; + int segmentIndex = -1; uintptr_t address = segActualLoadAddress(0); + uintptr_t segmentStartAddress = segActualLoadAddress(0); uintptr_t segmentEndAddress = segActualEndAddress(0); + uintptr_t segOffset; const char* symbolName = NULL; uint8_t symboFlags = 0; - int libraryOrdinal = 0; + long libraryOrdinal = 0; intptr_t addend = 0; const uint8_t* const start = fLinkEditBase + fDyldInfo->lazy_bind_off; const uint8_t* const end = &start[fDyldInfo->lazy_bind_size]; @@ -1064,19 +1442,30 @@ void ImageLoaderMachOCompressed::eachLazyBind(const LinkContext& context, bind_h break; case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: segmentIndex = immediate; - if ( segmentIndex > fSegmentsCount ) - dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (%d)\n", - segmentIndex, fSegmentsCount); - address = segActualLoadAddress(segmentIndex) + read_uleb128(p, end); + if ( (segmentIndex >= fSegmentsCount) || (segmentIndex < 0) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is out of range (0..%d)", + segmentIndex, fSegmentsCount-1); + if ( !segWriteable(segmentIndex) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is not writable", segmentIndex); + segOffset = read_uleb128(p, end); + if ( segOffset > segSize(segmentIndex) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(segmentIndex)); + segmentStartAddress = segActualLoadAddress(segmentIndex); segmentEndAddress = segActualEndAddress(segmentIndex); + address = segmentStartAddress + segOffset; break; case BIND_OPCODE_ADD_ADDR_ULEB: address += read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: - if ( address >= segmentEndAddress ) + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); - (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "forced lazy ", NULL, false); + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); + handler(context, this, address, type, symbolName, symboFlags, addend, libraryOrdinal, + NULL, "forced lazy ", NULL, false); address += sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: @@ -1140,11 +1529,11 @@ uintptr_t ImageLoaderMachOCompressed::doBindLazySymbol(uintptr_t* lazyPointer, c const uint8_t type = sect->flags & SECTION_TYPE; uint32_t symbolIndex = INDIRECT_SYMBOL_LOCAL; if ( type == S_LAZY_SYMBOL_POINTERS ) { - const uint32_t pointerCount = sect->size / sizeof(uintptr_t); + const size_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; + const size_t lazyIndex = lazyPointer - symbolPointers; symbolIndex = indirectTable[indirectTableOffset + lazyIndex]; } } @@ -1155,7 +1544,8 @@ uintptr_t ImageLoaderMachOCompressed::doBindLazySymbol(uintptr_t* lazyPointer, c if ( !twoLevel || context.bindFlat ) libraryOrdinal = BIND_SPECIAL_DYLIB_FLAT_LOOKUP; uintptr_t ptrToBind = (uintptr_t)lazyPointer; - uintptr_t symbolAddr = bindAt(context, ptrToBind, BIND_TYPE_POINTER, symbolName, 0, 0, libraryOrdinal, "lazy ", NULL); + uintptr_t symbolAddr = bindAt(context, this, ptrToBind, BIND_TYPE_POINTER, symbolName, 0, 0, libraryOrdinal, + NULL, "lazy ", NULL); ++fgTotalLazyBindFixups; return symbolAddr; } @@ -1169,6 +1559,7 @@ uintptr_t ImageLoaderMachOCompressed::doBindLazySymbol(uintptr_t* lazyPointer, c } + uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context, void (*lock)(), void (*unlock)()) { @@ -1184,73 +1575,27 @@ uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol(uint32_t lazyBindingI const uint8_t* const start = fLinkEditBase + fDyldInfo->lazy_bind_off; const uint8_t* const end = &start[fDyldInfo->lazy_bind_size]; - if ( lazyBindingInfoOffset > fDyldInfo->lazy_bind_size ) { - dyld::throwf("fast lazy bind offset out of range (%u, max=%u) in image %s", - lazyBindingInfoOffset, fDyldInfo->lazy_bind_size, this->getPath()); - } + uint8_t segIndex; + uintptr_t segOffset; + int libraryOrdinal; + const char* symbolName; + bool doneAfterBind; + uintptr_t result; + do { + if ( ! getLazyBindingInfo(lazyBindingInfoOffset, start, end, &segIndex, &segOffset, &libraryOrdinal, &symbolName, &doneAfterBind) ) + dyld::throwf("bad lazy bind info"); + + if ( segIndex >= fSegmentsCount ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", + segIndex, fSegmentsCount-1); + if ( segOffset > segSize(segIndex) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(segIndex)); + uintptr_t address = segActualLoadAddress(segIndex) + segOffset; + result = bindAt(context, this, address, BIND_TYPE_POINTER, symbolName, 0, 0, libraryOrdinal, + NULL, "lazy ", NULL, true); + // Some old apps had multiple lazy symbols bound at once + } while (!doneAfterBind && !context.strictMachORequired); - uint8_t type = BIND_TYPE_POINTER; - uintptr_t address = 0; - const char* symbolName = NULL; - uint8_t symboFlags = 0; - int libraryOrdinal = 0; - bool done = false; - uintptr_t result = 0; - const uint8_t* p = &start[lazyBindingInfoOffset]; - while ( !done && (p < end) ) { - uint8_t immediate = *p & BIND_IMMEDIATE_MASK; - uint8_t opcode = *p & BIND_OPCODE_MASK; - ++p; - switch (opcode) { - case BIND_OPCODE_DONE: - done = true; - break; - case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: - libraryOrdinal = immediate; - break; - case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: - libraryOrdinal = read_uleb128(p, end); - break; - case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: - // the special ordinals are negative numbers - if ( immediate == 0 ) - libraryOrdinal = 0; - else { - int8_t signExtended = BIND_OPCODE_MASK | immediate; - libraryOrdinal = signExtended; - } - break; - case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: - symbolName = (char*)p; - symboFlags = immediate; - while (*p != '\0') - ++p; - ++p; - break; - case BIND_OPCODE_SET_TYPE_IMM: - type = immediate; - break; - case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: - if ( immediate > fSegmentsCount ) - dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (%d)\n", - immediate, fSegmentsCount); - address = segActualLoadAddress(immediate) + read_uleb128(p, end); - break; - case BIND_OPCODE_DO_BIND: - - - result = this->bindAt(context, address, type, symbolName, 0, 0, libraryOrdinal, "lazy ", NULL, true); - break; - case BIND_OPCODE_SET_ADDEND_SLEB: - case BIND_OPCODE_ADD_ADDR_ULEB: - case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: - default: - dyld::throwf("bad lazy bind opcode %d", *p); - } - } - if ( !this->usesTwoLevelNameSpace() ) { // release dyld global lock if ( unlock != NULL ) @@ -1259,7 +1604,7 @@ uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol(uint32_t lazyBindingI return result; } -void ImageLoaderMachOCompressed::initializeCoalIterator(CoalIterator& it, unsigned int loadOrder) +void ImageLoaderMachOCompressed::initializeCoalIterator(CoalIterator& it, unsigned int loadOrder, unsigned) { it.image = this; it.symbolName = " "; @@ -1289,8 +1634,9 @@ bool ImageLoaderMachOCompressed::incrementCoalIterator(CoalIterator& it) const uint8_t* start = fLinkEditBase + fDyldInfo->weak_bind_off; const uint8_t* p = start + it.curIndex; const uint8_t* end = fLinkEditBase + fDyldInfo->weak_bind_off + this->fDyldInfo->weak_bind_size; - uint32_t count; - uint32_t skip; + uintptr_t count; + uintptr_t skip; + uintptr_t segOffset; while ( p < end ) { uint8_t immediate = *p & BIND_IMMEDIATE_MASK; uint8_t opcode = *p & BIND_OPCODE_MASK; @@ -1317,10 +1663,24 @@ bool ImageLoaderMachOCompressed::incrementCoalIterator(CoalIterator& it) it.addend = read_sleb128(p, end); break; case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: - if ( immediate > fSegmentsCount ) - dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (%d)\n", - immediate, fSegmentsCount); - it.address = segActualLoadAddress(immediate) + read_uleb128(p, end); + if ( immediate >= fSegmentsCount ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", + immediate, fSegmentsCount-1); + #if __arm__ + // iOS app compatibility + if ( !segWriteable(immediate) && it.image->isPositionIndependentExecutable() ) + #elif TEXT_RELOC_SUPPORT + // i386 OS X app compatibility + if ( !segWriteable(immediate) && !segHasRebaseFixUps(immediate) && !segHasBindFixUps(immediate) + && (!it.image->isExecutable() || it.image->isPositionIndependentExecutable()) ) + #else + if ( !segWriteable(immediate) ) + #endif + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB targets segment %s which is not writable", segName(immediate)); + segOffset = read_uleb128(p, end); + if ( segOffset > segSize(immediate) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(immediate)); + it.address = segActualLoadAddress(immediate) + segOffset; break; case BIND_OPCODE_ADD_ADDR_ULEB: it.address += read_uleb128(p, end); @@ -1356,7 +1716,7 @@ uintptr_t ImageLoaderMachOCompressed::getAddressCoalIterator(CoalIterator& it, c { //dyld::log("looking for %s in %s\n", it.symbolName, this->getPath()); const ImageLoader* foundIn = NULL; - const ImageLoader::Symbol* sym = this->findExportedSymbol(it.symbolName, &foundIn); + const ImageLoader::Symbol* sym = this->findShallowExportedSymbol(it.symbolName, &foundIn); if ( sym != NULL ) { //dyld::log("sym=%p, foundIn=%p\n", sym, foundIn); return foundIn->getExportedSymbolAddress(sym, context, this); @@ -1365,7 +1725,7 @@ uintptr_t ImageLoaderMachOCompressed::getAddressCoalIterator(CoalIterator& it, c } -void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintptr_t value, ImageLoader* targetImage, const LinkContext& context) +void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintptr_t value, ImageLoader* targetImage, unsigned targetIndex, const LinkContext& context) { // weak binding done too early with inserted libraries if ( this->getState() < dyld_image_state_bound ) @@ -1379,8 +1739,9 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt uintptr_t address = it.address; const char* symbolName = it.symbolName; intptr_t addend = it.addend; - uint32_t count; - uint32_t skip; + uintptr_t count; + uintptr_t skip; + uintptr_t segOffset; bool done = false; bool boundSomething = false; while ( !done && (p < end) ) { @@ -1401,26 +1762,40 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt addend = read_sleb128(p, end); break; case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: - if ( immediate > fSegmentsCount ) - dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (%d)\n", - immediate, fSegmentsCount); - address = segActualLoadAddress(immediate) + read_uleb128(p, end); + if ( immediate >= fSegmentsCount ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", + immediate, fSegmentsCount-1); + #if __arm__ + // iOS app compatibility + if ( !segWriteable(immediate) && it.image->isPositionIndependentExecutable() ) + #elif TEXT_RELOC_SUPPORT + // i386 OS X app compatibility + if ( !segWriteable(immediate) && !segHasRebaseFixUps(immediate) && !segHasBindFixUps(immediate) + && (!it.image->isExecutable() || it.image->isPositionIndependentExecutable()) ) + #else + if ( !segWriteable(immediate) ) + #endif + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB targets segment %s which is not writable", segName(immediate)); + segOffset = read_uleb128(p, end); + if ( segOffset > segSize(immediate) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(immediate)); + address = segActualLoadAddress(immediate) + segOffset; break; case BIND_OPCODE_ADD_ADDR_ULEB: address += read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: - bindLocation(context, address, value, targetImage, type, symbolName, addend, "weak "); + bindLocation(context, this->imageBaseAddress(), address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak ", NULL, fSlide); boundSomething = true; address += sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - bindLocation(context, address, value, targetImage, type, symbolName, addend, "weak "); + bindLocation(context, this->imageBaseAddress(), address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak ", NULL, fSlide); boundSomething = true; address += read_uleb128(p, end) + sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - bindLocation(context, address, value, targetImage, type, symbolName, addend, "weak "); + bindLocation(context, this->imageBaseAddress(), address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak ", NULL, fSlide); boundSomething = true; address += immediate*sizeof(intptr_t) + sizeof(intptr_t); break; @@ -1428,7 +1803,7 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt count = read_uleb128(p, end); skip = read_uleb128(p, end); for (uint32_t i=0; i < count; ++i) { - bindLocation(context, address, value, targetImage, type, symbolName, addend, "weak "); + bindLocation(context, this->imageBaseAddress(), address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak ", NULL, fSlide); boundSomething = true; address += skip + sizeof(intptr_t); } @@ -1442,21 +1817,18 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt context.addDynamicReference(this, targetImage); } -uintptr_t ImageLoaderMachOCompressed::interposeAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char*, - uint8_t, intptr_t, int, const char*, LastLookup*, bool runResolver) +uintptr_t ImageLoaderMachOCompressed::interposeAt(const LinkContext& context, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char*, + uint8_t, intptr_t, long, + ExtraBindData *extraBindData, + const char*, LastLookup*, bool runResolver) { if ( type == BIND_TYPE_POINTER ) { uintptr_t* fixupLocation = (uintptr_t*)addr; - uintptr_t value = *fixupLocation; - for (std::vector::iterator it=fgInterposingTuples.begin(); it != fgInterposingTuples.end(); it++) { - // replace all references to 'replacee' with 'replacement' - if ( (value == it->replacee) && (this != it->replacementImage) ) { - if ( context.verboseInterposing ) { - dyld::log("dyld: interposing: at %p replace 0x%lX with 0x%lX in %s\n", - fixupLocation, it->replacee, it->replacement, this->getPath()); - } - *fixupLocation = it->replacement; - } + uintptr_t curValue = *fixupLocation; + uintptr_t newValue = interposedAddress(context, curValue, image); + if ( newValue != curValue) { + *fixupLocation = newValue; } } return 0; @@ -1468,84 +1840,81 @@ void ImageLoaderMachOCompressed::doInterpose(const LinkContext& context) dyld::log("dyld: interposing %lu tuples onto image: %s\n", fgInterposingTuples.size(), this->getPath()); // update prebound symbols - eachBind(context, &ImageLoaderMachOCompressed::interposeAt); - eachLazyBind(context, &ImageLoaderMachOCompressed::interposeAt); + eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::interposeAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); + eachLazyBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::interposeAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); } - - -const char* ImageLoaderMachOCompressed::findClosestSymbol(const void* addr, const void** closestAddr) const +uintptr_t ImageLoaderMachOCompressed::dynamicInterposeAt(const LinkContext& context, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t, intptr_t, long, + ExtraBindData *extraBindData, + const char*, LastLookup*, bool runResolver) { - // called by dladdr() - // only works with compressed LINKEDIT if classic symbol table is also present - const macho_nlist* symbolTable = NULL; - const char* symbolTableStrings = 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]); + if ( type == BIND_TYPE_POINTER ) { + uintptr_t* fixupLocation = (uintptr_t*)addr; + uintptr_t value = *fixupLocation; + // don't apply interposing to table entries. + if ( (context.dynamicInterposeArray <= (void*)addr) && ((void*)addr < &context.dynamicInterposeArray[context.dynamicInterposeCount]) ) + return 0; + for(size_t i=0; i < context.dynamicInterposeCount; ++i) { + if ( value == (uintptr_t)context.dynamicInterposeArray[i].replacee ) { + if ( context.verboseInterposing ) { + dyld::log("dyld: dynamic interposing: at %p replace %p with %p in %s\n", + fixupLocation, context.dynamicInterposeArray[i].replacee, context.dynamicInterposeArray[i].replacement, image->getPath()); } - break; - case LC_DYSYMTAB: - dynSymbolTable = (struct dysymtab_command*)cmd; - break; - } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); - } - // no symbol table => no lookup by address - if ( (symbolTable == NULL) || (dynSymbolTable == NULL) ) - return NULL; - - uintptr_t targetAddress = (uintptr_t)addr - fSlide; - const struct macho_nlist* bestSymbol = NULL; - // first walk all global symbols - const struct macho_nlist* const globalsStart = &symbolTable[dynSymbolTable->iextdefsym]; - const struct macho_nlist* const globalsEnd= &globalsStart[dynSymbolTable->nextdefsym]; - for (const struct macho_nlist* s = globalsStart; s < globalsEnd; ++s) { - if ( (s->n_type & N_TYPE) == N_SECT ) { - if ( bestSymbol == NULL ) { - if ( s->n_value <= targetAddress ) - bestSymbol = s; - } - else if ( (s->n_value <= targetAddress) && (bestSymbol->n_value < s->n_value) ) { - bestSymbol = s; + *fixupLocation = (uintptr_t)context.dynamicInterposeArray[i].replacement; } } } - // next walk all local symbols - const struct macho_nlist* const localsStart = &symbolTable[dynSymbolTable->ilocalsym]; - const struct macho_nlist* const localsEnd= &localsStart[dynSymbolTable->nlocalsym]; - for (const struct macho_nlist* s = localsStart; s < localsEnd; ++s) { - if ( ((s->n_type & N_TYPE) == N_SECT) && ((s->n_type & N_STAB) == 0) ) { - if ( bestSymbol == NULL ) { - if ( s->n_value <= targetAddress ) - bestSymbol = s; - } - else if ( (s->n_value <= targetAddress) && (bestSymbol->n_value < s->n_value) ) { - bestSymbol = s; - } - } - } - if ( bestSymbol != NULL ) { -#if __arm__ - if (bestSymbol->n_desc & N_ARM_THUMB_DEF) - *closestAddr = (void*)((bestSymbol->n_value | 1) + fSlide); - else - *closestAddr = (void*)(bestSymbol->n_value + fSlide); -#else - *closestAddr = (void*)(bestSymbol->n_value + fSlide); -#endif - return &symbolTableStrings[bestSymbol->n_un.n_strx]; - } - return NULL; + return 0; +} + +void ImageLoaderMachOCompressed::dynamicInterpose(const LinkContext& context) +{ + if ( context.verboseInterposing ) + dyld::log("dyld: dynamic interposing %lu tuples onto image: %s\n", context.dynamicInterposeCount, this->getPath()); + + // update already bound references to symbols + eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::dynamicInterposeAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); + eachLazyBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::dynamicInterposeAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); +} + +const char* ImageLoaderMachOCompressed::findClosestSymbol(const void* addr, const void** closestAddr) const +{ + return ImageLoaderMachO::findClosestSymbol((mach_header*)fMachOData, addr, closestAddr); } @@ -1598,9 +1967,8 @@ void ImageLoaderMachOCompressed::updateAlternateLazyPointer(uint8_t* stub, void* void ImageLoaderMachOCompressed::updateOptimizedLazyPointers(const LinkContext& context) { #if __arm__ || __x86_64__ - // find stubs and lazy pointer sections + // find stubs and indirect symbol table const struct macho_section* stubsSection = NULL; - const struct macho_section* lazyPointerSection = NULL; const dysymtab_command* dynSymbolTable = NULL; const macho_header* mh = (macho_header*)fMachOData; const uint32_t cmd_count = mh->ncmds; @@ -1615,8 +1983,6 @@ void ImageLoaderMachOCompressed::updateOptimizedLazyPointers(const LinkContext& const uint8_t type = sect->flags & SECTION_TYPE; if ( type == S_SYMBOL_STUBS ) stubsSection = sect; - else if ( type == S_LAZY_SYMBOL_POINTERS ) - lazyPointerSection = sect; } } else if ( cmd->cmd == LC_DYSYMTAB ) { @@ -1624,37 +1990,81 @@ void ImageLoaderMachOCompressed::updateOptimizedLazyPointers(const LinkContext& } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - - // sanity check if ( dynSymbolTable == NULL ) return; - if ( (stubsSection == NULL) || (lazyPointerSection == NULL) ) - return; - const uint32_t stubsCount = stubsSection->size / stubsSection->reserved2; - const uint32_t lazyPointersCount = lazyPointerSection->size / sizeof(void*); - if ( stubsCount != lazyPointersCount ) + const uint32_t* const indirectTable = (uint32_t*)&fLinkEditBase[dynSymbolTable->indirectsymoff]; + if ( stubsSection == NULL ) return; + const uint32_t stubsSize = stubsSection->reserved2; + const uint32_t stubsCount = (uint32_t)(stubsSection->size / stubsSize); const uint32_t stubsIndirectTableOffset = stubsSection->reserved1; - const uint32_t lazyPointersIndirectTableOffset = lazyPointerSection->reserved1; if ( (stubsIndirectTableOffset+stubsCount) > dynSymbolTable->nindirectsyms ) return; - if ( (lazyPointersIndirectTableOffset+lazyPointersCount) > dynSymbolTable->nindirectsyms ) + uint8_t* const stubsAddr = (uint8_t*)(stubsSection->addr + this->fSlide); + + // for each lazy pointer section + 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* lazyPointerSection=sectionsStart; lazyPointerSection < sectionsEnd; ++lazyPointerSection) { + const uint8_t type = lazyPointerSection->flags & SECTION_TYPE; + if ( type != S_LAZY_SYMBOL_POINTERS ) + continue; + const uint32_t lazyPointersCount = (uint32_t)(lazyPointerSection->size / sizeof(void*)); + const uint32_t lazyPointersIndirectTableOffset = lazyPointerSection->reserved1; + if ( (lazyPointersIndirectTableOffset+lazyPointersCount) > dynSymbolTable->nindirectsyms ) + continue; + void** const lazyPointersAddr = (void**)(lazyPointerSection->addr + this->fSlide); + // for each lazy pointer + for(uint32_t lpIndex=0; lpIndex < lazyPointersCount; ++lpIndex) { + const uint32_t lpSymbolIndex = indirectTable[lazyPointersIndirectTableOffset+lpIndex]; + // find matching stub and validate it uses this lazy pointer + for(uint32_t stubIndex=0; stubIndex < stubsCount; ++stubIndex) { + if ( indirectTable[stubsIndirectTableOffset+stubIndex] == lpSymbolIndex ) { + this->updateAlternateLazyPointer(stubsAddr+stubIndex*stubsSize, &lazyPointersAddr[lpIndex], context); + break; + } + } + } + + } + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + +#endif +} + + +void ImageLoaderMachOCompressed::registerEncryption(const encryption_info_command* encryptCmd, const LinkContext& context) +{ +#if __arm__ || __arm64__ + if ( encryptCmd == NULL ) return; - - // walk stubs and lazy pointers - const uint32_t* const indirectTable = (uint32_t*)&fLinkEditBase[dynSymbolTable->indirectsymoff]; - void** const lazyPointersStartAddr = (void**)(lazyPointerSection->addr + this->fSlide); - uint8_t* const stubsStartAddr = (uint8_t*)(stubsSection->addr + this->fSlide); - uint8_t* stub = stubsStartAddr; - void** lpa = lazyPointersStartAddr; - for(uint32_t i=0; i < stubsCount; ++i, stub += stubsSection->reserved2, ++lpa) { - // sanity check symbol index of stub and lazy pointer match - if ( indirectTable[stubsIndirectTableOffset+i] != indirectTable[lazyPointersIndirectTableOffset+i] ) - continue; - this->updateAlternateLazyPointer(stub, lpa, context); + const mach_header* mh = NULL; + for(unsigned int i=0; i < fSegmentsCount; ++i) { + if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) { + mh = (mach_header*)segActualLoadAddress(i); + break; + } + } + void* start = ((uint8_t*)mh) + encryptCmd->cryptoff; + size_t len = encryptCmd->cryptsize; + uint32_t cputype = mh->cputype; + uint32_t cpusubtype = mh->cpusubtype; + uint32_t cryptid = encryptCmd->cryptid; + if (context.verboseMapping) { + dyld::log(" 0x%08lX->0x%08lX configured for FairPlay decryption\n", (long)start, (long)start+len); + } + int result = mremap_encrypted(start, len, cryptid, cputype, cpusubtype); + if ( result != 0 ) { + dyld::throwf("mremap_encrypted() => %d, errno=%d for %s\n", result, errno, this->getPath()); } - #endif } +