Loading...
--- /dev/null
+++ dyld/dyld-1340/common/MachOLayout.cpp
@@ -0,0 +1,2395 @@
+/*
+ * Copyright (c) 2017 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "Array.h"
+#include "Header.h"
+#include "MachOLayout.h"
+#include "MachOFile.h"
+
+#include <TargetConditionals.h>
+#include "Defines.h"
+#if SUPPORT_CLASSIC_RELOCS
+ #include <mach-o/reloc.h>
+ #include <mach-o/x86_64/reloc.h>
+#endif
+#include <mach-o/nlist.h>
+
+// FIXME: We should get this from cctools
+#define DYLD_CACHE_ADJ_V2_FORMAT 0x7F
+
+namespace mach_o
+{
+
+// MARK: --- Layout methods ---
+
+Layout::Layout(MachOFileRef mf, std::span<SegmentLayout> segments, const LinkeditLayout& linkedit)
+ : mf(std::move(mf)), segments(segments), linkedit(linkedit)
+{
+}
+
+uint64_t Layout::textUnslidVMAddr() const
+{
+ for ( const SegmentLayout& segment : this->segments ) {
+ if ( segment.kind == SegmentLayout::Kind::text )
+ return segment.vmAddr;
+ }
+
+ // MachOFile::preferredLoadAddress seems to return 0 if we didn't find __TEXT, so match it
+ return 0;
+}
+
+bool Layout::isSwiftLibrary() const
+{
+ if ( std::optional<uint32_t> flags = this->getObjcInfoFlags(); flags.has_value() ) {
+ uint32_t swiftVersion = ((flags.value() >> 8) & 0xFF);
+ return (swiftVersion != 0);
+ }
+ return false;
+}
+
+std::optional<uint32_t> Layout::getObjcInfoFlags() const
+{
+ struct objc_image_info {
+ int32_t version;
+ uint32_t flags;
+ };
+
+ __block std::optional<uint32_t> flags;
+ ((const Header*)this->mf)->forEachSection(^(const Header::SegmentInfo& segInfo, const Header::SectionInfo& sectInfo, bool& stop) {
+ if ( (sectInfo.sectionName.starts_with("__objc_imageinfo")) && sectInfo.segmentName.starts_with("__DATA") ) {
+ uint64_t segmentOffset = sectInfo.fileOffset - segInfo.fileOffset;
+ objc_image_info* info = (objc_image_info*)(this->segments[sectInfo.segIndex].buffer + segmentOffset);
+ flags = info->flags;
+ stop = true;
+ }
+ });
+ return flags;
+}
+
+bool Layout::hasSection(std::string_view segmentName, std::string_view sectionName) const
+{
+ __block bool result = false;
+ ((const Header*)this->mf)->forEachSection(^(const Header::SectionInfo& sectInfo, bool& stop) {
+ if ( (sectInfo.segmentName == segmentName) && (sectInfo.sectionName == sectionName) ) {
+ result = true;
+ stop = true;
+ }
+ });
+ return result;
+}
+
+namespace {
+ struct LinkEditContentChunk
+ {
+ const char* name;
+ uint32_t alignment;
+ uint32_t fileOffsetStart;
+ uint32_t size;
+
+ // only have a few chunks, so bubble sort is ok. Don't use libc's qsort because it may call malloc
+ static void sort(LinkEditContentChunk array[], unsigned long count)
+ {
+ for (unsigned i=0; i < count-1; ++i) {
+ bool done = true;
+ for (unsigned j=0; j < count-i-1; ++j) {
+ if ( array[j].fileOffsetStart > array[j+1].fileOffsetStart ) {
+ LinkEditContentChunk temp = array[j];
+ array[j] = array[j+1];
+ array[j+1] = temp;
+ done = false;
+ }
+ }
+ if ( done )
+ break;
+ }
+ }
+ };
+} // anonymous namespace
+
+bool Layout::isValidLinkeditLayout(Diagnostics &diag, const char *path) const
+{
+ typedef dyld3::MachOFile::Malformed Malformed;
+
+ const uint32_t ptrSize = this->mf->pointerSize();
+
+ // build vector of all blobs in LINKEDIT
+ LinkEditContentChunk blobs[32];
+ LinkEditContentChunk* bp = blobs;
+ if ( const Linkedit& blob = this->linkedit.rebaseOpcodes; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"rebase opcodes", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+ if ( const Linkedit& blob = this->linkedit.regularBindOpcodes; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"bind opcodes", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+ if ( const Linkedit& blob = this->linkedit.weakBindOpcodes; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"weak bind opcodes", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+ if ( const Linkedit& blob = this->linkedit.lazyBindOpcodes; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"lazy bind opcodes", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+ if ( const Linkedit& blob = this->linkedit.exportsTrie; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"exports trie", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+ if ( const Linkedit& blob = this->linkedit.chainedFixups; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"chained fixups", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+
+#if SUPPORT_CLASSIC_RELOCS
+ if ( const Linkedit& blob = this->linkedit.localRelocs; blob.hasValue() ) {
+ if ( blob.entryCount != 0 ) {
+ uint32_t bufferSize = (uint32_t)(blob.entryCount * sizeof(relocation_info));
+ *bp++ = {"local relocations", ptrSize, blob.fileOffset, bufferSize };
+ }
+ }
+ if ( const Linkedit& blob = this->linkedit.externRelocs; blob.hasValue() ) {
+ if ( blob.entryCount != 0 ) {
+ uint32_t bufferSize = (uint32_t)(blob.entryCount * sizeof(relocation_info));
+ *bp++ = {"external relocations", ptrSize, blob.fileOffset, bufferSize };
+ }
+ }
+#endif
+ if ( const Linkedit& blob = this->linkedit.indirectSymbolTable; blob.hasValue() ) {
+ if ( blob.entryCount != 0 ) {
+ uint32_t bufferSize = (uint32_t)(blob.entryCount * sizeof(uint32_t));
+ *bp++ = {"indirect symbol table", 4, blob.fileOffset, bufferSize };
+ }
+ }
+
+ if ( const Linkedit& blob = this->linkedit.splitSegInfo; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"shared cache info", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+ if ( const Linkedit& blob = this->linkedit.functionStarts; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"function starts", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+ if ( const Linkedit& blob = this->linkedit.dataInCode; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"data in code", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+ if ( const Linkedit& blob = this->linkedit.symbolTable; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"symbol table", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+ if ( const Linkedit& blob = this->linkedit.symbolStrings; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"symbol table strings", 1, blob.fileOffset, blob.bufferSize };
+ }
+ if ( const Linkedit& blob = this->linkedit.codeSignature; blob.hasValue() ) {
+ if ( blob.bufferSize != 0 )
+ *bp++ = {"code signature", ptrSize, blob.fileOffset, blob.bufferSize };
+ }
+
+ // check for bad combinations
+ if ( (this->linkedit.dyldInfoCmd == LC_DYLD_INFO_ONLY) ) {
+ if ( (this->linkedit.localRelocs.entryCount != 0) && this->mf->enforceFormat(Malformed::dyldInfoAndlocalRelocs) ) {
+ diag.error("in '%s' malformed mach-o contains LC_DYLD_INFO_ONLY and local relocations", path);
+ return false;
+ }
+ if ( this->linkedit.externRelocs.entryCount != 0 ) {
+ diag.error("in '%s' malformed mach-o contains LC_DYLD_INFO_ONLY and external relocations", path);
+ return false;
+ }
+ }
+
+ bool checkMissingDyldInfo = true;
+#if BUILDING_DYLDINFO || BUILDING_APP_CACHE_UTIL
+ checkMissingDyldInfo = this->mf->isDyldManaged() && !this->mf->isStaticExecutable();
+#endif
+ if ( (this->linkedit.dyldInfoCmd == 0 ) && !this->linkedit.hasDynSymTab && checkMissingDyldInfo ) {
+ diag.error("in '%s' malformed mach-o misssing LC_DYLD_INFO and LC_DYSYMTAB", path);
+ return false;
+ }
+
+ // FIXME: Remove this hack
+#if BUILDING_APP_CACHE_UTIL
+ if ( this->mf->isFileSet() )
+ return true;
+#endif
+
+ const unsigned long blobCount = bp - blobs;
+ if ( blobCount == 0 ) {
+ diag.error("in '%s' malformed mach-o missing LINKEDIT", path);
+ return false;
+ }
+
+ // Find the linkedit
+ uint32_t linkeditFileOffset = ~0U;
+ uint32_t linkeditFileSize = ~0U;
+ for ( const SegmentLayout& segment : this->segments ) {
+ if ( segment.kind == SegmentLayout::Kind::linkedit ) {
+ linkeditFileOffset = (uint32_t)segment.fileOffset;
+ linkeditFileSize = (uint32_t)segment.fileSize;
+ break;
+ }
+ }
+
+ uint32_t linkeditFileEnd = linkeditFileOffset + linkeditFileSize;
+
+
+ // sort blobs by file-offset and error on overlaps
+ LinkEditContentChunk::sort(blobs, blobCount);
+ uint32_t prevEnd = linkeditFileOffset;
+ const char* prevName = "start of LINKEDIT";
+ for (unsigned long i=0; i < blobCount; ++i) {
+ const LinkEditContentChunk& blob = blobs[i];
+ if ( blob.fileOffsetStart < prevEnd ) {
+ diag.error("in '%s' LINKEDIT overlap of %s and %s", path, prevName, blob.name);
+ return false;
+ }
+ if (dyld3::greaterThanAddOrOverflow(blob.fileOffsetStart, blob.size, linkeditFileEnd)) {
+ diag.error("in '%s' LINKEDIT content '%s' extends beyond end of segment", path, blob.name);
+ return false;
+ }
+ if ( (blob.fileOffsetStart & (blob.alignment-1)) != 0 ) {
+ // <rdar://problem/51115705> relax code sig alignment for pre iOS13
+ Malformed kind = (strcmp(blob.name, "code signature") == 0) ? Malformed::codeSigAlignment : Malformed::linkeditAlignment;
+ if ( this->mf->enforceFormat(kind) )
+ diag.error("in '%s' mis-aligned LINKEDIT content '%s'", path, blob.name);
+ }
+ prevEnd = blob.fileOffsetStart + blob.size;
+ prevName = blob.name;
+ }
+
+ // Check for invalid symbol table sizes
+ if ( this->linkedit.hasSymTab ) {
+ const Linkedit& symbolTable = this->linkedit.symbolTable;
+ if ( symbolTable.entryCount > 0x10000000 ) {
+ diag.error("in '%s' malformed mach-o image: symbol table too large", path);
+ return false;
+ }
+ if ( this->linkedit.hasDynSymTab ) {
+ // validate indirect symbol table
+ const Linkedit& localSymbolTable = this->linkedit.localSymbolTable;
+ const Linkedit& globalSymbolTable = this->linkedit.globalSymbolTable;
+ const Linkedit& undefSymbolTable = this->linkedit.undefSymbolTable;
+ const Linkedit& indirectSymbolTable = this->linkedit.indirectSymbolTable;
+ if ( indirectSymbolTable.entryCount != 0 ) {
+ if ( indirectSymbolTable.entryCount > 0x10000000 ) {
+ diag.error("in '%s' malformed mach-o image: indirect symbol table too large", path);
+ return false;
+ }
+ }
+ if ( (localSymbolTable.entryCount > symbolTable.entryCount) || (localSymbolTable.entryIndex > symbolTable.entryCount) ) {
+ diag.error("in '%s' malformed mach-o image: indirect symbol table local symbol count exceeds total symbols", path);
+ return false;
+ }
+ if ( (localSymbolTable.entryIndex + localSymbolTable.entryCount) < localSymbolTable.entryIndex ) {
+ diag.error("in '%s' malformed mach-o image: indirect symbol table local symbol count wraps", path);
+ return false;
+ }
+ if ( (globalSymbolTable.entryCount > symbolTable.entryCount)
+ || (globalSymbolTable.entryIndex > symbolTable.entryCount) ) {
+ diag.error("in '%s' malformed mach-o image: indirect symbol table extern symbol count exceeds total symbols", path);
+ return false;
+ }
+ if ( (globalSymbolTable.entryIndex + globalSymbolTable.entryCount) < globalSymbolTable.entryIndex ) {
+ diag.error("in '%s' malformed mach-o image: indirect symbol table extern symbol count wraps", path);
+ return false;
+ }
+ if ( (undefSymbolTable.entryCount > symbolTable.entryCount) || (undefSymbolTable.entryIndex > symbolTable.entryCount) ) {
+ diag.error("in '%s' malformed mach-o image: indirect symbol table undefined symbol count exceeds total symbols", path);
+ return false;
+ }
+ if ( (undefSymbolTable.entryIndex + undefSymbolTable.entryCount) < undefSymbolTable.entryIndex ) {
+ diag.error("in '%s' malformed mach-o image: indirect symbol table undefined symbol count wraps", path);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Layout::findExportedSymbol(Diagnostics& diag, const char* symbolName, bool weakImport,
+ FoundSymbol& foundInfo) const
+{
+ if ( this->linkedit.exportsTrie.hasValue() ) {
+ // FIXME: Move all this to the ExportTrie class
+ const uint8_t* trieStart = this->linkedit.exportsTrie.buffer;
+ const uint8_t* trieEnd = trieStart + this->linkedit.exportsTrie.bufferSize;
+ const uint8_t* node = dyld3::MachOFile::trieWalk(diag, trieStart, trieEnd, symbolName);
+ if ( node == nullptr ) {
+ // symbol not exported from this image. Seach any re-exported dylibs
+ // FIXME: Implement this
+#if 0
+ __block unsigned depIndex = 0;
+ __block bool foundInReExportedDylib = false;
+ forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
+ if ( isReExport && findDependent ) {
+ if ( const MachOLoaded* depMH = findDependent(this, depIndex) ) {
+ if ( depMH->findExportedSymbol(diag, symbolName, weakImport, foundInfo, findDependent) ) {
+ stop = true;
+ foundInReExportedDylib = true;
+ }
+ }
+ }
+ ++depIndex;
+ });
+ return foundInReExportedDylib;
+#endif
+ return false;
+ }
+ const uint8_t* p = node;
+ const uint64_t flags = dyld3::MachOFile::read_uleb128(diag, p, trieEnd);
+ if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) {
+ // FIXME: Implement this
+#if 0
+ if ( !findDependent )
+ return false;
+ // re-export from another dylib, lookup there
+ const uint64_t ordinal = dyld3::MachOFile::read_uleb128(diag, p, trieEnd);
+ const char* importedName = (char*)p;
+ if ( importedName[0] == '\0' )
+ importedName = symbolName;
+ if ( (ordinal == 0) || (ordinal > dependentDylibCount()) ) {
+ diag.error("re-export ordinal %lld out of range for %s", ordinal, symbolName);
+ return false;
+ }
+ uint32_t depIndex = (uint32_t)(ordinal-1);
+ if ( const MachOLoaded* depMH = findDependent(this, depIndex) ) {
+ return depMH->findExportedSymbol(diag, importedName, weakImport, foundInfo, findDependent);
+ }
+ else if (weakImport) {
+ return false;
+ }
+ else {
+ diag.error("dependent dylib %lld not found for re-exported symbol %s", ordinal, symbolName);
+ return false;
+ }
+#endif
+ return false;
+ }
+ foundInfo.kind = FoundSymbol::Kind::headerOffset;
+ foundInfo.isThreadLocal = false;
+ foundInfo.isWeakDef = false;
+ foundInfo.foundInDylib = this->mf;
+ foundInfo.value = dyld3::MachOFile::read_uleb128(diag, p, trieEnd);
+ foundInfo.resolverFuncOffset = 0;
+ foundInfo.foundSymbolName = symbolName;
+ if ( diag.hasError() )
+ return false;
+ switch ( flags & EXPORT_SYMBOL_FLAGS_KIND_MASK ) {
+ case EXPORT_SYMBOL_FLAGS_KIND_REGULAR:
+ if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) {
+ foundInfo.kind = FoundSymbol::Kind::headerOffset;
+ foundInfo.resolverFuncOffset = (uint32_t)dyld3::MachOFile::read_uleb128(diag, p, trieEnd);
+ }
+ else {
+ foundInfo.kind = FoundSymbol::Kind::headerOffset;
+ }
+ if ( flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION )
+ foundInfo.isWeakDef = true;
+ break;
+ case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL:
+ foundInfo.isThreadLocal = true;
+ break;
+ case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE:
+ foundInfo.kind = FoundSymbol::Kind::absolute;
+ break;
+ default:
+ diag.error("unsupported exported symbol kind. flags=%llu at node offset=0x%0lX", flags, (long)(node-trieStart));
+ return false;
+ }
+ return true;
+ }
+ else {
+ // this is an old binary (before macOS 10.6), scan the symbol table
+ foundInfo.foundInDylib.reset();
+
+ SymbolTable symbolTable(*this);
+ symbolTable.forEachGlobalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type,
+ uint8_t n_sect, uint16_t n_desc, bool& stop) {
+ if ( strcmp(aSymbolName, symbolName) == 0 ) {
+ foundInfo.kind = FoundSymbol::Kind::headerOffset;
+ foundInfo.isThreadLocal = false;
+ foundInfo.foundInDylib = this->mf;
+ foundInfo.value = n_value - this->textUnslidVMAddr();
+ foundInfo.resolverFuncOffset = 0;
+ foundInfo.foundSymbolName = symbolName;
+ stop = true;
+ }
+ });
+
+ // FIXME: Implement this
+#if 0
+ if ( !foundInfo.foundInDylib.has_value() ) {
+ // symbol not exported from this image. Search any re-exported dylibs
+ __block unsigned depIndex = 0;
+ forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
+ if ( isReExport && findDependent ) {
+ if ( const MachOLoaded* depMH = findDependent(this, depIndex) ) {
+ if ( depMH->findExportedSymbol(diag, symbolName, weakImport, foundInfo, findDependent) ) {
+ stop = true;
+ }
+ }
+ }
+ ++depIndex;
+ });
+ }
+#endif
+ return foundInfo.foundInDylib.has_value();
+ }
+}
+
+// MARK: --- Fixups methods ---
+
+Fixups::Fixups(const Layout& layout)
+ : layout(layout)
+{
+}
+
+void Fixups::forEachBindTarget(Diagnostics& diag, bool allowLazyBinds, intptr_t slide,
+ void (^handler)(const BindTargetInfo& info, bool& stop),
+ void (^overrideHandler)(const BindTargetInfo& info, bool& stop)) const
+{
+ if ( this->layout.mf->isPreload() )
+ return;
+ if ( this->layout.mf->hasChainedFixups() )
+ this->forEachBindTarget_ChainedFixups(diag, handler);
+ else if ( this->layout.mf->hasOpcodeFixups() )
+ this->forEachBindTarget_Opcodes(diag, allowLazyBinds, handler, overrideHandler);
+#if SUPPORT_CLASSIC_RELOCS
+ else
+ this->forEachBindTarget_Relocations(diag, slide, handler);
+#endif
+}
+
+void Fixups::forEachBindTarget_ChainedFixups(Diagnostics& diag, void (^handler)(const BindTargetInfo& info, bool& stop)) const
+{
+ __block unsigned targetIndex = 0;
+ this->forEachChainedFixupTarget(diag, ^(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop) {
+ BindTargetInfo info;
+ info.targetIndex = targetIndex;
+ info.libOrdinal = libOrdinal;
+ info.symbolName = symbolName;
+ info.addend = addend;
+ info.weakImport = weakImport;
+ info.lazyBind = false;
+ handler(info, stop);
+ ++targetIndex;
+ });
+
+ // The C++ spec says main executables can define non-weak functions which override weak-defs in dylibs
+ // This happens automatically for anything bound at launch, but the dyld cache is pre-bound so we need
+ // to patch any binds that are overridden by this non-weak in the main executable.
+ if ( diag.noError() && this->layout.mf->isMainExecutable() && this->layout.mf->hasWeakDefs() ) {
+ dyld3::MachOFile::forEachTreatAsWeakDef(^(const char* symbolName) {
+ BindTargetInfo info;
+ info.targetIndex = targetIndex;
+ info.libOrdinal = BIND_SPECIAL_DYLIB_WEAK_LOOKUP;
+ info.symbolName = symbolName;
+ info.addend = 0;
+ info.weakImport = false;
+ info.lazyBind = false;
+ bool stop = false;
+ handler(info, stop);
+ ++targetIndex;
+ });
+ }
+}
+
+void Fixups::parseOrgArm64eChainedFixups(Diagnostics& diag,
+ void (^targetCount)(uint32_t totalTargets, bool& stop),
+ void (^addTarget)(bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal, uint8_t type, const char* symbolName, uint64_t addend, bool weakImport, bool& stop),
+ void (^addChainStart)(uint32_t segmentIndex, bool segIndexSet, uint64_t segmentOffset, uint16_t format, bool& stop)) const
+{
+ bool stop = false;
+
+ const uint32_t dylibCount = this->layout.mf->dependentDylibCount();
+
+ if ( this->layout.linkedit.regularBindOpcodes.hasValue() ) {
+ // process bind opcodes
+ const uint8_t* p = this->layout.linkedit.regularBindOpcodes.buffer;
+ const uint8_t* end = p + this->layout.linkedit.regularBindOpcodes.bufferSize;
+ uint8_t type = 0;
+ uint64_t segmentOffset = 0;
+ uint8_t segmentIndex = 0;
+ const char* symbolName = NULL;
+ int libraryOrdinal = 0;
+ bool segIndexSet = false;
+ bool libraryOrdinalSet = false;
+ uint64_t targetTableCount;
+ uint64_t addend = 0;
+ bool weakImport = false;
+ while ( !stop && diag.noError() && (p < end) ) {
+ uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
+ uint8_t opcode = *p & BIND_OPCODE_MASK;
+ ++p;
+ switch (opcode) {
+ case BIND_OPCODE_DONE:
+ stop = true;
+ break;
+ case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
+ libraryOrdinal = immediate;
+ libraryOrdinalSet = true;
+ break;
+ case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
+ libraryOrdinal = (int)dyld3::MachOFile::read_uleb128(diag, p, end);
+ libraryOrdinalSet = true;
+ 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;
+ }
+ libraryOrdinalSet = true;
+ break;
+ case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
+ weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 );
+ symbolName = (char*)p;
+ while (*p != '\0')
+ ++p;
+ ++p;
+ break;
+ case BIND_OPCODE_SET_TYPE_IMM:
+ type = immediate;
+ break;
+ case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+ segmentIndex = immediate;
+ segmentOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+ segIndexSet = true;
+ break;
+ case BIND_OPCODE_SET_ADDEND_SLEB:
+ addend = dyld3::MachOFile::read_sleb128(diag, p, end);
+ break;
+ case BIND_OPCODE_DO_BIND:
+ if ( addTarget )
+ addTarget(libraryOrdinalSet, dylibCount, libraryOrdinal, type, symbolName, addend, weakImport, stop);
+ break;
+ case BIND_OPCODE_THREADED:
+ switch (immediate) {
+ case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB:
+ targetTableCount = dyld3::MachOFile::read_uleb128(diag, p, end);
+ if ( targetTableCount > 65535 ) {
+ diag.error("BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB size too large");
+ stop = true;
+ }
+ else {
+ if ( targetCount )
+ targetCount((uint32_t)targetTableCount, stop);
+ }
+ break;
+ case BIND_SUBOPCODE_THREADED_APPLY:
+ if ( addChainStart )
+ addChainStart(segmentIndex, segIndexSet, segmentOffset, DYLD_CHAINED_PTR_ARM64E, stop);
+ break;
+ default:
+ diag.error("bad BIND_OPCODE_THREADED sub-opcode 0x%02X", immediate);
+ }
+ break;
+ default:
+ diag.error("bad bind opcode 0x%02X", immediate);
+ }
+ }
+ if ( diag.hasError() )
+ return;
+ }
+}
+
+void Fixups::forEachChainedFixupTarget(Diagnostics& diag, void (^callback)(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop)) const
+{
+ if ( this->layout.linkedit.regularBindOpcodes.hasValue() ) {
+ parseOrgArm64eChainedFixups(diag, nullptr, ^(bool libraryOrdinalSet, uint32_t dylibCount,
+ int libOrdinal, uint8_t type, const char* symbolName, uint64_t fixAddend, bool weakImport, bool& stopChain) {
+ callback(libOrdinal, symbolName, fixAddend, weakImport, stopChain);
+ }, nullptr);
+ }
+ else if ( this->layout.linkedit.chainedFixups.hasValue() ) {
+ const dyld_chained_fixups_header* header = (dyld_chained_fixups_header*)this->layout.linkedit.chainedFixups.buffer;
+ dyld3::MachOFile::forEachChainedFixupTarget(diag, header, this->layout.linkedit.chainedFixups.cmd, callback);
+ }
+}
+
+#if (BUILDING_DYLD || BUILDING_LIBDYLD) && !__arm64e__
+ #define SUPPORT_OLD_ARM64E_FORMAT 0
+#else
+ #define SUPPORT_OLD_ARM64E_FORMAT 1
+#endif
+
+// find dyld_chained_starts_in_image* in image
+// if old arm64e binary, synthesize dyld_chained_starts_in_image*
+void Fixups::withThreadedRebaseAsChainStarts(Diagnostics& diag, void (^callback)(const dyld_chained_fixups_header* header, uint64_t fixupsSize)) const
+{
+#if SUPPORT_OLD_ARM64E_FORMAT
+ // don't want this code in non-arm64e dyld because it causes a stack protector which dereferences a GOT pointer before GOT is set up
+ // old arm64e binary, create a dyld_chained_starts_in_image for caller
+ uint64_t baseAddress = ((const Header*)this->layout.mf)->preferredLoadAddress();
+ uint64_t imagePageCount = this->layout.mf->mappedSize()/0x4000;
+ size_t bufferSize = this->layout.linkedit.regularBindOpcodes.bufferSize + (size_t)imagePageCount*sizeof(uint16_t) + 512;
+ BLOCK_ACCCESSIBLE_ARRAY(uint8_t, buffer, bufferSize);
+ uint8_t* bufferEnd = &buffer[bufferSize];
+ dyld_chained_fixups_header* header = (dyld_chained_fixups_header*)buffer;
+ header->fixups_version = 0;
+ header->starts_offset = sizeof(dyld_chained_fixups_header);
+ header->imports_offset = 0;
+ header->symbols_offset = 0;
+ header->imports_count = 0;
+ header->imports_format = 0;
+ header->symbols_format = 0;
+ dyld_chained_starts_in_image* starts = (dyld_chained_starts_in_image*)(dyld_chained_starts_in_image*)((uint8_t*)header + header->starts_offset);
+ starts->seg_count = (uint32_t)this->layout.segments.size();
+ for (uint32_t i=0; i < starts->seg_count; ++i)
+ starts->seg_info_offset[i] = 0;
+ __block uint8_t curSegIndex = 0;
+ __block dyld_chained_starts_in_segment* curSeg = (dyld_chained_starts_in_segment*)(&(starts->seg_info_offset[starts->seg_count]));
+ parseOrgArm64eChainedFixups(diag, nullptr, nullptr, ^(uint32_t segmentIndex, bool segIndexSet, uint64_t segmentOffset, uint16_t format, bool& stop) {
+ uint32_t pageIndex = (uint32_t)(segmentOffset/0x1000);
+ if ( segmentIndex != curSegIndex ) {
+ if ( curSegIndex == 0 ) {
+ starts->seg_info_offset[segmentIndex] = (uint32_t)((uint8_t*)curSeg - (uint8_t*)starts);
+ }
+ else {
+ starts->seg_info_offset[segmentIndex] = (uint32_t)((uint8_t*)(&curSeg->page_start[curSeg->page_count]) - (uint8_t*)starts);
+ curSeg = (dyld_chained_starts_in_segment*)((uint8_t*)starts+starts->seg_info_offset[segmentIndex]);
+ assert((uint8_t*)curSeg < bufferEnd);
+ }
+ curSeg->page_count = 0;
+ curSegIndex = segmentIndex;
+ }
+ while ( curSeg->page_count != pageIndex ) {
+ assert((uint8_t*)(&curSeg->page_start[curSeg->page_count]) < bufferEnd);
+ curSeg->page_start[curSeg->page_count] = 0xFFFF;
+ curSeg->page_count++;
+ }
+ curSeg->size = (uint32_t)((uint8_t*)(&curSeg->page_start[pageIndex]) - (uint8_t*)curSeg);
+ curSeg->page_size = 0x1000; // old arm64e encoding used 4KB pages
+ curSeg->pointer_format = DYLD_CHAINED_PTR_ARM64E;
+ curSeg->segment_offset = this->layout.segments[segmentIndex].vmAddr - baseAddress;
+ curSeg->max_valid_pointer = 0;
+ curSeg->page_count = pageIndex+1;
+ assert((uint8_t*)(&curSeg->page_start[pageIndex]) < bufferEnd);
+ curSeg->page_start[pageIndex] = segmentOffset & 0xFFF;
+ //fprintf(stderr, "segment_offset=0x%llX, vmAddr=0x%llX\n", curSeg->segment_offset, segments[segmentIndex].vmAddr );
+ //printf("segIndex=%d, segOffset=0x%08llX, page_start[%d]=0x%04X, page_start[%d]=0x%04X\n",
+ // segmentIndex, segmentOffset, pageIndex, curSeg->page_start[pageIndex], pageIndex-1, pageIndex ? curSeg->page_start[pageIndex-1] : 0);
+ });
+ callback(header, (uint64_t)bufferSize);
+#endif
+}
+
+const dyld_chained_fixups_header* Fixups::chainedFixupsHeader() const {
+ if ( this->layout.linkedit.chainedFixups.hasValue() ) {
+ // find dyld_chained_starts_in_image from dyld_chained_fixups_header
+ return (dyld_chained_fixups_header*)this->layout.linkedit.chainedFixups.buffer;
+ }
+ return nullptr;
+}
+
+// find dyld_chained_starts_in_image* in image
+// if old arm64e binary, synthesize dyld_chained_starts_in_image*
+void Fixups::withChainStarts(Diagnostics& diag, void (^callback)(const dyld_chained_starts_in_image*)) const
+{
+ if ( const dyld_chained_fixups_header* chainHeader = this->chainedFixupsHeader() ) {
+ // find dyld_chained_starts_in_image from dyld_chained_fixups_header
+ callback((dyld_chained_starts_in_image*)((uint8_t*)chainHeader + chainHeader->starts_offset));
+ }
+#if SUPPORT_OLD_ARM64E_FORMAT
+ // don't want this code in non-arm64e dyld because it causes a stack protector which dereferences a GOT pointer before GOT is set up
+ else if ( this->layout.linkedit.regularBindOpcodes.hasValue() && (this->layout.mf->cputype == CPU_TYPE_ARM64) && (this->layout.mf->maskedCpuSubtype() == CPU_SUBTYPE_ARM64E) ) {
+ // old arm64e binary, create a dyld_chained_starts_in_image for caller
+ this->withThreadedRebaseAsChainStarts(diag, ^(const dyld_chained_fixups_header* header, uint64_t fixupsSize) {
+ callback((dyld_chained_starts_in_image*)((uint8_t*)header + header->starts_offset));
+ });
+ }
+#endif
+ else {
+ diag.error("image does not use chained fixups");
+ }
+}
+
+void Fixups::forEachFixupInAllChains(Diagnostics& diag, const dyld_chained_starts_in_image* starts, bool notifyNonPointers,
+ void (^handler)(ChainedFixupPointerOnDisk* fixupLocation, uint64_t fixupSegmentOffset,
+ const dyld_chained_starts_in_segment* segInfo, bool& stop)) const
+{
+
+ bool stopped = false;
+ for (uint32_t segIndex=0; segIndex < starts->seg_count && !stopped; ++segIndex) {
+ if ( starts->seg_info_offset[segIndex] == 0 )
+ continue;
+ const dyld_chained_starts_in_segment* segInfo = (dyld_chained_starts_in_segment*)((uint8_t*)starts + starts->seg_info_offset[segIndex]);
+ auto adaptor = ^(ChainedFixupPointerOnDisk* fixupLocation, uint64_t fixupSegmentOffset, bool& stop) {
+ handler(fixupLocation, fixupSegmentOffset, segInfo, stop);
+ };
+ forEachFixupInSegmentChains(diag, segInfo, segIndex, notifyNonPointers, adaptor);
+ }
+}
+
+void Fixups::forEachFixupInSegmentChains(Diagnostics& diag, const dyld_chained_starts_in_segment* segInfo, uint32_t segIndex, bool notifyNonPointers,
+ void (^handler)(ChainedFixupPointerOnDisk* fixupLocation, uint64_t fixupSegmentOffset, bool& stop)) const
+{
+ const uint8_t* segmentBuffer = this->layout.segments[segIndex].buffer;
+ auto adaptor = ^(ChainedFixupPointerOnDisk* fixupLocation, bool& stop) {
+ uint64_t fixupSegmentOffset = (uint64_t)fixupLocation - (uint64_t)segmentBuffer;
+ handler(fixupLocation, fixupSegmentOffset, stop);
+ };
+ bool stopped = false;
+ for (uint32_t pageIndex=0; pageIndex < segInfo->page_count && !stopped; ++pageIndex) {
+ uint16_t offsetInPage = segInfo->page_start[pageIndex];
+ if ( offsetInPage == DYLD_CHAINED_PTR_START_NONE )
+ continue;
+ const uint8_t* pageContentStart = segmentBuffer + (pageIndex * segInfo->page_size);
+ if ( offsetInPage & DYLD_CHAINED_PTR_START_MULTI ) {
+ // 32-bit chains which may need multiple starts per page
+ uint32_t overflowIndex = offsetInPage & ~DYLD_CHAINED_PTR_START_MULTI;
+ bool chainEnd = false;
+ while (!stopped && !chainEnd) {
+ chainEnd = (segInfo->page_start[overflowIndex] & DYLD_CHAINED_PTR_START_LAST);
+ offsetInPage = (segInfo->page_start[overflowIndex] & ~DYLD_CHAINED_PTR_START_LAST);
+ ChainedFixupPointerOnDisk* chain = (ChainedFixupPointerOnDisk*)(pageContentStart+offsetInPage);
+
+ stopped = dyld3::MachOFile::walkChain(diag, chain, segInfo->pointer_format, notifyNonPointers, segInfo->max_valid_pointer, adaptor);
+ ++overflowIndex;
+ }
+ }
+ else {
+ // one chain per page
+ ChainedFixupPointerOnDisk* chain = (ChainedFixupPointerOnDisk*)(pageContentStart+offsetInPage);
+ stopped = dyld3::MachOFile::walkChain(diag, chain, segInfo->pointer_format, notifyNonPointers, segInfo->max_valid_pointer, adaptor);
+ }
+ }
+}
+
+void Fixups::forEachFixupChainSegment(Diagnostics& diag, const dyld_chained_starts_in_image* starts,
+ void (^handler)(const dyld_chained_starts_in_segment* segInfo, uint32_t segIndex, bool& stop))
+{
+ bool stopped = false;
+ for (uint32_t segIndex=0; segIndex < starts->seg_count && !stopped; ++segIndex) {
+ if ( starts->seg_info_offset[segIndex] == 0 )
+ continue;
+ const dyld_chained_starts_in_segment* segInfo = (dyld_chained_starts_in_segment*)((uint8_t*)starts + starts->seg_info_offset[segIndex]);
+ handler(segInfo, segIndex, stopped);
+ }
+}
+
+uint16_t Fixups::chainedPointerFormat() const
+{
+ if ( const dyld_chained_fixups_header* chainHeader = this->chainedFixupsHeader() ) {
+ // get pointer format from chain info struct in LINKEDIT
+ return dyld3::MachOFile::chainedPointerFormat(chainHeader);
+ }
+ assert(this->layout.mf->cputype == CPU_TYPE_ARM64
+ && (this->layout.mf->maskedCpuSubtype() == CPU_SUBTYPE_ARM64E)
+ && "chainedPointerFormat() called on non-chained binary");
+ return DYLD_CHAINED_PTR_ARM64E;
+}
+
+// walk through all binds, unifying weak, lazy, and regular binds
+void Fixups::forEachBindUnified_Opcodes(Diagnostics& diag, bool allowLazyBinds,
+ void (^handler)(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& targetInfo, bool& stop),
+ void (^overrideHandler)(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& targetInfo, bool& stop)) const
+{
+ {
+ __block unsigned targetIndex = 0;
+ __block BindTargetInfo targetInfo;
+ BindDetailedHandler binder = ^(const char* opcodeName,
+ bool segIndexSet, bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal,
+ uint32_t pointerSize, uint32_t segmentIndex, uint64_t segmentOffset,
+ uint8_t type, const char* symbolName, bool weakImport, bool lazyBind,
+ uint64_t addend, bool targetOrAddendChanged, bool& stop) {
+ uint64_t bindVmOffset = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+ uint64_t runtimeOffset = bindVmOffset - this->layout.textUnslidVMAddr();
+ if ( targetOrAddendChanged ) {
+ targetInfo.targetIndex = targetIndex++;
+ targetInfo.libOrdinal = libOrdinal;
+ targetInfo.symbolName = symbolName;
+ targetInfo.addend = addend;
+ targetInfo.weakImport = weakImport;
+ targetInfo.lazyBind = lazyBind && allowLazyBinds;
+ }
+ handler(runtimeOffset, segmentIndex, targetInfo, stop);
+ };
+ bool stopped = this->forEachBind_OpcodesRegular(diag, binder);
+ if ( stopped )
+ return;
+ stopped = this->forEachBind_OpcodesLazy(diag, binder);
+ if ( stopped )
+ return;
+ }
+
+ // Opcode based weak-binds effectively override other binds/rebases. Process them last
+ // To match dyld2, they are allowed to fail to find a target, in which case the normal rebase/bind will
+ // not be overridden.
+ {
+ __block unsigned weakTargetIndex = 0;
+ __block BindTargetInfo weakTargetInfo;
+ BindDetailedHandler weakBinder = ^(const char* opcodeName,
+ bool segIndexSet, bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal,
+ uint32_t pointerSize, uint32_t segmentIndex, uint64_t segmentOffset,
+ uint8_t type, const char* symbolName, bool weakImport, bool lazyBind,
+ uint64_t addend, bool targetOrAddendChanged, bool& stop) {
+
+ uint64_t bindVmOffset = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+ uint64_t runtimeOffset = bindVmOffset - this->layout.textUnslidVMAddr();
+ if ( (symbolName != weakTargetInfo.symbolName) || (strcmp(symbolName, weakTargetInfo.symbolName) != 0) || (weakTargetInfo.addend != addend) ) {
+ weakTargetInfo.targetIndex = weakTargetIndex++;
+ weakTargetInfo.libOrdinal = BIND_SPECIAL_DYLIB_WEAK_LOOKUP;
+ weakTargetInfo.symbolName = symbolName;
+ weakTargetInfo.addend = addend;
+ weakTargetInfo.weakImport = false;
+ weakTargetInfo.lazyBind = false;
+ }
+ overrideHandler(runtimeOffset, segmentIndex, weakTargetInfo, stop);
+ };
+ auto strongHandler = ^(const char* strongName) { };
+ this->forEachBind_OpcodesWeak(diag, weakBinder, strongHandler);
+ }
+}
+
+void Fixups::forEachBindTarget_Opcodes(Diagnostics& diag, bool allowLazyBinds,
+ void (^handler)(const BindTargetInfo& info, bool& stop),
+ void (^overrideHandler)(const BindTargetInfo& info, bool& stop)) const
+{
+ __block unsigned lastTargetIndex = -1;
+ __block unsigned lastWeakBindTargetIndex = -1;
+ this->forEachBindUnified_Opcodes(diag, allowLazyBinds,
+ ^(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& targetInfo, bool& stop) {
+ // Regular/lazy binds
+ if ( lastTargetIndex != targetInfo.targetIndex) {
+ handler(targetInfo, stop);
+ lastTargetIndex = targetInfo.targetIndex;
+ }
+ }, ^(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& targetInfo, bool& stop) {
+ // Weak binds
+ if ( lastWeakBindTargetIndex != targetInfo.targetIndex) {
+ overrideHandler(targetInfo, stop);
+ lastWeakBindTargetIndex = targetInfo.targetIndex;
+ }
+ });
+}
+
+bool Fixups::forEachBind_OpcodesLazy(Diagnostics& diag, BindDetailedHandler handler) const
+{
+ if ( !this->layout.linkedit.lazyBindOpcodes.hasValue() )
+ return false;
+
+ uint32_t lazyDoneCount = 0;
+ uint32_t lazyBindCount = 0;
+ const uint32_t ptrSize = this->layout.mf->pointerSize();
+ bool stop = false;
+ const uint32_t dylibCount = this->layout.mf->dependentDylibCount();
+ const uint8_t* p = this->layout.linkedit.lazyBindOpcodes.buffer;
+ const uint8_t* end = p + this->layout.linkedit.lazyBindOpcodes.bufferSize;
+ uint8_t type = BIND_TYPE_POINTER;
+ uint64_t segmentOffset = 0;
+ uint8_t segmentIndex = 0;
+ const char* symbolName = NULL;
+ int libraryOrdinal = 0;
+ bool segIndexSet = false;
+ bool libraryOrdinalSet = false;
+ int64_t addend = 0;
+ bool weakImport = false;
+ while ( !stop && diag.noError() && (p < end) ) {
+ uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
+ uint8_t opcode = *p & BIND_OPCODE_MASK;
+ ++p;
+ switch (opcode) {
+ case BIND_OPCODE_DONE:
+ // this opcode marks the end of each lazy pointer binding
+ ++lazyDoneCount;
+ break;
+ case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
+ libraryOrdinal = immediate;
+ libraryOrdinalSet = true;
+ break;
+ case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
+ libraryOrdinal = (int)dyld3::MachOFile::read_uleb128(diag, p, end);
+ libraryOrdinalSet = true;
+ 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;
+ }
+ libraryOrdinalSet = true;
+ break;
+ case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
+ weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 );
+ symbolName = (char*)p;
+ while (*p != '\0')
+ ++p;
+ ++p;
+ break;
+ case BIND_OPCODE_SET_ADDEND_SLEB:
+ addend = dyld3::MachOFile::read_sleb128(diag, p, end);
+ break;
+ case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+ segmentIndex = immediate;
+ segmentOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+ segIndexSet = true;
+ break;
+ case BIND_OPCODE_DO_BIND:
+ handler("BIND_OPCODE_DO_BIND", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+ ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, true, addend, true, stop);
+ segmentOffset += ptrSize;
+ ++lazyBindCount;
+ break;
+ case BIND_OPCODE_SET_TYPE_IMM:
+ 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:
+ diag.error("bad lazy bind opcode 0x%02X", opcode);
+ break;
+ }
+ }
+ if ( lazyDoneCount > lazyBindCount+7 ) {
+ // diag.error("lazy bind opcodes missing binds");
+ }
+ return stop;
+}
+
+
+
+bool Fixups::forEachBind_OpcodesWeak(Diagnostics& diag, BindDetailedHandler handler, void (^strongHandler)(const char* symbolName)) const
+{
+ if ( !this->layout.linkedit.weakBindOpcodes.hasValue() )
+ return false;
+
+ const uint32_t ptrSize = this->layout.mf->pointerSize();
+ bool stop = false;
+ const uint32_t dylibCount = this->layout.mf->dependentDylibCount();
+ const uint8_t* p = this->layout.linkedit.weakBindOpcodes.buffer;
+ const uint8_t* end = p + this->layout.linkedit.weakBindOpcodes.bufferSize;
+ uint8_t type = BIND_TYPE_POINTER;
+ uint64_t segmentOffset = 0;
+ uint8_t segmentIndex = 0;
+ const char* symbolName = NULL;
+ int libraryOrdinal = BIND_SPECIAL_DYLIB_WEAK_LOOKUP;
+ bool segIndexSet = false;
+ bool libraryOrdinalSet = true;
+ int64_t addend = 0;
+ bool weakImport = false;
+ bool targetOrAddendChanged = true;
+ bool done = false;
+ uint64_t count;
+ uint64_t skip;
+ while ( !stop && diag.noError() && (p < end) && !done ) {
+ 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:
+ case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
+ case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
+ diag.error("unexpected dylib ordinal in weak_bind");
+ break;
+ case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
+ weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 );
+ symbolName = (char*)p;
+ while (*p != '\0')
+ ++p;
+ ++p;
+ if ( immediate & BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION ) {
+ strongHandler(symbolName);
+ }
+ targetOrAddendChanged = true;
+ break;
+ case BIND_OPCODE_SET_TYPE_IMM:
+ type = immediate;
+ break;
+ case BIND_OPCODE_SET_ADDEND_SLEB:
+ addend = dyld3::MachOFile::read_sleb128(diag, p, end);
+ targetOrAddendChanged = true;
+ break;
+ case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+ segmentIndex = immediate;
+ segmentOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+ segIndexSet = true;
+ break;
+ case BIND_OPCODE_ADD_ADDR_ULEB:
+ segmentOffset += dyld3::MachOFile::read_uleb128(diag, p, end);
+ break;
+ case BIND_OPCODE_DO_BIND:
+ handler("BIND_OPCODE_DO_BIND", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+ ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+ segmentOffset += ptrSize;
+ targetOrAddendChanged = false;
+ break;
+ case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
+ handler("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+ ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+ segmentOffset += dyld3::MachOFile::read_uleb128(diag, p, end) + ptrSize;
+ targetOrAddendChanged = false;
+ break;
+ case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
+ handler("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+ ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+ segmentOffset += immediate*ptrSize + ptrSize;
+ targetOrAddendChanged = false;
+ break;
+ case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
+ count = dyld3::MachOFile::read_uleb128(diag, p, end);
+ skip = dyld3::MachOFile::read_uleb128(diag, p, end);
+ for (uint32_t i=0; i < count; ++i) {
+ handler("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+ ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+ segmentOffset += skip + ptrSize;
+ targetOrAddendChanged = false;
+ if ( stop )
+ break;
+ }
+ break;
+ default:
+ diag.error("bad bind opcode 0x%02X", *p);
+ }
+ }
+ return stop;
+}
+
+bool Fixups::forEachBind_OpcodesRegular(Diagnostics& diag, BindDetailedHandler handler) const
+{
+ if ( !this->layout.linkedit.regularBindOpcodes.hasValue() )
+ return false;
+
+ const uint32_t ptrSize = this->layout.mf->pointerSize();
+ bool stop = false;
+ const uint32_t dylibCount = this->layout.mf->dependentDylibCount();
+ const uint8_t* p = this->layout.linkedit.regularBindOpcodes.buffer;
+ const uint8_t* end = p + this->layout.linkedit.regularBindOpcodes.bufferSize;
+ uint8_t type = 0;
+ uint64_t segmentOffset = 0;
+ uint8_t segmentIndex = 0;
+ const char* symbolName = NULL;
+ int libraryOrdinal = 0;
+ bool segIndexSet = false;
+ bool libraryOrdinalSet = false;
+ bool targetOrAddendChanged = false;
+ bool done = false;
+ int64_t addend = 0;
+ uint64_t count;
+ uint64_t skip;
+ bool weakImport = false;
+ while ( !stop && diag.noError() && (p < end) && !done ) {
+ 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;
+ libraryOrdinalSet = true;
+ break;
+ case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
+ libraryOrdinal = (int)dyld3::MachOFile::read_uleb128(diag, p, end);
+ libraryOrdinalSet = true;
+ 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;
+ }
+ libraryOrdinalSet = true;
+ break;
+ case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
+ weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 );
+ symbolName = (char*)p;
+ while (*p != '\0')
+ ++p;
+ ++p;
+ targetOrAddendChanged = true;
+ break;
+ case BIND_OPCODE_SET_TYPE_IMM:
+ type = immediate;
+ break;
+ case BIND_OPCODE_SET_ADDEND_SLEB:
+ addend = dyld3::MachOFile::read_sleb128(diag, p, end);
+ targetOrAddendChanged = true;
+ break;
+ case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+ segmentIndex = immediate;
+ segmentOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+ segIndexSet = true;
+ break;
+ case BIND_OPCODE_ADD_ADDR_ULEB:
+ segmentOffset += dyld3::MachOFile::read_uleb128(diag, p, end);
+ break;
+ case BIND_OPCODE_DO_BIND:
+ handler("BIND_OPCODE_DO_BIND", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+ ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+ segmentOffset += ptrSize;
+ targetOrAddendChanged = false;
+ break;
+ case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
+ handler("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+ ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+ segmentOffset += dyld3::MachOFile::read_uleb128(diag, p, end) + ptrSize;
+ targetOrAddendChanged = false;
+ break;
+ case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
+ handler("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+ ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+ segmentOffset += immediate*ptrSize + ptrSize;
+ targetOrAddendChanged = false;
+ break;
+ case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
+ count = dyld3::MachOFile::read_uleb128(diag, p, end);
+ skip = dyld3::MachOFile::read_uleb128(diag, p, end);
+ for (uint32_t i=0; i < count; ++i) {
+ handler("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB", segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal,
+ ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+ segmentOffset += skip + ptrSize;
+ targetOrAddendChanged = false;
+ if ( stop )
+ break;
+ }
+ break;
+ default:
+ diag.error("bad bind opcode 0x%02X", *p);
+ }
+ }
+ return stop;
+}
+
+void Fixups::forEachBindLocation_Opcodes(Diagnostics& diag, void (^handler)(uint64_t runtimeOffset, uint32_t segmentIndex, unsigned targetIndex, bool& stop),
+ void (^overrideHandler)(uint64_t runtimeOffset, uint32_t segmentIndex, unsigned overrideBindTargetIndex, bool& stop)) const
+{
+ this->forEachBindUnified_Opcodes(diag, false,
+ ^(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& targetInfo, bool& stop) {
+ handler(runtimeOffset, segmentIndex, targetInfo.targetIndex, stop);
+ }, ^(uint64_t runtimeOffset, uint32_t segmentIndex, const BindTargetInfo& weakTargetInfo, bool& stop) {
+ overrideHandler(runtimeOffset, segmentIndex, weakTargetInfo.targetIndex, stop);
+ });
+}
+
+void Fixups::forEachBindLocation_Relocations(Diagnostics& diag, void (^handler)(uint64_t runtimeOffset, unsigned targetIndex,
+ bool& stop)) const
+{
+ // As we don't need the private externs workaround, we also don't need a slide here
+ bool supportPrivateExternsWorkaround = false;
+ intptr_t unusedSlide = 0;
+
+ __block int targetIndex = -1;
+ this->forEachBind_Relocations(diag, supportPrivateExternsWorkaround, unusedSlide,
+ ^(const char* opcodeName,
+ bool segIndexSet, bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal,
+ uint32_t pointerSize, uint32_t segmentIndex, uint64_t segmentOffset,
+ uint8_t type, const char* symbolName, bool weakImport, bool lazyBind,
+ uint64_t addend, bool targetOrAddendChanged, bool& stop) {
+ if ( targetOrAddendChanged )
+ ++targetIndex;
+ uint64_t bindVMAddr = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+ uint64_t runtimeOffset = bindVMAddr - this->layout.textUnslidVMAddr();
+ handler(runtimeOffset, targetIndex, stop);
+ });
+}
+
+// old binary, walk external relocations and indirect symbol table
+void Fixups::forEachBindTarget_Relocations(Diagnostics& diag, intptr_t slide,
+ void (^handler)(const BindTargetInfo& info, bool& stop)) const
+{
+ __block unsigned targetIndex = 0;
+ this->forEachBind_Relocations(diag, true, slide,
+ ^(const char* opcodeName,
+ bool segIndexSet, bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal,
+ uint32_t pointerSize, uint32_t segmentIndex, uint64_t segmentOffset,
+ uint8_t type, const char* symbolName, bool weakImport, bool lazyBind,
+ uint64_t addend, bool targetOrAddendChanged, bool& stop) {
+ if ( targetOrAddendChanged ) {
+ BindTargetInfo info;
+ info.targetIndex = targetIndex;
+ info.libOrdinal = libOrdinal;
+ info.symbolName = symbolName;
+ info.addend = addend;
+ info.weakImport = weakImport;
+ info.lazyBind = lazyBind;
+ handler(info, stop);
+ ++targetIndex;
+ }
+ });
+}
+
+bool Fixups::forEachRebaseLocation_Opcodes(Diagnostics& diag, void (^handler)(uint64_t runtimeOffset, uint32_t segmentIndex, bool& stop)) const
+{
+ return this->forEachRebase_Opcodes(diag,
+ ^(const char* opcodeName, bool segIndexSet, uint32_t pointerSize,
+ uint8_t segmentIndex, uint64_t segmentOffset, Rebase kind,
+ bool& stop) {
+ uint64_t rebaseVMAddr = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+ uint64_t runtimeOffset = rebaseVMAddr - this->layout.textUnslidVMAddr();
+ handler(runtimeOffset, segmentIndex, stop);
+ });
+}
+
+void Fixups::forEachRebase(Diagnostics& diag, void (^callback)(uint64_t runtimeOffset, uint64_t rebasedValue, bool& stop)) const
+{
+ if ( !this->layout.linkedit.rebaseOpcodes.hasValue() )
+ return;
+
+ const bool is64 = this->layout.mf->is64();
+ this->forEachRebase_Opcodes(diag, ^(const char* opcodeName, bool segIndexSet, uint32_t pointerSize,
+ uint8_t segmentIndex, uint64_t segmentOffset, Rebase kind,
+ bool& stop) {
+ uint64_t rebaseVMAddr = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+ uint64_t runtimeOffset = rebaseVMAddr - this->layout.textUnslidVMAddr();
+ const uint8_t* fixupLoc = this->layout.segments[segmentIndex].buffer + segmentOffset;
+ uint64_t targetVMAddr = 0;
+ if ( is64 ) {
+ targetVMAddr = *(uint64_t*)fixupLoc;
+ } else {
+ targetVMAddr = *(uint32_t*)fixupLoc;
+ }
+ callback(runtimeOffset, targetVMAddr, stop);
+ });
+}
+
+bool Fixups::forEachRebase_Opcodes(Diagnostics& diag, RebaseDetailHandler handler) const
+{
+ const bool is64 = this->layout.mf->is64();
+ const Rebase pointerRebaseKind = is64 ? Rebase::pointer64 : Rebase::pointer32;
+ assert(this->layout.linkedit.rebaseOpcodes.hasValue());
+
+ const uint8_t* const start = this->layout.linkedit.rebaseOpcodes.buffer;
+ const uint8_t* const end = start + this->layout.linkedit.rebaseOpcodes.bufferSize;
+ const uint8_t* p = start;
+ const uint32_t ptrSize = this->layout.mf->pointerSize();
+ Rebase kind = Rebase::unknown;
+ int segIndex = 0;
+ uint64_t segOffset = 0;
+ uint64_t count;
+ uint64_t skip;
+ bool segIndexSet = false;
+ bool stop = false;
+ while ( !stop && diag.noError() && (p < end) ) {
+ uint8_t immediate = *p & REBASE_IMMEDIATE_MASK;
+ uint8_t opcode = *p & REBASE_OPCODE_MASK;
+ ++p;
+ switch (opcode) {
+ case REBASE_OPCODE_DONE:
+ // Allow some padding, in case rebases were somehow aligned to 16-bytes in size
+ if ( (end - p) > 15 )
+ diag.error("rebase opcodes terminated early at offset %d of %d", (int)(p-start), (int)(end-start));
+ stop = true;
+ break;
+ case REBASE_OPCODE_SET_TYPE_IMM:
+ switch ( immediate ) {
+ case REBASE_TYPE_POINTER:
+ kind = pointerRebaseKind;
+ break;
+ case REBASE_TYPE_TEXT_ABSOLUTE32:
+ kind = Rebase::textAbsolute32;
+ break;
+ case REBASE_TYPE_TEXT_PCREL32:
+ kind = Rebase::textPCrel32;
+ break;
+ default:
+ kind = Rebase::unknown;
+ break;
+ }
+ break;
+ case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+ segIndex = immediate;
+ segOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+ segIndexSet = true;
+ break;
+ case REBASE_OPCODE_ADD_ADDR_ULEB:
+ segOffset += dyld3::MachOFile::read_uleb128(diag, p, end);
+ break;
+ case REBASE_OPCODE_ADD_ADDR_IMM_SCALED:
+ segOffset += immediate*ptrSize;
+ break;
+ case REBASE_OPCODE_DO_REBASE_IMM_TIMES:
+ for (int i=0; i < immediate; ++i) {
+ handler("REBASE_OPCODE_DO_REBASE_IMM_TIMES", segIndexSet, ptrSize, segIndex, segOffset, kind, stop);
+ segOffset += ptrSize;
+ if ( stop )
+ break;
+ }
+ break;
+ case REBASE_OPCODE_DO_REBASE_ULEB_TIMES:
+ count = dyld3::MachOFile::read_uleb128(diag, p, end);
+ for (uint32_t i=0; i < count; ++i) {
+ handler("REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", segIndexSet, ptrSize, segIndex, segOffset, kind, stop);
+ segOffset += ptrSize;
+ if ( stop )
+ break;
+ }
+ break;
+ case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB:
+ handler("REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", segIndexSet, ptrSize, segIndex, segOffset, kind, stop);
+ segOffset += dyld3::MachOFile::read_uleb128(diag, p, end) + ptrSize;
+ break;
+ case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB:
+ count = dyld3::MachOFile::read_uleb128(diag, p, end);
+ if ( diag.hasError() )
+ break;
+ skip = dyld3::MachOFile::read_uleb128(diag, p, end);
+ for (uint32_t i=0; i < count; ++i) {
+ handler("REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB", segIndexSet, ptrSize, segIndex, segOffset, kind, stop);
+ segOffset += skip + ptrSize;
+ if ( stop )
+ break;
+ }
+ break;
+ default:
+ diag.error("unknown rebase opcode 0x%02X", opcode);
+ }
+ }
+ return stop;
+}
+
+bool Fixups::forEachRebaseLocation_Relocations(Diagnostics& diag,
+ void (^handler)(uint64_t runtimeOffset, uint32_t segmentIndex,
+ bool& stop)) const
+{
+ return this->forEachRebase_Relocations(diag, ^(const char* opcodeName, bool segIndexSet,
+ uint32_t pointerSize, uint8_t segmentIndex,
+ uint64_t segmentOffset, Rebase kind, bool& stop) {
+ uint64_t rebaseVmOffset = this->layout.segments[segmentIndex].vmAddr + segmentOffset;
+ uint64_t runtimeOffset = rebaseVmOffset - this->layout.textUnslidVMAddr();
+ handler(runtimeOffset, segmentIndex, stop);
+ });
+}
+
+#if SUPPORT_CLASSIC_RELOCS
+// relocs are normally sorted, we don't want to use qsort because it may switch to mergesort which uses malloc
+static void sortRelocations(dyld3::Array<relocation_info>& relocs)
+{
+ // The kernel linker has malloc, and old-style relocations are extremely common. So use qsort
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+ ::qsort(&relocs[0], (size_t)relocs.count(), sizeof(relocation_info),
+ [](const void* l, const void* r) -> int {
+ if ( ((relocation_info*)l)->r_address < ((relocation_info*)r)->r_address )
+ return -1;
+ else
+ return 1;
+ });
+#else
+ uint64_t count = relocs.count();
+ for (uint64_t i=0; i < count-1; ++i) {
+ bool done = true;
+ for (uint64_t j=0; j < count-i-1; ++j) {
+ if ( relocs[j].r_address > relocs[j+1].r_address ) {
+ relocation_info temp = relocs[j];
+ relocs[j] = relocs[j+1];
+ relocs[j+1] = temp;
+ done = false;
+ }
+ }
+ if ( done )
+ break;
+ }
+#endif
+}
+
+bool Fixups::forEachRebase_Relocations(Diagnostics& diag, RebaseDetailHandler handler) const
+{
+ // old binary, walk relocations
+ bool is64Bit = this->layout.mf->is64();
+ const uint8_t ptrSize = this->layout.mf->pointerSize();
+ const uint64_t relocsStartAddress = localRelocBaseAddress();
+ const relocation_info* const relocsStart = (const relocation_info*)this->layout.linkedit.localRelocs.buffer;
+ const relocation_info* const relocsEnd = &relocsStart[this->layout.linkedit.localRelocs.entryCount];
+ const uint8_t relocSize = (is64Bit ? 3 : 2);
+ bool stop = false;
+ STACK_ALLOC_OVERFLOW_SAFE_ARRAY(relocation_info, relocs, 2048);
+ for (const relocation_info* reloc=relocsStart; (reloc < relocsEnd) && !stop; ++reloc) {
+ if ( reloc->r_length != relocSize ) {
+ bool shouldEmitError = true;
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+ if ( this->layout.mf->usesClassicRelocationsInKernelCollection() && (reloc->r_length == 2) && (relocSize == 3) )
+ shouldEmitError = false;
+#endif
+ if ( shouldEmitError ) {
+ diag.error("local relocation has wrong r_length");
+ break;
+ }
+ }
+ if ( reloc->r_type != 0 ) { // 0 == X86_64_RELOC_UNSIGNED == GENERIC_RELOC_VANILLA == ARM64_RELOC_UNSIGNED
+ diag.error("local relocation has wrong r_type");
+ break;
+ }
+ relocs.push_back(*reloc);
+ }
+ if ( !relocs.empty() ) {
+ sortRelocations(relocs);
+ for (relocation_info reloc : relocs) {
+ uint32_t addrOff = reloc.r_address;
+ uint32_t segIndex = 0;
+ uint64_t segOffset = 0;
+ uint64_t addr = 0;
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+ // xnu for x86_64 has __HIB mapped before __DATA, so offsets appear to be
+ // negative
+ if ( this->layout.mf->isStaticExecutable() || this->layout.mf->isFileSet() ) {
+ addr = relocsStartAddress + (int32_t)addrOff;
+ } else {
+ addr = relocsStartAddress + addrOff;
+ }
+#else
+ addr = relocsStartAddress + addrOff;
+#endif
+ if ( segIndexAndOffsetForAddress(addr, segIndex, segOffset) ) {
+ Rebase kind = (reloc.r_length == 2) ? Rebase::pointer32 : Rebase::pointer64;
+ if ( this->layout.mf->cputype == CPU_TYPE_I386 ) {
+ if ( this->layout.segments[segIndex].executable() )
+ kind = Rebase::textAbsolute32;
+ }
+ handler("local relocation", true, ptrSize, segIndex, segOffset, kind, stop);
+ }
+ else {
+ diag.error("local relocation has out of range r_address");
+ break;
+ }
+ }
+ }
+ // then process indirect symbols
+ const Rebase pointerRebaseKind = is64Bit ? Rebase::pointer64 : Rebase::pointer32;
+ intptr_t unusedSlide = 0;
+ forEachIndirectPointer(diag, false, unusedSlide,
+ ^(uint64_t address, bool bind, int bindLibOrdinal,
+ const char* bindSymbolName, bool bindWeakImport, bool bindLazy,
+ bool selfModifyingStub, bool& indStop) {
+ if ( bind )
+ return;
+ uint32_t segIndex = 0;
+ uint64_t segOffset = 0;
+ if ( segIndexAndOffsetForAddress(address, segIndex, segOffset) ) {
+ handler("local relocation", true, ptrSize, segIndex, segOffset, pointerRebaseKind, indStop);
+ }
+ else {
+ diag.error("local relocation has out of range r_address");
+ indStop = true;
+ }
+ });
+
+ return stop;
+}
+
+
+bool Fixups::forEachBind_Relocations(Diagnostics& diag, bool supportPrivateExternsWorkaround,
+ intptr_t slide, BindDetailedHandler handler) const
+{
+ // Firmare binaries won't have a dynSymTab
+ if ( !this->layout.linkedit.externRelocs.hasValue() )
+ return false;
+
+ const uint64_t relocsStartAddress = externalRelocBaseAddress();
+ const relocation_info* const relocsStart = (const relocation_info*)this->layout.linkedit.externRelocs.buffer;
+ const relocation_info* const relocsEnd = &relocsStart[this->layout.linkedit.externRelocs.entryCount];
+ bool is64Bit = this->layout.mf->is64() ;
+ const uint32_t ptrSize = this->layout.mf->pointerSize();
+ const uint32_t dylibCount = this->layout.mf->dependentDylibCount();
+ const uint8_t relocSize = (is64Bit ? 3 : 2);
+ const void* symbolTable = this->layout.linkedit.symbolTable.buffer;
+ const struct nlist_64* symbols64 = (nlist_64*)symbolTable;
+ const struct nlist* symbols32 = (struct nlist*)symbolTable;
+ const char* stringPool = (char*)this->layout.linkedit.symbolStrings.buffer;
+ uint32_t symCount = this->layout.linkedit.symbolTable.entryCount;
+ uint32_t poolSize = this->layout.linkedit.symbolStrings.bufferSize;
+ uint32_t lastSymIndx = -1;
+ uint64_t lastAddend = 0;
+ bool stop = false;
+ for (const relocation_info* reloc=relocsStart; (reloc < relocsEnd) && !stop; ++reloc) {
+ bool isBranch = false;
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+ if ( this->layout.mf->isKextBundle() ) {
+ // kext's may have other kinds of relocations, eg, branch relocs. Skip them
+ if ( this->layout.mf->isArch("x86_64") || this->layout.mf->isArch("x86_64h") ) {
+ if ( reloc->r_type == X86_64_RELOC_BRANCH ) {
+ if ( reloc->r_length != 2 ) {
+ diag.error("external relocation has wrong r_length");
+ break;
+ }
+ if ( reloc->r_pcrel != true ) {
+ diag.error("external relocation should be pcrel");
+ break;
+ }
+ isBranch = true;
+ }
+ }
+ }
+#endif
+ if ( !isBranch ) {
+ if ( reloc->r_length != relocSize ) {
+ diag.error("external relocation has wrong r_length");
+ break;
+ }
+ if ( reloc->r_type != 0 ) { // 0 == X86_64_RELOC_UNSIGNED == GENERIC_RELOC_VANILLA == ARM64_RELOC_UNSIGNED
+ diag.error("external relocation has wrong r_type");
+ break;
+ }
+ }
+ uint32_t segIndex = 0;
+ uint64_t segOffset = 0;
+ if ( segIndexAndOffsetForAddress(relocsStartAddress+reloc->r_address, segIndex, segOffset) ) {
+ uint32_t symbolIndex = reloc->r_symbolnum;
+ if ( symbolIndex > symCount ) {
+ diag.error("external relocation has out of range r_symbolnum");
+ break;
+ }
+ else {
+ uint32_t strOffset = is64Bit ? symbols64[symbolIndex].n_un.n_strx : symbols32[symbolIndex].n_un.n_strx;
+ uint16_t n_desc = is64Bit ? symbols64[symbolIndex].n_desc : symbols32[symbolIndex].n_desc;
+ uint8_t n_type = is64Bit ? symbols64[symbolIndex].n_type : symbols32[symbolIndex].n_type;
+ uint32_t libOrdinal = libOrdinalFromDesc(n_desc);
+ if ( strOffset >= poolSize ) {
+ diag.error("external relocation has r_symbolnum=%d which has out of range n_strx", symbolIndex);
+ break;
+ }
+ else {
+ const char* symbolName = stringPool + strOffset;
+ bool weakImport = (n_desc & N_WEAK_REF);
+ const uint8_t* content = this->layout.segments[segIndex].buffer + segOffset;
+ uint64_t addend = (reloc->r_length == 3) ? *((uint64_t*)content) : *((uint32_t*)content);
+ // Handle defined weak def symbols which need to get a special ordinal
+ if ( ((n_type & N_TYPE) == N_SECT) && ((n_type & N_EXT) != 0) && ((n_desc & N_WEAK_DEF) != 0) )
+ libOrdinal = BIND_SPECIAL_DYLIB_WEAK_LOOKUP;
+ uint8_t type = isBranch ? BIND_TYPE_TEXT_PCREL32 : BIND_TYPE_POINTER;
+ bool targetOrAddendChanged = (lastSymIndx != symbolIndex) || (lastAddend != addend);
+ handler("external relocation", true, true, dylibCount, libOrdinal,
+ ptrSize, segIndex, segOffset, type, symbolName, weakImport, false, addend, targetOrAddendChanged, stop);
+ lastSymIndx = symbolIndex;
+ lastAddend = addend;
+ }
+ }
+ }
+ else {
+ diag.error("local relocation has out of range r_address");
+ break;
+ }
+ }
+ // then process indirect symbols
+ forEachIndirectPointer(diag, supportPrivateExternsWorkaround, slide,
+ ^(uint64_t address, bool bind, int bindLibOrdinal,
+ const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& indStop) {
+ if ( !bind )
+ return;
+ uint32_t segIndex = 0;
+ uint64_t segOffset = 0;
+ if ( segIndexAndOffsetForAddress(address, segIndex, segOffset) ) {
+ handler("indirect symbol", true, true, dylibCount, bindLibOrdinal,
+ ptrSize, segIndex, segOffset, BIND_TYPE_POINTER, bindSymbolName, bindWeakImport, bindLazy, 0, true, indStop);
+ }
+ else {
+ diag.error("indirect symbol has out of range address");
+ indStop = true;
+ }
+ });
+
+ return false;
+}
+#endif // SUPPORT_CLASSIC_RELOCS
+
+void Fixups::forEachIndirectPointer(Diagnostics& diag, bool supportPrivateExternsWorkaround, intptr_t slide,
+ void (^handler)(uint64_t pointerAddress, bool bind, int bindLibOrdinal, const char* bindSymbolName,
+ bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& stop)) const
+{
+ // find lazy and non-lazy pointer sections
+ const bool is64Bit = this->layout.mf->is64();
+ const uint32_t* const indirectSymbolTable = (uint32_t*)this->layout.linkedit.indirectSymbolTable.buffer;
+ const uint32_t indirectSymbolTableCount = this->layout.linkedit.indirectSymbolTable.entryCount;
+ const uint32_t ptrSize = this->layout.mf->pointerSize();
+ const void* symbolTable = this->layout.linkedit.symbolTable.buffer;
+ const struct nlist_64* symbols64 = (nlist_64*)symbolTable;
+ const struct nlist* symbols32 = (struct nlist*)symbolTable;
+ const char* stringPool = (char*)this->layout.linkedit.symbolStrings.buffer;
+ uint32_t symCount = this->layout.linkedit.symbolTable.entryCount;
+ uint32_t poolSize = this->layout.linkedit.symbolStrings.bufferSize;
+ __block bool stop = false;
+
+ // Old kexts put S_LAZY_SYMBOL_POINTERS on the __got section, even if they didn't have indirect symbols to prcess.
+ // In that case, skip the loop as there shouldn't be anything to process
+ if ( (indirectSymbolTableCount == 0) && this->layout.mf->isKextBundle() )
+ return;
+
+ ((const Header*)this->layout.mf)->forEachSection(^(const Header::SectionInfo& sectInfo, bool& sectionStop) {
+ uint8_t sectionType = (sectInfo.flags & SECTION_TYPE);
+ bool selfModifyingStub = (sectionType == S_SYMBOL_STUBS) && (sectInfo.flags & S_ATTR_SELF_MODIFYING_CODE) && (sectInfo.reserved2 == 5) && (this->layout.mf->cputype == CPU_TYPE_I386);
+ if ( (sectionType != S_LAZY_SYMBOL_POINTERS) && (sectionType != S_NON_LAZY_SYMBOL_POINTERS) && !selfModifyingStub )
+ return;
+ if ( (sectInfo.flags & S_ATTR_SELF_MODIFYING_CODE) && !selfModifyingStub ) {
+ diag.error("S_ATTR_SELF_MODIFYING_CODE section type only valid in old i386 binaries");
+ sectionStop = true;
+ return;
+ }
+ uint32_t elementSize = selfModifyingStub ? sectInfo.reserved2 : ptrSize;
+ uint32_t elementCount = (uint32_t)(sectInfo.size/elementSize);
+ if ( dyld3::greaterThanAddOrOverflow(sectInfo.reserved1, elementCount, indirectSymbolTableCount) ) {
+ diag.error("section %.*s overflows indirect symbol table", (int)sectInfo.sectionName.size(), sectInfo.sectionName.data());
+ sectionStop = true;
+ return;
+ }
+
+ for (uint32_t i=0; (i < elementCount) && !stop; ++i) {
+ uint32_t symNum = indirectSymbolTable[sectInfo.reserved1 + i];
+ if ( symNum == INDIRECT_SYMBOL_ABS )
+ continue;
+ if ( symNum == INDIRECT_SYMBOL_LOCAL ) {
+ handler(sectInfo.address+i*elementSize, false, 0, "", false, false, false, stop);
+ continue;
+ }
+ if ( symNum > symCount ) {
+ diag.error("indirect symbol[%d] = %d which is invalid symbol index", sectInfo.reserved1 + i, symNum);
+ sectionStop = true;
+ return;
+ }
+ uint16_t n_desc = is64Bit ? symbols64[symNum].n_desc : symbols32[symNum].n_desc;
+ uint8_t n_type = is64Bit ? symbols64[symNum].n_type : symbols32[symNum].n_type;
+ uint32_t libOrdinal = libOrdinalFromDesc(n_desc);
+ uint32_t strOffset = is64Bit ? symbols64[symNum].n_un.n_strx : symbols32[symNum].n_un.n_strx;
+ if ( strOffset > poolSize ) {
+ diag.error("symbol[%d] string offset out of range", sectInfo.reserved1 + i);
+ sectionStop = true;
+ return;
+ }
+ const char* symbolName = stringPool + strOffset;
+ bool weakImport = (n_desc & N_WEAK_REF);
+ bool lazy = (sectionType == S_LAZY_SYMBOL_POINTERS);
+#if SUPPORT_PRIVATE_EXTERNS_WORKAROUND
+ if ( lazy && ((n_type & N_PEXT) != 0) ) {
+ // don't know why the static linker did not eliminate the internal reference to a private extern definition
+ // As this is private extern, we know the symbol lookup will fail. We also know that this is a lazy-bind, and so
+ // there is a corresponding rebase. The rebase will be run later, and will slide whatever value is in here.
+ // So lets change the value in this slot, and let the existing rebase slide it for us
+ // Note we only want to change the value in memory once, before rebases are applied. We don't want to accidentally
+ // change it again later.
+ if ( supportPrivateExternsWorkaround ) {
+ uintptr_t* ptr = (uintptr_t*)((uint8_t*)(sectInfo.address+i*elementSize) + slide);
+ uint64_t n_value = is64Bit ? symbols64[symNum].n_value : symbols32[symNum].n_value;
+ *ptr = (uintptr_t)n_value;
+ }
+ continue;
+ }
+#endif
+ // Handle defined weak def symbols which need to get a special ordinal
+ if ( ((n_type & N_TYPE) == N_SECT) && ((n_type & N_EXT) != 0) && ((n_desc & N_WEAK_DEF) != 0) )
+ libOrdinal = BIND_SPECIAL_DYLIB_WEAK_LOOKUP;
+ handler(sectInfo.address+i*elementSize, true, libOrdinal, symbolName, weakImport, lazy, selfModifyingStub, stop);
+ }
+ sectionStop = stop;
+ });
+}
+
+uint64_t Fixups::localRelocBaseAddress() const
+{
+ if ( this->layout.mf->isArch("x86_64") || this->layout.mf->isArch("x86_64h") ) {
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+ if ( this->layout.mf->isKextBundle() ) {
+ // for kext bundles the reloc base address starts at __TEXT segment
+ return this->layout.segments[0].vmAddr;
+ }
+#endif
+ // for all other kinds, the x86_64 reloc base address starts at first writable segment (usually __DATA)
+ for ( const SegmentLayout& segment : this->layout.segments ) {
+ if ( segment.writable() )
+ return segment.vmAddr;
+ }
+ }
+ return this->layout.segments[0].vmAddr;
+}
+
+uint64_t Fixups::externalRelocBaseAddress() const
+{
+ // Dyld caches are too large for a raw r_address, so everything is an offset from the base address
+ if ( this->layout.mf->inDyldCache() ) {
+ return ((const Header*)this->layout.mf)->preferredLoadAddress();
+ }
+
+#if BUILDING_APP_CACHE_UTIL || BUILDING_DYLDINFO
+ if ( this->layout.mf->isKextBundle() ) {
+ // for kext bundles the reloc base address starts at __TEXT segment
+ return ((const Header*)this->layout.mf)->preferredLoadAddress();
+ }
+#endif
+
+ if ( this->layout.mf->isArch("x86_64") || this->layout.mf->isArch("x86_64h") ) {
+ // for x86_64 reloc base address starts at first writable segment (usually __DATA)
+ for ( const SegmentLayout& segment : this->layout.segments ) {
+ if ( segment.writable() )
+ return segment.vmAddr;
+ }
+ }
+ // For everyone else we start at 0
+ return 0;
+}
+
+bool Fixups::segIndexAndOffsetForAddress(uint64_t addr, uint32_t& segIndex, uint64_t& segOffset) const
+{
+ for (uint32_t i=0; i < this->layout.segments.size(); ++i) {
+ const SegmentLayout& segment = this->layout.segments[i];
+ if ( (segment.vmAddr <= addr) && (addr < (segment.vmAddr + segment.vmSize)) ) {
+ segIndex = i;
+ segOffset = addr - segment.vmAddr;
+ return true;
+ }
+ }
+ return false;
+}
+
+int Fixups::libOrdinalFromDesc(uint16_t n_desc) const
+{
+ // -flat_namespace is always flat lookup
+ if ( (this->layout.mf->flags & MH_TWOLEVEL) == 0 )
+ return BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+
+ // extract byte from undefined symbol entry
+ int libIndex = GET_LIBRARY_ORDINAL(n_desc);
+ switch ( libIndex ) {
+ case SELF_LIBRARY_ORDINAL:
+ return BIND_SPECIAL_DYLIB_SELF;
+
+ case DYNAMIC_LOOKUP_ORDINAL:
+ return BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+
+ case EXECUTABLE_ORDINAL:
+ return BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE;
+ }
+
+ return libIndex;
+}
+
+// MARK: --- SplitSeg methods ---
+
+SplitSeg::SplitSeg(const Layout& layout)
+ : layout(layout)
+{
+}
+
+bool SplitSeg::hasMarker() const
+{
+ if ( !this->layout.linkedit.splitSegInfo.hasValue() )
+ return false;
+
+ return this->layout.linkedit.splitSegInfo.bufferSize == 0;
+}
+
+bool SplitSeg::isV1() const
+{
+ if ( !this->layout.linkedit.splitSegInfo.hasValue() )
+ return false;
+
+ const void* splitSegStart = this->layout.linkedit.splitSegInfo.buffer;
+ return (*(const uint8_t*)splitSegStart) != DYLD_CACHE_ADJ_V2_FORMAT;
+}
+
+bool SplitSeg::isV2() const
+{
+ if ( !this->layout.linkedit.splitSegInfo.hasValue() )
+ return false;
+
+ const void* splitSegStart = this->layout.linkedit.splitSegInfo.buffer;
+ return (*(const uint8_t*)splitSegStart) == DYLD_CACHE_ADJ_V2_FORMAT;
+}
+
+bool SplitSeg::hasValue() const
+{
+ return this->layout.linkedit.splitSegInfo.hasValue();
+}
+
+void SplitSeg::forEachReferenceV2(Diagnostics& diag, ReferenceCallbackV2 callback) const
+{
+ const uint8_t* infoStart = layout.linkedit.splitSegInfo.buffer;
+ const uint8_t* infoEnd = infoStart + layout.linkedit.splitSegInfo.bufferSize;
+
+ if ( *infoStart++ != DYLD_CACHE_ADJ_V2_FORMAT ) {
+ return;
+ }
+
+ // Whole :== <count> FromToSection+
+ // FromToSection :== <from-sect-index> <to-sect-index> <count> ToOffset+
+ // ToOffset :== <to-sect-offset-delta> <count> FromOffset+
+ // FromOffset :== <kind> <count> <from-sect-offset-delta>
+ const uint8_t* p = infoStart;
+ uint64_t sectionCount = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+ for (uint64_t i=0; i < sectionCount; ++i) {
+ uint64_t fromSectionIndex = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+ uint64_t toSectionIndex = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+ uint64_t toOffsetCount = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+ uint64_t toSectionOffset = 0;
+ for (uint64_t j=0; j < toOffsetCount; ++j) {
+ uint64_t toSectionDelta = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+ uint64_t fromOffsetCount = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+ toSectionOffset += toSectionDelta;
+ for (uint64_t k=0; k < fromOffsetCount; ++k) {
+ uint64_t kind = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+ if ( kind > 13 ) {
+ diag.error("bad kind (%llu) value in %s\n", kind, ((const Header*)this->layout.mf)->installName());
+ }
+ uint64_t fromSectDeltaCount = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+ uint64_t fromSectionOffset = 0;
+ for (uint64_t l=0; l < fromSectDeltaCount; ++l) {
+ uint64_t delta = dyld3::MachOFile::read_uleb128(diag, p, infoEnd);
+ fromSectionOffset += delta;
+ bool stop = false;
+ callback(fromSectionIndex, fromSectionOffset, toSectionIndex, toSectionOffset, stop);
+ if ( stop )
+ return;
+ }
+ }
+ }
+ }
+}
+
+
+void SplitSeg::forEachSplitSegSection(void (^callback)(std::string_view segmentName,
+ std::string_view sectionName,
+ uint64_t sectionVMAddr)) const
+{
+ callback("mach header", "", 0);
+ ((const Header*)this->layout.mf)->forEachSection(^(const Header::SectionInfo §Info, bool &stop) {
+ callback(sectInfo.segmentName, sectInfo.sectionName, sectInfo.address);
+ });
+}
+
+// MARK: --- ExportTrie methods ---
+
+ExportTrie::ExportTrie(const Layout& layout)
+ : layout(layout)
+{
+}
+
+static void recurseTrie(Diagnostics& diag, const uint8_t* const start, const uint8_t* p, const uint8_t* const end,
+ dyld3::OverflowSafeArray<char>& cummulativeString, int curStrOffset, bool& stop,
+ ExportTrie::ExportsCallback callback)
+{
+ if ( p >= end ) {
+ diag.error("malformed trie, node past end");
+ return;
+ }
+ const uint64_t terminalSize = dyld3::MachOFile::read_uleb128(diag, p, end);
+ const uint8_t* children = p + terminalSize;
+ if ( terminalSize != 0 ) {
+ uint64_t imageOffset = 0;
+ uint64_t flags = dyld3::MachOFile::read_uleb128(diag, p, end);
+ uint64_t other = 0;
+ const char* importName = nullptr;
+ if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) {
+ other = dyld3::MachOFile::read_uleb128(diag, p, end); // dylib ordinal
+ importName = (char*)p;
+ }
+ else {
+ imageOffset = dyld3::MachOFile::read_uleb128(diag, p, end);
+ if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER )
+ other = dyld3::MachOFile::read_uleb128(diag, p, end);
+ else
+ other = 0;
+ }
+ if ( diag.hasError() )
+ return;
+ callback(cummulativeString.begin(), imageOffset, flags, other, importName, stop);
+ if ( stop )
+ return;
+ }
+ if ( children > end ) {
+ diag.error("malformed trie, terminalSize extends beyond trie data");
+ return;
+ }
+ const uint8_t childrenCount = *children++;
+ const uint8_t* s = children;
+ for (uint8_t i=0; i < childrenCount; ++i) {
+ int edgeStrLen = 0;
+ while (*s != '\0') {
+ cummulativeString.resize(curStrOffset+edgeStrLen + 1);
+ cummulativeString[curStrOffset+edgeStrLen] = *s++;
+ ++edgeStrLen;
+ if ( s > end ) {
+ diag.error("malformed trie node, child node extends past end of trie\n");
+ return;
+ }
+ }
+ cummulativeString.resize(curStrOffset+edgeStrLen + 1);
+ cummulativeString[curStrOffset+edgeStrLen] = *s++;
+ uint64_t childNodeOffset = dyld3::MachOFile::read_uleb128(diag, s, end);
+ if (childNodeOffset == 0) {
+ diag.error("malformed trie, childNodeOffset==0");
+ return;
+ }
+ recurseTrie(diag, start, start+childNodeOffset, end, cummulativeString, curStrOffset+edgeStrLen, stop, callback);
+ if ( diag.hasError() || stop )
+ return;
+ }
+}
+
+void ExportTrie::forEachExportedSymbol(Diagnostics& diag, ExportsCallback callback) const
+{
+ if ( layout.linkedit.exportsTrie.hasValue() ) {
+ const uint8_t* trieStart = layout.linkedit.exportsTrie.buffer;
+ const uint8_t* trieEnd = trieStart + layout.linkedit.exportsTrie.bufferSize;
+
+ // We still emit empty export trie load commands just as a placeholder to show we have
+ // no exports. In that case, don't start recursing as we'll immediately think we ran
+ // of the end of the buffer
+ if ( trieStart == trieEnd )
+ return;
+
+ bool stop = false;
+ STACK_ALLOC_OVERFLOW_SAFE_ARRAY(char, cummulativeString, 4096);
+ recurseTrie(diag, trieStart, trieStart, trieEnd, cummulativeString, 0, stop, callback);
+ }
+}
+
+// MARK: --- ChainedFixupPointerOnDisk methods ---
+
+uint64_t ChainedFixupPointerOnDisk::Arm64e::unpackTarget() const
+{
+ assert(this->authBind.bind == 0);
+ assert(this->authBind.auth == 0);
+ return ((uint64_t)(this->rebase.high8) << 56) | (this->rebase.target);
+}
+
+uint64_t ChainedFixupPointerOnDisk::Arm64e::signExtendedAddend() const
+{
+ assert(this->authBind.bind == 1);
+ assert(this->authBind.auth == 0);
+ uint64_t addend19 = this->bind.addend;
+ if ( addend19 & 0x40000 )
+ return addend19 | 0xFFFFFFFFFFFC0000ULL;
+ else
+ return addend19;
+}
+
+const char* ChainedFixupPointerOnDisk::Arm64e::keyName(uint8_t keyBits)
+{
+ static const char* const names[] = {
+ "IA", "IB", "DA", "DB"
+ };
+ assert(keyBits < 4);
+ return names[keyBits];
+}
+const char* ChainedFixupPointerOnDisk::Arm64e::keyName() const
+{
+ assert(this->authBind.auth == 1);
+ return keyName(this->authBind.key);
+}
+
+uint64_t ChainedFixupPointerOnDisk::Arm64e::signPointer(uint64_t unsignedAddr, void* loc, bool addrDiv, uint16_t diversity, uint8_t key)
+{
+ // don't sign NULL
+ if ( unsignedAddr == 0 )
+ return 0;
+
+#if __has_feature(ptrauth_calls)
+ uint64_t extendedDiscriminator = diversity;
+ if ( addrDiv )
+ extendedDiscriminator = __builtin_ptrauth_blend_discriminator(loc, extendedDiscriminator);
+ switch ( key ) {
+ case 0: // IA
+ return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 0, extendedDiscriminator);
+ case 1: // IB
+ return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 1, extendedDiscriminator);
+ case 2: // DA
+ return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 2, extendedDiscriminator);
+ case 3: // DB
+ return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 3, extendedDiscriminator);
+ }
+ assert(0 && "invalid signing key");
+#else
+ assert(0 && "arm64e signing only arm64e");
+#endif
+}
+
+
+uint64_t ChainedFixupPointerOnDisk::Arm64e::signPointer(void* loc, uint64_t target) const
+{
+ assert(this->authBind.auth == 1);
+ return signPointer(target, loc, authBind.addrDiv, authBind.diversity, authBind.key);
+}
+
+uint64_t ChainedFixupPointerOnDisk::Generic64::unpackedTarget() const
+{
+ return (((uint64_t)this->rebase.high8) << 56) | (uint64_t)(this->rebase.target);
+}
+
+uint64_t ChainedFixupPointerOnDisk::Generic64::signExtendedAddend() const
+{
+ uint64_t addend27 = this->bind.addend;
+ uint64_t top8Bits = addend27 & 0x00007F80000ULL;
+ uint64_t bottom19Bits = addend27 & 0x0000007FFFFULL;
+ uint64_t newValue = (top8Bits << 13) | (((uint64_t)(bottom19Bits << 37) >> 37) & 0x00FFFFFFFFFFFFFF);
+ return newValue;
+}
+
+const char* ChainedFixupPointerOnDisk::Kernel64::keyName() const
+{
+ static const char* names[] = {
+ "IA", "IB", "DA", "DB"
+ };
+ assert(this->isAuth == 1);
+ uint8_t keyBits = this->key;
+ assert(keyBits < 4);
+ return names[keyBits];
+}
+
+uint64_t ChainedFixupPointerOnDisk::Cache64e::high8() const
+{
+ assert(this->regular.auth == 0);
+ return ((uint64_t)(this->regular.high8) << 56);
+}
+
+const char* ChainedFixupPointerOnDisk::Cache64e::keyName() const
+{
+ assert(this->auth.auth == 1);
+ static const char* const names[] = {
+ "IA", "DA"
+ };
+ assert(this->auth.keyIsData < 2);
+ return names[this->auth.keyIsData];
+}
+
+uint64_t ChainedFixupPointerOnDisk::Cache64e::signPointer(uint64_t unsignedAddr, void* loc, bool addrDiv, uint16_t diversity, uint8_t keyIsData)
+{
+ // don't sign NULL
+ if ( unsignedAddr == 0 )
+ return 0;
+
+#if __has_feature(ptrauth_calls)
+ uint64_t extendedDiscriminator = diversity;
+ if ( addrDiv )
+ extendedDiscriminator = __builtin_ptrauth_blend_discriminator(loc, extendedDiscriminator);
+ switch ( keyIsData ) {
+ case 0: // IA
+ return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 0, extendedDiscriminator);
+ case 1: // DA
+ return (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 2, extendedDiscriminator);
+ }
+ assert(0 && "invalid signing key");
+#else
+ assert(0 && "arm64e signing only arm64e");
+#endif
+}
+
+
+uint64_t ChainedFixupPointerOnDisk::Cache64e::signPointer(void* loc, uint64_t target) const
+{
+ assert(this->auth.auth == 1);
+ return signPointer(target, loc, auth.addrDiv, auth.diversity, auth.keyIsData);
+}
+
+bool ChainedFixupPointerOnDisk::isRebase(uint16_t pointerFormat, uint64_t preferedLoadAddress, uint64_t& targetRuntimeOffset) const
+{
+ switch (pointerFormat) {
+ case DYLD_CHAINED_PTR_ARM64E:
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND:
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
+ case DYLD_CHAINED_PTR_ARM64E_KERNEL:
+ case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
+ if ( this->arm64e.bind.bind )
+ return false;
+ if ( this->arm64e.authRebase.auth ) {
+ targetRuntimeOffset = this->arm64e.authRebase.target;
+ return true;
+ }
+ else {
+ targetRuntimeOffset = this->arm64e.unpackTarget();
+ if ( (pointerFormat == DYLD_CHAINED_PTR_ARM64E) || (pointerFormat == DYLD_CHAINED_PTR_ARM64E_FIRMWARE) ) {
+ targetRuntimeOffset -= preferedLoadAddress;
+ }
+ return true;
+ }
+ break;
+ case DYLD_CHAINED_PTR_64:
+ case DYLD_CHAINED_PTR_64_OFFSET:
+ if ( this->generic64.bind.bind )
+ return false;
+ targetRuntimeOffset = this->generic64.unpackedTarget();
+ if ( pointerFormat == DYLD_CHAINED_PTR_64 )
+ targetRuntimeOffset -= preferedLoadAddress;
+ return true;
+ break;
+ case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
+ case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
+ targetRuntimeOffset = this->kernel64.target;
+ return true;
+ break;
+ case DYLD_CHAINED_PTR_32:
+ if ( this->generic32.bind.bind )
+ return false;
+ targetRuntimeOffset = this->generic32.rebase.target - preferedLoadAddress;
+ return true;
+ break;
+ case DYLD_CHAINED_PTR_32_FIRMWARE:
+ targetRuntimeOffset = this->firmware32.target - preferedLoadAddress;
+ return true;
+ break;
+ case DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE:
+ if ( this->cache64e.regular.auth ) {
+ targetRuntimeOffset = this->cache64e.auth.runtimeOffset;
+ return true;
+ }
+ else {
+ targetRuntimeOffset = this->cache64e.regular.runtimeOffset;
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+ assert(0 && "unsupported pointer chain format");
+}
+
+bool ChainedFixupPointerOnDisk::isBind(uint16_t pointerFormat, uint32_t& bindOrdinal, int64_t& addend) const
+{
+ addend = 0;
+ switch (pointerFormat) {
+ case DYLD_CHAINED_PTR_ARM64E:
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND:
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
+ case DYLD_CHAINED_PTR_ARM64E_KERNEL:
+ case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
+ if ( !this->arm64e.authBind.bind )
+ return false;
+ if ( this->arm64e.authBind.auth ) {
+ if ( pointerFormat == DYLD_CHAINED_PTR_ARM64E_USERLAND24 )
+ bindOrdinal = this->arm64e.authBind24.ordinal;
+ else
+ bindOrdinal = this->arm64e.authBind.ordinal;
+ return true;
+ }
+ else {
+ if ( pointerFormat == DYLD_CHAINED_PTR_ARM64E_USERLAND24 )
+ bindOrdinal = this->arm64e.bind24.ordinal;
+ else
+ bindOrdinal = this->arm64e.bind.ordinal;
+ addend = this->arm64e.signExtendedAddend();
+ return true;
+ }
+ break;
+ case DYLD_CHAINED_PTR_64:
+ case DYLD_CHAINED_PTR_64_OFFSET:
+ if ( !this->generic64.bind.bind )
+ return false;
+ bindOrdinal = this->generic64.bind.ordinal;
+ addend = this->generic64.bind.addend;
+ return true;
+ break;
+ case DYLD_CHAINED_PTR_32:
+ if ( !this->generic32.bind.bind )
+ return false;
+ bindOrdinal = this->generic32.bind.ordinal;
+ addend = this->generic32.bind.addend;
+ return true;
+ break;
+ case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
+ case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
+ return false;
+ default:
+ break;
+ }
+ assert(0 && "unsupported pointer chain format");
+}
+
+unsigned ChainedFixupPointerOnDisk::strideSize(uint16_t pointerFormat)
+{
+ switch (pointerFormat) {
+ case DYLD_CHAINED_PTR_ARM64E:
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND:
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
+ case DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE:
+ return 8;
+ case DYLD_CHAINED_PTR_ARM64E_KERNEL:
+ case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
+ case DYLD_CHAINED_PTR_32_FIRMWARE:
+ case DYLD_CHAINED_PTR_64:
+ case DYLD_CHAINED_PTR_64_OFFSET:
+ case DYLD_CHAINED_PTR_32:
+ case DYLD_CHAINED_PTR_32_CACHE:
+ case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
+ return 4;
+ case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
+ return 1;
+ }
+ assert(0 && "unsupported pointer chain format");
+}
+
+// MARK: --- SymbolTable methods ---
+
+SymbolTable::SymbolTable(const Layout& layout)
+ : layout(layout)
+{
+}
+
+void SymbolTable::forEachLocalSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const
+{
+ const bool is64Bit = this->layout.mf->is64();
+ if ( this->layout.linkedit.symbolTable.hasValue() ) {
+ uint32_t localsStartIndex = 0;
+ uint32_t localsCount = this->layout.linkedit.symbolTable.entryCount;
+ if ( this->layout.linkedit.localSymbolTable.hasValue() ) {
+ localsStartIndex = this->layout.linkedit.localSymbolTable.entryIndex;
+ localsCount = this->layout.linkedit.localSymbolTable.entryCount;
+ }
+ uint32_t maxStringOffset = this->layout.linkedit.symbolStrings.bufferSize;
+ const char* stringPool = (char*)this->layout.linkedit.symbolStrings.buffer;
+ const struct nlist* symbols = (struct nlist*)this->layout.linkedit.symbolTable.buffer;
+ const struct nlist_64* symbols64 = (struct nlist_64*)this->layout.linkedit.symbolTable.buffer;
+ bool stop = false;
+ for (uint32_t i=0; (i < localsCount) && !stop; ++i) {
+ if ( is64Bit ) {
+ const struct nlist_64& sym = symbols64[localsStartIndex+i];
+ if ( sym.n_un.n_strx > maxStringOffset )
+ continue;
+ if ( ((sym.n_type & N_EXT) == 0) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
+ callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+ }
+ else {
+ const struct nlist& sym = symbols[localsStartIndex+i];
+ if ( sym.n_un.n_strx > maxStringOffset )
+ continue;
+ if ( ((sym.n_type & N_EXT) == 0) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
+ callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+ }
+ }
+ }
+}
+
+
+void SymbolTable::forEachGlobalSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const
+{
+ const bool is64Bit = this->layout.mf->is64();
+ if ( this->layout.linkedit.symbolTable.hasValue() ) {
+ uint32_t globalsStartIndex = 0;
+ uint32_t globalsCount = this->layout.linkedit.symbolTable.entryCount;
+ if ( this->layout.linkedit.globalSymbolTable.hasValue() ) {
+ globalsStartIndex = this->layout.linkedit.globalSymbolTable.entryIndex;
+ globalsCount = this->layout.linkedit.globalSymbolTable.entryCount;
+ }
+ uint32_t maxStringOffset = this->layout.linkedit.symbolStrings.bufferSize;
+ const char* stringPool = (char*)this->layout.linkedit.symbolStrings.buffer;
+ const struct nlist* symbols = (struct nlist*)this->layout.linkedit.symbolTable.buffer;
+ const struct nlist_64* symbols64 = (struct nlist_64*)this->layout.linkedit.symbolTable.buffer;
+ bool stop = false;
+ for (uint32_t i=0; (i < globalsCount) && !stop; ++i) {
+ if ( is64Bit ) {
+ const struct nlist_64& sym = symbols64[globalsStartIndex+i];
+ if ( sym.n_un.n_strx > maxStringOffset )
+ continue;
+ if ( (sym.n_type & N_EXT) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
+ callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+ }
+ else {
+ const struct nlist& sym = symbols[globalsStartIndex+i];
+ if ( sym.n_un.n_strx > maxStringOffset )
+ continue;
+ if ( (sym.n_type & N_EXT) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
+ callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+ }
+ }
+ }
+}
+
+
+void SymbolTable::forEachImportedSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const
+{
+ const bool is64Bit = this->layout.mf->is64();
+ if ( this->layout.linkedit.symbolTable.hasValue() ) {
+ uint32_t undefsStartIndex = 0;
+ uint32_t undefsCount = this->layout.linkedit.symbolTable.entryCount;
+ if ( this->layout.linkedit.undefSymbolTable.hasValue() ) {
+ undefsStartIndex = this->layout.linkedit.undefSymbolTable.entryIndex;
+ undefsCount = this->layout.linkedit.undefSymbolTable.entryCount;
+ }
+ uint32_t maxStringOffset = this->layout.linkedit.symbolStrings.bufferSize;
+ const char* stringPool = (char*)this->layout.linkedit.symbolStrings.buffer;
+ const struct nlist* symbols = (struct nlist*)this->layout.linkedit.symbolTable.buffer;
+ const struct nlist_64* symbols64 = (struct nlist_64*)this->layout.linkedit.symbolTable.buffer;
+ bool stop = false;
+ for (uint32_t i=0; (i < undefsCount) && !stop; ++i) {
+ if ( is64Bit ) {
+ const struct nlist_64& sym = symbols64[undefsStartIndex+i];
+ if ( sym.n_un.n_strx > maxStringOffset )
+ continue;
+ if ( (sym.n_type & N_TYPE) == N_UNDF )
+ callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+ }
+ else {
+ const struct nlist& sym = symbols[undefsStartIndex+i];
+ if ( sym.n_un.n_strx > maxStringOffset )
+ continue;
+ if ( (sym.n_type & N_TYPE) == N_UNDF )
+ callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
+ }
+ }
+ }
+}
+
+void SymbolTable::forEachIndirectSymbol(Diagnostics& diag,
+ void (^callback)(const char* symbolName, uint32_t symNum)) const
+{
+ // find lazy and non-lazy pointer sections
+ const bool is64Bit = this->layout.mf->is64();
+ const uint32_t* const indirectSymbolTable = (uint32_t*)this->layout.linkedit.indirectSymbolTable.buffer;
+ const uint32_t indirectSymbolTableCount = this->layout.linkedit.indirectSymbolTable.entryCount;
+ const void* symbolTable = this->layout.linkedit.symbolTable.buffer;
+ const struct nlist_64* symbols64 = (nlist_64*)symbolTable;
+ const struct nlist* symbols32 = (struct nlist*)symbolTable;
+ const char* stringPool = (char*)this->layout.linkedit.symbolStrings.buffer;
+ uint32_t symCount = this->layout.linkedit.symbolTable.entryCount;
+ uint32_t poolSize = this->layout.linkedit.symbolStrings.bufferSize;
+
+ if ( indirectSymbolTableCount == 0 )
+ return;
+
+ for (uint32_t i = 0; i != indirectSymbolTableCount; ++i ) {
+ uint32_t symNum = indirectSymbolTable[i];
+ if ( symNum == INDIRECT_SYMBOL_ABS ) {
+ // FIXME: The client wants to know about all the entries, so what should we pass here?
+ callback(nullptr, symNum);
+ continue;
+ }
+ if ( symNum == INDIRECT_SYMBOL_LOCAL ) {
+ callback("", symNum);
+ continue;
+ }
+ if ( symNum == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS) ) {
+ // FIXME: We are using the "local" callback. Should we use the "abs" one instead
+ callback("", symNum);
+ continue;
+ }
+ if ( symNum > symCount ) {
+ diag.error("indirect symbol[%d] = %d which is invalid symbol index", i, symNum);
+ return;
+ }
+ uint32_t strOffset = is64Bit ? symbols64[symNum].n_un.n_strx : symbols32[symNum].n_un.n_strx;
+ if ( strOffset > poolSize ) {
+ diag.error("symbol[%d] string offset out of range", i);
+ return;
+ }
+ const char* symbolName = stringPool + strOffset;
+ callback(symbolName, symNum);
+ }
+}
+
+} // namespace mach_o
+