X-Git-Url: https://git.saurik.com/apple/dyld.git/blobdiff_plain/39a8cd101b922f08058746122efff58c14b57605..ba4c3badc27ea8c4637b4e91a49725742a02a53c:/src/ImageLoaderMachOCompressed.cpp?ds=inline diff --git a/src/ImageLoaderMachOCompressed.cpp b/src/ImageLoaderMachOCompressed.cpp index 7d3812b..a908c91 100644 --- a/src/ImageLoaderMachOCompressed.cpp +++ b/src/ImageLoaderMachOCompressed.cpp @@ -23,22 +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 @@ -56,46 +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 >= 64 || slice << bit >> bit != slice) - dyld::throwf("uleb128 too big"); - 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 |= ((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 @@ -108,12 +98,13 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateMainExecutabl image->setSlide(slide); // for PIE record end of program, to know where to start loading dylibs - if ( (mh->flags & MH_PIE) && !context.noPIE ) + if ( slide != 0 ) fgNextPIEDylibAddress = (uintptr_t)image->getEnd(); - - image->setNeverUnload(); + + 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) { @@ -129,9 +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 LinkContext& context) + unsigned int segCount, unsigned int libCount, + 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); @@ -139,9 +133,21 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromFile(cons // record info about file image->setFileInfo(info.st_dev, info.st_ino, info.st_mtime); + // 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(); + // finish construction image->instantiateFinish(context); @@ -154,17 +160,21 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromFile(cons else if ( (installName != NULL) && (strcmp(path, "/usr/lib/libgcc_s.1.dylib") == 0) && (strcmp(installName, "/usr/lib/libSystem.B.dylib") == 0) ) image->setPathUnowned("/usr/lib/libSystem.B.dylib"); #endif - else if ( path[0] != '/' ) { + else if ( (path[0] != '/') || (strstr(path, "../") != NULL) ) { + // rdar://problem/10733082 Fix up @rpath based paths during introspection // rdar://problem/5135363 turn relative paths into absolute paths so gdb, Symbolication can later find them char realPath[MAXPATHLEN]; - if ( realpath(path, realPath) != NULL ) - image->setPath(realPath); + if ( fcntl(fd, F_GETPATH, realPath) == 0 ) + image->setPaths(path, realPath); else image->setPath(path); } else image->setPath(path); + // make sure path is stable before recording in dyld_all_image_infos + image->setMapped(context); + // pre-fetch content of __DATA and __LINKEDIT segment for faster launches // don't do this on prebound images or if prefetching is disabled if ( !context.preFetchDisabled && !image->isPrebindable()) { @@ -183,8 +193,9 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromFile(cons } // create image by using cached mach-o file -ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromCache(const macho_header* mh, const char* path, const struct stat& info, - unsigned int segCount, unsigned int libCount, const LinkContext& context) +ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromCache(const macho_header* mh, const char* path, long slide, + const struct stat& info, unsigned int segCount, + unsigned int libCount, const LinkContext& context) { ImageLoaderMachOCompressed* image = ImageLoaderMachOCompressed::instantiateStart(mh, path, segCount, libCount); try { @@ -194,6 +205,8 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromCache(con // remember this is from shared cache and cannot be unloaded image->fInSharedCache = true; image->setNeverUnload(); + image->setSlide(slide); + image->disableCoverageCheck(); // segments already mapped in cache if ( context.verboseMapping ) { @@ -204,6 +217,7 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromCache(con } image->instantiateFinish(context); + image->setMapped(context); } catch (...) { // ImageLoader::setMapped() can throw an exception to block loading of image @@ -231,11 +245,14 @@ 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); image->instantiateFinish(context); + image->setMapped(context); } catch (...) { // ImageLoader::setMapped() can throw an exception to block loading of image @@ -280,10 +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(); - - // notify state change - this->setMapped(context); + this->parseLoadCmds(context); } uint32_t* ImageLoaderMachOCompressed::segmentCommandOffsets() const @@ -295,8 +309,8 @@ uint32_t* ImageLoaderMachOCompressed::segmentCommandOffsets() const ImageLoader* ImageLoaderMachOCompressed::libImage(unsigned int libIndex) const { const uintptr_t* images = ((uintptr_t*)(((uint8_t*)this) + sizeof(ImageLoaderMachOCompressed) + fSegmentsCount*sizeof(uint32_t))); - // mask off low bit - return (ImageLoader*)(images[libIndex] & (-2)); + // mask off low bits + return (ImageLoader*)(images[libIndex] & (-4)); } bool ImageLoaderMachOCompressed::libReExported(unsigned int libIndex) const @@ -306,13 +320,22 @@ bool ImageLoaderMachOCompressed::libReExported(unsigned int libIndex) const return ((images[libIndex] & 1) != 0); } +bool ImageLoaderMachOCompressed::libIsUpward(unsigned int libIndex) const +{ + const uintptr_t* images = ((uintptr_t*)(((uint8_t*)this) + sizeof(ImageLoaderMachOCompressed) + fSegmentsCount*sizeof(uint32_t))); + // re-export flag is second bit + return ((images[libIndex] & 2) != 0); +} + -void ImageLoaderMachOCompressed::setLibImage(unsigned int libIndex, ImageLoader* image, bool reExported) +void ImageLoaderMachOCompressed::setLibImage(unsigned int libIndex, ImageLoader* image, bool reExported, bool upward) { uintptr_t* images = ((uintptr_t*)(((uint8_t*)this) + sizeof(ImageLoaderMachOCompressed) + fSegmentsCount*sizeof(uint32_t))); uintptr_t value = (uintptr_t)image; if ( reExported ) value |= 1; + if ( upward ) + value |= 2; images[libIndex] = value; } @@ -351,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 @@ -364,7 +387,7 @@ void ImageLoaderMachOCompressed::markLINKEDIT(const LinkContext& context, int ad const char* adstr = "sequential"; if ( advise == MADV_FREE ) adstr = "free"; - dyld::log("%18s %s 0x%0lX -> 0x%0lX\n", "__LINKEDIT", adstr, start, end-1); + dyld::log("%18s %s 0x%0lX -> 0x%0lX for %s\n", "__LINKEDIT", adstr, start, end-1, this->getPath()); } } @@ -372,6 +395,9 @@ void ImageLoaderMachOCompressed::markLINKEDIT(const LinkContext& context, int ad void ImageLoaderMachOCompressed::rebaseAt(const LinkContext& context, uintptr_t addr, uintptr_t slide, uint8_t type) { + if ( context.verboseRebase ) { + dyld::log("dyld: rebase: %s:*0x%08lX += 0x%08lX\n", this->getShortName(), (uintptr_t)addr, slide); + } //dyld::log("0x%08lX type=%d\n", addr, type); uintptr_t* locationToFix = (uintptr_t*)addr; switch (type) { @@ -389,14 +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) { - const uintptr_t slide = this->fSlide; + CRSetCrashLogMessage2(this->getPath()); const uint8_t* const start = fLinkEditBase + fDyldInfo->rebase_off; const uint8_t* const end = &start[fDyldInfo->rebase_size]; const uint8_t* p = start; @@ -405,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; @@ -422,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); @@ -436,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); @@ -446,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); @@ -454,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); @@ -464,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); @@ -472,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)); } } } @@ -481,70 +516,51 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) free((void*)msg); throw newMsg; } + CRSetCrashLogMessage2(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("findExportedSymbolCompressed(%s) in %s\n", symbol, this->getShortName()); + //dyld::log("Compressed::findExportedSymbol(%s) in %s\n", symbol, this->getShortName()); if ( fDyldInfo->export_size == 0 ) return NULL; +#if LOG_BINDINGS + dyld::logBindings("%s: %s\n", this->getShortName(), symbol); +#endif ++ImageLoaderMachO::fgSymbolTrieSearchs; const uint8_t* start = &fLinkEditBase[fDyldInfo->export_off]; const uint8_t* end = &start[fDyldInfo->export_size]; - const uint8_t* p = start; - const char* s = symbol; - do { - const uint8_t terminalSize = *p++; - const uint8_t* children = p + terminalSize; - if ( (*s == '\0') && (terminalSize != 0) ) { - // found match, return pointer to terminal part of node - //dyld::log("findExportedSymbol(%s) in %s found match, returning %p\n", symbol, this->getShortName(), p); - if ( foundIn != NULL ) - *foundIn = (ImageLoader*)this; - return (Symbol*)p; - } - const uint8_t childrenCount = *children++; - const uint8_t* e = children; - const uint8_t* newNode = NULL; - for (uint8_t i=0; i < childrenCount; ++i) { - const char* ss = s; - bool wrongEdge = false; - //dyld::log("findExportedSymbol() looking at edge %s for match to %s\n", e, s); - // scan whole edge to get to next edge - // if edge is longer than target symbol name, don't read past end of symbol name - while ( *e != '\0' ) { - if ( !wrongEdge ) { - if ( *e != *ss++ ) - wrongEdge = true; - } - ++e; - } - if ( wrongEdge ) { - // advance to next child - ++e; - read_uleb128(e, end); + const uint8_t* foundNodeStart = this->trieWalk(start, end, symbol); + if ( foundNodeStart != NULL ) { + const uint8_t* p = foundNodeStart; + 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 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((unsigned int)ordinal-1); + //dyld::log("Compressed::findExportedSymbol(), %s -> %s/%s\n", symbol, reexportedFrom->getShortName(), importedName); + const char* reExportLibPath = libPath((unsigned int)ordinal-1); + return reexportedFrom->findExportedSymbol(importedName, true, reExportLibPath, foundIn); } else { - // the symbol so far matches this edge (child) - // so advance to the child's node - ++e; - uint32_t nodeOffset = read_uleb128(e, end); - newNode = &start[nodeOffset]; - s = ss; - //dyld::log("findExportedSymbol() found matching edge advancing to node 0x%x\n", nodeOffset); - break; + //dyld::throwf("bad mach-o binary, library ordinal (%u) invalid (max %u) for re-exported symbol %s in %s", + // ordinal, libraryCount(), symbol, this->getPath()); } } - if ( newNode != NULL ) - p = newNode; else { - //dyld::log("findExportedSymbol(%s) in %s failed\n", symbol, this->getShortName()); - return NULL; + //dyld::log("findExportedSymbol(%s) in %s found match, returning %p\n", symbol, this->getShortName(), p); + if ( foundIn != NULL ) + *foundIn = (ImageLoader*)this; + // return pointer to terminal part of node + return (Symbol*)foundNodeStart; } - } while ( true ); + } + return NULL; } @@ -556,18 +572,44 @@ bool ImageLoaderMachOCompressed::containsSymbol(const void* addr) const } -uintptr_t ImageLoaderMachOCompressed::exportedSymbolAddress(const Symbol* symbol) const +uintptr_t ImageLoaderMachOCompressed::exportedSymbolAddress(const LinkContext& context, const Symbol* symbol, const ImageLoader* requestor, bool runResolver) const { const uint8_t* exportNode = (uint8_t*)symbol; const uint8_t* exportTrieStart = fLinkEditBase + fDyldInfo->export_off; 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); - if ( (flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) == EXPORT_SYMBOL_FLAGS_KIND_REGULAR ) - return read_uleb128(exportNode, exportTrieEnd) + (uintptr_t)fMachOData; - else - throw "unsupported exported symbol kind"; + //dyld::log("exportedSymbolAddress(): node=%p, nodeOffset=0x%04X in %s\n", symbol, (int)((uint8_t*)symbol - exportTrieStart), this->getShortName()); + 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 + 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)(); + if ( context.verboseBind ) + dyld::log("dyld: resolver at %p returned 0x%08lX\n", resolver, result); + return result; + } + 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=%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=%lu at node=%p", flags, symbol); + return read_uleb128(exportNode, exportTrieEnd); + default: + dyld::throwf("unsupported exported symbol kind. flags=%lu at node=%p", flags, symbol); + } } bool ImageLoaderMachOCompressed::exportedSymbolIsWeakDefintion(const Symbol* symbol) const @@ -577,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 ); } @@ -614,51 +656,157 @@ const char* ImageLoaderMachOCompressed::importedSymbolName(const Symbol* symbol) -uintptr_t ImageLoaderMachOCompressed::resolveFlat(const LinkContext& context, const char* symbolName, bool weak_import, const ImageLoader** foundIn) +uintptr_t ImageLoaderMachOCompressed::resolveFlat(const LinkContext& context, const char* symbolName, bool weak_import, + bool runResolver, const ImageLoader** foundIn) { const Symbol* sym; if ( context.flatExportFinder(symbolName, &sym, foundIn) ) { - if ( (*foundIn != this) && !(*foundIn)->neverUnload() ) - this->addDynamicReference(*foundIn); - return (*foundIn)->getExportedSymbolAddress(sym, context, this); + 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->ImageLoaderMachO::findExportedSymbol(symbolName, false, foundIn); + sym = this->findShallowExportedSymbol(symbolName, foundIn); if ( sym != NULL ) - return (*foundIn)->getExportedSymbolAddress(sym, context, this); + 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(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, 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 ) { - return (*foundIn)->getExportedSymbolAddress(sym, context, this); + 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"); +} + + +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 - throwSymbolNotFound(symbolName, this->getPath(), targetImage->getPath()); + // 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, - LastLookup* last) + uint8_t symboFlags, long libraryOrdinal, const ImageLoader** targetImage, + LastLookup* last, bool runResolver) { *targetImage = NULL; @@ -675,7 +823,10 @@ uintptr_t ImageLoaderMachOCompressed::resolve(const LinkContext& context, const bool weak_import = (symboFlags & BIND_SYMBOL_FLAGS_WEAK_IMPORT); uintptr_t symbolAddress; if ( context.bindFlat || (libraryOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP) ) { - symbolAddress = this->resolveFlat(context, symbolName, weak_import, targetImage); + 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 ) { @@ -685,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 ) { @@ -701,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, 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; @@ -722,23 +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) +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); + 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); } @@ -746,49 +905,293 @@ void ImageLoaderMachOCompressed::throwBadBindingAddress(uintptr_t address, uintp void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLazysBound) { + CRSetCrashLogMessage2(this->getPath()); + // 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 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 this image is in the shared cache, but depends on someting no longer in the shared cache, + #if TEXT_RELOC_SUPPORT + // if there were __TEXT fixups, restore write protection + if ( fTextSegmentBinds ) + this->makeTextSegmentWritable(context, false); + #endif + + // if this image is in the shared cache, but depends on something no longer in the shared cache, // there is no way to reset the lazy pointers, so force bind them now - if ( forceLazysBound || fInSharedCache ) + if ( forceLazysBound || fInSharedCache ) this->doBindJustLazies(context); + + // this image is in cache, but something below it is not. If + // this image has lazy pointer to a resolver function, then + // 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 this->setupLazyPointerHandler(context); - - // tell kernel we are done with chunks of LINKEDIT - if ( !context.preFetchDisabled ) - this->markFreeLINKEDIT(context); + CRSetCrashLogMessage2(NULL); } 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]; @@ -804,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 @@ -816,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; @@ -832,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); - 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); + 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); + 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); + 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); } @@ -885,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]; @@ -934,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, "lazy forced", NULL); + 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: @@ -1010,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]; } } @@ -1025,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; } @@ -1039,76 +1559,52 @@ uintptr_t ImageLoaderMachOCompressed::doBindLazySymbol(uintptr_t* lazyPointer, c } -uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context) + +uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context, + void (*lock)(), void (*unlock)()) { + // race condition with flat-namespace lazy binding + if ( this->usesTwoLevelNameSpace() ) { + // two-level namespace lookup does not require lock because dependents can't be unloaded before this image + } + else { + // acquire dyld global lock + if ( lock != NULL ) + lock(); + } + 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 ) - throw "fast lazy bind offset out of range"; - - 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); - 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); - } - } + 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); + + if ( !this->usesTwoLevelNameSpace() ) { + // release dyld global lock + if ( unlock != NULL ) + unlock(); + } return result; } -void ImageLoaderMachOCompressed::initializeCoalIterator(CoalIterator& it, unsigned int loadOrder) +void ImageLoaderMachOCompressed::initializeCoalIterator(CoalIterator& it, unsigned int loadOrder, unsigned) { it.image = this; it.symbolName = " "; @@ -1138,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; @@ -1166,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); @@ -1191,7 +1702,7 @@ bool ImageLoaderMachOCompressed::incrementCoalIterator(CoalIterator& it) } break; default: - dyld::throwf("bad weak bind opcode %d", *p); + dyld::throwf("bad weak bind opcode '%d' found after processing %d bytes in '%s'", *p, (int)(p-start), this->getPath()); } } /// hmmm, BIND_OPCODE_DONE is missing... @@ -1205,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); @@ -1214,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 ) @@ -1228,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) ) { @@ -1250,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; @@ -1277,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); } @@ -1285,86 +1811,260 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt default: dyld::throwf("bad bind opcode %d in weak binding info", *p); } - } - if ( boundSomething && (targetImage != this) && !targetImage->neverUnload() ) - this->addDynamicReference(targetImage); + } + // C++ weak coalescing cannot be tracked by reference counting. Error on side of never unloading. + if ( boundSomething && (targetImage != this) ) + context.addDynamicReference(this, targetImage); +} + +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 curValue = *fixupLocation; + uintptr_t newValue = interposedAddress(context, curValue, image); + if ( newValue != curValue) { + *fixupLocation = newValue; + } + } + return 0; } +void ImageLoaderMachOCompressed::doInterpose(const LinkContext& context) +{ + if ( context.verboseInterposing ) + dyld::log("dyld: interposing %lu tuples onto image: %s\n", fgInterposingTuples.size(), this->getPath()); + + // update prebound 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::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); + }); +} +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) +{ + 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()); + } + *fixupLocation = (uintptr_t)context.dynamicInterposeArray[i].replacement; + } + } + } + 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 { - // called by dladdr() - // only works with compressed LINKEDIT if classic symbol table is also present - const macho_nlist* symbolTable = NULL; - const char* symbolTableStrings = NULL; + return ImageLoaderMachO::findClosestSymbol((mach_header*)fMachOData, addr, closestAddr); +} + + +#if PREBOUND_IMAGE_SUPPORT +void ImageLoaderMachOCompressed::resetPreboundLazyPointers(const LinkContext& context) +{ + // no way to back off a prebound compress image +} +#endif + + +#if __arm__ || __x86_64__ +void ImageLoaderMachOCompressed::updateAlternateLazyPointer(uint8_t* stub, void** originalLazyPointerAddr, const LinkContext& context) +{ +#if __arm__ + uint32_t* instructions = (uint32_t*)stub; + // sanity check this is a stub we understand + if ( (instructions[0] != 0xe59fc004) || (instructions[1] != 0xe08fc00c) || (instructions[2] != 0xe59cf000) ) + return; + + void** lazyPointerAddr = (void**)(instructions[3] + (stub + 12)); +#endif +#if __x86_64__ + // sanity check this is a stub we understand + if ( (stub[0] != 0xFF) || (stub[1] != 0x25) ) + return; + int32_t ripOffset = *((int32_t*)(&stub[2])); + void** lazyPointerAddr = (void**)(ripOffset + stub + 6); +#endif + + // if stub does not use original lazy pointer (meaning it was optimized by update_dyld_shared_cache) + if ( lazyPointerAddr != originalLazyPointerAddr ) { + // only de-optimization lazy pointers if they are part of shared cache not loaded (because overridden) + const ImageLoader* lazyPointerImage = context.findImageContainingAddress(lazyPointerAddr); + if ( lazyPointerImage != NULL ) + return; + + // copy newly re-bound lazy pointer value to shared lazy pointer + *lazyPointerAddr = *originalLazyPointerAddr; + + if ( context.verboseBind ) + dyld::log("dyld: alter bind: %s: *0x%08lX = 0x%08lX \n", + this->getShortName(), (long)lazyPointerAddr, (long)*originalLazyPointerAddr); + } +} +#endif + + +// overriding shared cache dylibs with resolvers fails +void ImageLoaderMachOCompressed::updateOptimizedLazyPointers(const LinkContext& context) +{ +#if __arm__ || __x86_64__ + // find stubs and indirect symbol table + const struct macho_section* stubsSection = NULL; const dysymtab_command* dynSymbolTable = NULL; - 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; 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; + 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_SYMBOL_STUBS ) + stubsSection = sect; + } + } + else if ( cmd->cmd == LC_DYSYMTAB ) { + dynSymbolTable = (struct dysymtab_command*)cmd; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - // no symbol table => no lookup by address - if ( (symbolTable == NULL) || (dynSymbolTable == NULL) ) - return NULL; + if ( dynSymbolTable == NULL ) + return; + 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; + if ( (stubsIndirectTableOffset+stubsCount) > dynSymbolTable->nindirectsyms ) + return; + 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; + } + } + } - 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; } } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - // 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; - } + +#endif +} + + +void ImageLoaderMachOCompressed::registerEncryption(const encryption_info_command* encryptCmd, const LinkContext& context) +{ +#if __arm__ || __arm64__ + if ( encryptCmd == NULL ) + return; + 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; } } - if ( bestSymbol != NULL ) { - *closestAddr = (void*)(bestSymbol->n_value + fSlide); - return &symbolTableStrings[bestSymbol->n_un.n_strx]; + 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); } - return NULL; + 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 } -#if PREBOUND_IMAGE_SUPPORT -void ImageLoaderMachOCompressed::resetPreboundLazyPointers(const LinkContext& context) -{ - // no way to back off a prebound compress image -} -#endif