2 * Copyright (c) 2019 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
24 #include <sys/types.h>
25 #include <mach/mach.h>
31 #include <mach-o/reloc.h>
32 #include <mach-o/nlist.h>
33 #include <TargetConditionals.h>
35 #include "MachOAnalyzerSet.h"
36 #include "DyldSharedCache.h"
39 namespace dyld
{ void log(const char*, ...); }
44 static bool hasHigh8(uint64_t addend
)
46 // distinguish negative addend from TBI
47 if ( (addend
>> 56) == 0 )
49 return ( (addend
>> 48) != 0xFFFF );
52 void MachOAnalyzerSet::WrappedMachO::forEachBind(Diagnostics
& diag
, FixUpHandler fixUpHandler
, CachePatchHandler patchHandler
) const
54 const bool is64
= _mh
->is64();
55 __block
int lastLibOrdinal
= 256;
56 __block
const char* lastSymbolName
= nullptr;
57 __block
uint64_t lastAddend
= 0;
58 __block FixupTarget target
;
59 __block PointerMetaData pmd
;
60 _mh
->forEachBind(diag
, ^(uint64_t runtimeOffset
, int libOrdinal
, const char* symbolName
, bool weakImport
, bool lazyBind
, uint64_t addend
, bool& stop
) {
61 if ( (symbolName
== lastSymbolName
) && (libOrdinal
== lastLibOrdinal
) && (addend
== lastAddend
) ) {
62 // same symbol lookup as last location
63 fixUpHandler(runtimeOffset
, pmd
, target
, stop
);
65 else if ( this->findSymbolFrom(diag
, libOrdinal
, symbolName
, weakImport
, lazyBind
, addend
, patchHandler
, target
) ) {
67 if ( is64
&& (target
.addend
!= 0) ) {
68 if ( hasHigh8(target
.addend
) ) {
69 pmd
.high8
= (target
.addend
>> 56);
70 target
.offsetInImage
&= 0x00FFFFFFFFFFFFFFULL
;
71 target
.addend
&= 0x00FFFFFFFFFFFFFFULL
;
74 if ( !target
.skippableWeakDef
) {
75 fixUpHandler(runtimeOffset
, pmd
, target
, stop
);
76 lastSymbolName
= symbolName
;
77 lastLibOrdinal
= libOrdinal
;
82 // call handler with missing symbol before stopping
83 if ( target
.kind
== FixupTarget::Kind::bindMissingSymbol
)
84 fixUpHandler(runtimeOffset
, pmd
, target
, stop
);
87 }, ^(const char* symbolName
) {
91 MachOAnalyzerSet::PointerMetaData::PointerMetaData()
95 this->authenticated
= 0;
97 this->usesAddrDiversity
= 0;
100 MachOAnalyzerSet::PointerMetaData::PointerMetaData(const MachOLoaded::ChainedFixupPointerOnDisk
* fixupLoc
, uint16_t pointer_format
)
104 this->authenticated
= 0;
106 this->usesAddrDiversity
= 0;
107 switch ( pointer_format
) {
108 case DYLD_CHAINED_PTR_ARM64E
:
109 case DYLD_CHAINED_PTR_ARM64E_KERNEL
:
110 case DYLD_CHAINED_PTR_ARM64E_USERLAND
:
111 case DYLD_CHAINED_PTR_ARM64E_FIRMWARE
:
112 case DYLD_CHAINED_PTR_ARM64E_USERLAND24
:
113 this->authenticated
= fixupLoc
->arm64e
.authRebase
.auth
;
114 if ( this->authenticated
) {
115 this->key
= fixupLoc
->arm64e
.authRebase
.key
;
116 this->usesAddrDiversity
= fixupLoc
->arm64e
.authRebase
.addrDiv
;
117 this->diversity
= fixupLoc
->arm64e
.authRebase
.diversity
;
119 else if ( fixupLoc
->arm64e
.bind
.bind
== 0 ) {
120 this->high8
= fixupLoc
->arm64e
.rebase
.high8
;
123 case DYLD_CHAINED_PTR_64
:
124 case DYLD_CHAINED_PTR_64_OFFSET
:
125 if ( fixupLoc
->generic64
.bind
.bind
== 0 )
126 this->high8
= fixupLoc
->generic64
.rebase
.high8
;
132 void MachOAnalyzerSet::WrappedMachO::forEachFixup(Diagnostics
& diag
, FixUpHandler fixup
, CachePatchHandler patcher
) const
134 uint16_t fmPointerFormat
;
135 uint32_t fmStartsCount
;
136 const uint32_t* fmStarts
;
137 const MachOAnalyzer
* ma
= _mh
;
138 const uint64_t prefLoadAddr
= ma
->preferredLoadAddress();
139 if ( ma
->hasChainedFixups() ) {
140 // build targets table
141 STACK_ALLOC_OVERFLOW_SAFE_ARRAY(FixupTarget
, targets
, 512);
142 ma
->forEachChainedFixupTarget(diag
, ^(int libOrdinal
, const char* symbolName
, uint64_t addend
, bool weakImport
, bool& stop
) {
143 targets
.default_constuct_back();
144 FixupTarget
& foundTarget
= targets
.back();
145 if ( !this->findSymbolFrom(diag
, libOrdinal
, symbolName
, weakImport
, false, addend
, patcher
, foundTarget
) ) {
146 // call handler with missing symbol before stopping
147 if ( foundTarget
.kind
== FixupTarget::Kind::bindMissingSymbol
)
148 fixup(0, PointerMetaData(), foundTarget
, stop
);
152 if ( diag
.hasError() )
156 ma
->withChainStarts(diag
, ma
->chainStartsOffset(), ^(const dyld_chained_starts_in_image
* startsInfo
) {
157 ma
->forEachFixupInAllChains(diag
, startsInfo
, false, ^(MachOLoaded::ChainedFixupPointerOnDisk
* fixupLoc
,
158 const dyld_chained_starts_in_segment
* segInfo
, bool& fixupsStop
) {
159 uint64_t fixupOffset
= (uint8_t*)fixupLoc
- (uint8_t*)ma
;
160 uint64_t targetOffset
;
161 uint32_t bindOrdinal
;
162 int64_t embeddedAddend
;
163 PointerMetaData
pmd(fixupLoc
, segInfo
->pointer_format
);
164 if ( fixupLoc
->isBind(segInfo
->pointer_format
, bindOrdinal
, embeddedAddend
) ) {
165 if ( bindOrdinal
< targets
.count() ) {
166 if ( embeddedAddend
== 0 ) {
167 if ( hasHigh8(targets
[bindOrdinal
].addend
) ) {
168 FixupTarget targetWithoutHigh8
= targets
[bindOrdinal
];
169 pmd
.high8
= (targetWithoutHigh8
.addend
>> 56);
170 targetWithoutHigh8
.offsetInImage
&= 0x00FFFFFFFFFFFFFFULL
;
171 targetWithoutHigh8
.addend
&= 0x00FFFFFFFFFFFFFFULL
;
172 fixup(fixupOffset
, pmd
, targetWithoutHigh8
, fixupsStop
);
175 fixup(fixupOffset
, pmd
, targets
[bindOrdinal
], fixupsStop
);
179 // pointer on disk encodes extra addend, make pseudo target for that
180 FixupTarget targetWithAddend
= targets
[bindOrdinal
];
181 targetWithAddend
.addend
+= embeddedAddend
;
182 targetWithAddend
.offsetInImage
+= embeddedAddend
;
183 fixup(fixupOffset
, pmd
, targetWithAddend
, fixupsStop
);
187 diag
.error("out of range bind ordinal %d (max %lu)", bindOrdinal
, targets
.count());
191 else if ( fixupLoc
->isRebase(segInfo
->pointer_format
, prefLoadAddr
, targetOffset
) ) {
192 FixupTarget rebaseTarget
;
193 rebaseTarget
.kind
= FixupTarget::Kind::rebase
;
194 rebaseTarget
.foundInImage
= *this;
195 rebaseTarget
.offsetInImage
= targetOffset
& 0x00FFFFFFFFFFFFFFULL
;
196 rebaseTarget
.isLazyBindRebase
= false; // FIXME
197 fixup(fixupOffset
, pmd
, rebaseTarget
, fixupsStop
);
202 else if ( ma
->hasFirmwareChainStarts(&fmPointerFormat
, &fmStartsCount
, &fmStarts
) ) {
203 // This is firmware which only has rebases, the chain starts info is in a section (not LINKEDIT)
204 ma
->forEachFixupInAllChains(diag
, fmPointerFormat
, fmStartsCount
, fmStarts
, ^(MachOLoaded::ChainedFixupPointerOnDisk
* fixupLoc
, bool& stop
) {
205 uint64_t fixupOffset
= (uint8_t*)fixupLoc
- (uint8_t*)ma
;
206 PointerMetaData
pmd(fixupLoc
, fmPointerFormat
);
207 uint64_t targetOffset
;
208 fixupLoc
->isRebase(fmPointerFormat
, prefLoadAddr
, targetOffset
);
209 FixupTarget rebaseTarget
;
210 rebaseTarget
.kind
= FixupTarget::Kind::rebase
;
211 rebaseTarget
.foundInImage
= *this;
212 rebaseTarget
.offsetInImage
= targetOffset
& 0x00FFFFFFFFFFFFFFULL
;
213 rebaseTarget
.isLazyBindRebase
= false;
214 fixup(fixupOffset
, pmd
, rebaseTarget
, stop
);
218 // process all rebase opcodes
219 const bool is64
= ma
->is64();
220 ma
->forEachRebase(diag
, ^(uint64_t runtimeOffset
, bool isLazyPointerRebase
, bool& stop
) {
221 uint64_t* loc
= (uint64_t*)((uint8_t*)ma
+ runtimeOffset
);
222 uint64_t locValue
= is64
? *loc
: *((uint32_t*)loc
);
223 FixupTarget rebaseTarget
;
226 pmd
.high8
= (locValue
>> 56);
227 rebaseTarget
.kind
= FixupTarget::Kind::rebase
;
228 rebaseTarget
.foundInImage
= *this;
229 rebaseTarget
.offsetInImage
= (locValue
& 0x00FFFFFFFFFFFFFFULL
) - prefLoadAddr
;
230 rebaseTarget
.isLazyBindRebase
= isLazyPointerRebase
;
231 fixup(runtimeOffset
, pmd
, rebaseTarget
, stop
);
233 if ( diag
.hasError() )
236 // process all bind opcodes
237 this->forEachBind(diag
, fixup
, patcher
);
239 if ( diag
.hasError() )
242 // main executable may define operator new/delete symbols that overrides weak-defs but have no fixups
243 if ( ma
->isMainExecutable() && ma
->hasWeakDefs() ) {
244 _set
->wmo_findExtraSymbolFrom(this, patcher
);
249 bool MachOAnalyzerSet::wmo_findSymbolFrom(const WrappedMachO
* fromWmo
, Diagnostics
& diag
, int libOrdinal
, const char* symbolName
, bool weakImport
,
250 bool lazyBind
, uint64_t addend
, CachePatchHandler patcher
, FixupTarget
& target
) const
252 target
.libOrdinal
= libOrdinal
;
253 if ( libOrdinal
== BIND_SPECIAL_DYLIB_FLAT_LOOKUP
) {
254 __block
bool found
= false;
255 this->mas_forEachImage(^(const WrappedMachO
& anImage
, bool hidden
, bool& stop
) {
256 // when an image is hidden (RTLD_LOCAL) it can still look up symbols in itself
257 if ( hidden
&& (fromWmo
->_mh
!= anImage
._mh
) )
259 if ( anImage
.findSymbolIn(diag
, symbolName
, addend
, target
) ) {
266 // see if missing symbol resolver can find something
267 if ( fromWmo
->missingSymbolResolver(weakImport
, lazyBind
, symbolName
, "flat namespace", fromWmo
->path(), target
) )
269 // fill out target info about missing symbol
270 target
.kind
= FixupTarget::Kind::bindMissingSymbol
;
271 target
.requestedSymbolName
= symbolName
;
272 target
.foundSymbolName
= nullptr;
273 target
.foundInImage
= WrappedMachO(); // no image it should be in
275 diag
.error("symbol '%s' not found, expected in flat namespace by '%s'", symbolName
, fromWmo
->path());
278 else if ( libOrdinal
== BIND_SPECIAL_DYLIB_WEAK_LOOKUP
) {
279 if ( this->mas_fromImageWeakDefLookup(*fromWmo
, symbolName
, addend
, patcher
, target
) ) {
280 target
.weakCoalesced
= true;
284 if ( !fromWmo
->_mh
->hasChainedFixups() ) {
285 // support old binaries where symbols have been stripped and have weak_bind to itself
286 target
.skippableWeakDef
= true;
289 // see if missing symbol resolver can find something
290 if ( fromWmo
->missingSymbolResolver(weakImport
, lazyBind
, symbolName
, "flat namespace", fromWmo
->path(), target
) )
292 // fill out target info about missing symbol
293 target
.kind
= FixupTarget::Kind::bindMissingSymbol
;
294 target
.requestedSymbolName
= symbolName
;
295 target
.foundSymbolName
= nullptr;
296 target
.foundInImage
= WrappedMachO(); // no image it should be in
298 diag
.error("symbol '%s' not found, expected to be weak-def coalesced in '%s'", symbolName
, fromWmo
->path());
302 int depIndex
= libOrdinal
- 1;
303 bool missingWeakDylib
= false;
304 WrappedMachO depHelper
;
305 const WrappedMachO
* targetImage
= nullptr;
306 if ( libOrdinal
== BIND_SPECIAL_DYLIB_SELF
) {
307 targetImage
= fromWmo
;
309 else if ( libOrdinal
== BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE
) {
310 this->mas_mainExecutable(depHelper
);
311 targetImage
= &depHelper
;
313 else if ( fromWmo
->dependent(depIndex
, depHelper
, missingWeakDylib
) ) {
314 targetImage
= &depHelper
;
317 diag
.error("unknown library ordinal %d in %s", libOrdinal
, fromWmo
->path());
320 // use two-level namespace target image
321 if ( !missingWeakDylib
&& targetImage
->findSymbolIn(diag
, symbolName
, addend
, target
) )
324 // see if missing symbol resolver can find something
325 const char* expectedInPath
= missingWeakDylib
? "missing dylib" : targetImage
->path();
326 if ( fromWmo
->missingSymbolResolver(weakImport
, lazyBind
, symbolName
, expectedInPath
, fromWmo
->path(), target
) )
329 // fill out target info about missing symbol
330 target
.kind
= FixupTarget::Kind::bindMissingSymbol
;
331 target
.requestedSymbolName
= symbolName
;
332 target
.foundSymbolName
= nullptr;
333 target
.foundInImage
= *targetImage
; // no image it is expected to be in
335 // symbol not found and not weak or lazy so error out
336 diag
.error("symbol '%s' not found, expected in '%s', needed by '%s'", symbolName
, expectedInPath
, fromWmo
->path());
342 // These are mangled symbols for all the variants of operator new and delete
343 // which a main executable can define (non-weak) and override the
344 // weak-def implementation in the OS.
345 static const char* const sTreatAsWeak
[] = {
346 "__Znwm", "__ZnwmRKSt9nothrow_t",
347 "__Znam", "__ZnamRKSt9nothrow_t",
348 "__ZdlPv", "__ZdlPvRKSt9nothrow_t", "__ZdlPvm",
349 "__ZdaPv", "__ZdaPvRKSt9nothrow_t", "__ZdaPvm",
350 "__ZnwmSt11align_val_t", "__ZnwmSt11align_val_tRKSt9nothrow_t",
351 "__ZnamSt11align_val_t", "__ZnamSt11align_val_tRKSt9nothrow_t",
352 "__ZdlPvSt11align_val_t", "__ZdlPvSt11align_val_tRKSt9nothrow_t", "__ZdlPvmSt11align_val_t",
353 "__ZdaPvSt11align_val_t", "__ZdaPvSt11align_val_tRKSt9nothrow_t", "__ZdaPvmSt11align_val_t"
356 void MachOAnalyzerSet::wmo_findExtraSymbolFrom(const WrappedMachO
* fromWmo
, CachePatchHandler patcher
) const
358 for (const char* weakSymbolName
: sTreatAsWeak
) {
359 Diagnostics exportDiag
;
360 FixupTarget dummyTarget
;
361 // pretend main executable does have a use of this operator new/delete and look up the impl
362 // this has the side effect of adding a cache patch if there is an impl outside the cache
363 wmo_findSymbolFrom(fromWmo
, exportDiag
, -3, weakSymbolName
, true, false, 0, patcher
, dummyTarget
);
368 bool MachOAnalyzerSet::WrappedMachO::findSymbolIn(Diagnostics
& diag
, const char* symbolName
, uint64_t addend
, FixupTarget
& target
) const
370 const MachOAnalyzer
* ma
= _mh
;
372 // if exports trie location not computed yet, do it now
373 ExportsTrie exportsTrie
= this->getExportsTrie();
375 target
.foundSymbolName
= nullptr;
376 if ( exportsTrie
.start
) {
377 if ( const uint8_t* node
= this->_mh
->trieWalk(diag
, exportsTrie
.start
, exportsTrie
.end
, symbolName
)) {
378 const uint8_t* p
= node
;
379 const uint64_t flags
= this->_mh
->read_uleb128(diag
, p
, exportsTrie
.end
);
380 if ( flags
& EXPORT_SYMBOL_FLAGS_REEXPORT
) {
381 // re-export from another dylib, lookup there
382 const uint64_t libOrdinal
= ma
->read_uleb128(diag
, p
, exportsTrie
.end
);
383 const char* importedName
= (char*)p
;
384 if ( importedName
[0] == '\0' )
385 importedName
= symbolName
;
386 const int depIndex
= (int)(libOrdinal
- 1);
387 bool missingWeakDylib
;
388 WrappedMachO depHelper
;
389 if ( this->dependent(depIndex
, depHelper
, missingWeakDylib
) && !missingWeakDylib
) {
390 if ( depHelper
.findSymbolIn(diag
, importedName
, addend
, target
) ) {
391 target
.requestedSymbolName
= symbolName
;
395 if ( !missingWeakDylib
)
396 diag
.error("re-export ordinal %lld out of range for %s", libOrdinal
, symbolName
);
399 target
.kind
= FixupTarget::Kind::bindToImage
;
400 target
.requestedSymbolName
= symbolName
;
401 target
.foundSymbolName
= symbolName
;
402 target
.foundInImage
= *this;
403 target
.isWeakDef
= false;
404 target
.addend
= addend
;
405 uint64_t trieValue
= ma
->read_uleb128(diag
, p
, exportsTrie
.end
);
406 switch ( flags
& EXPORT_SYMBOL_FLAGS_KIND_MASK
) {
407 case EXPORT_SYMBOL_FLAGS_KIND_REGULAR
:
408 target
.offsetInImage
= trieValue
+ addend
;
409 if ( flags
& EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER
) {
410 // for now, just return address of resolver helper stub
411 // FIXME handle running resolver
412 (void)this->_mh
->read_uleb128(diag
, p
, exportsTrie
.end
);
414 if ( flags
& EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION
)
415 target
.isWeakDef
= true;
417 case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL
:
418 // no type checking that client expected TLV yet
419 target
.offsetInImage
= trieValue
;
421 case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE
:
422 target
.kind
= FixupTarget::Kind::bindAbsolute
;
423 target
.offsetInImage
= trieValue
+ addend
;
426 diag
.error("unsupported exported symbol kind. flags=%llu at node offset=0x%0lX", flags
, (long)(node
-exportsTrie
.start
));
433 ma
->forEachGlobalSymbol(diag
, ^(const char* n_name
, uint64_t n_value
, uint8_t n_type
, uint8_t n_sect
, uint16_t n_desc
, bool& stop
) {
434 if ( strcmp(n_name
, symbolName
) == 0 ) {
435 target
.kind
= FixupTarget::Kind::bindToImage
;
436 target
.foundSymbolName
= symbolName
;
437 target
.requestedSymbolName
= symbolName
;
438 target
.foundInImage
= *this;
439 target
.offsetInImage
= n_value
- ma
->preferredLoadAddress() + addend
;
440 target
.addend
= addend
;
444 if ( target
.foundSymbolName
)
448 // symbol not exported from this image
449 // if this is a dylib and has re-exported dylibs, search those too
450 if ( (ma
->filetype
== MH_DYLIB
) && ((ma
->flags
& MH_NO_REEXPORTED_DYLIBS
) == 0) ) {
451 __block
unsigned depIndex
= 0;
452 ma
->forEachDependentDylib(^(const char* loadPath
, bool isWeak
, bool isReExport
, bool isUpward
, uint32_t compatVersion
, uint32_t curVersion
, bool& stop
) {
454 bool missingWeakDylib
;
456 if ( this->dependent(depIndex
, child
, missingWeakDylib
) && !missingWeakDylib
) {
457 if ( child
.findSymbolIn(diag
, symbolName
, addend
, target
) )
465 return (target
.foundSymbolName
!= nullptr);
469 MachOAnalyzerSet::ExportsTrie
MachOAnalyzerSet::wmo_getExportsTrie(const WrappedMachO
* wmo
) const
471 const uint8_t* start
= nullptr;
472 const uint8_t* end
= nullptr;
473 uint32_t runtimeOffset
;
475 if ( wmo
->_mh
->hasExportTrie(runtimeOffset
, size
) ) {
476 start
= (uint8_t*)wmo
->_mh
+ runtimeOffset
;
479 return { start
, end
};
483 // scan all weak-def images in load order
484 // return first non-weak defintion found
485 // otherwise first weak definition found
486 bool MachOAnalyzerSet::mas_fromImageWeakDefLookup(const WrappedMachO
& fromWmo
, const char* symbolName
, uint64_t addend
, CachePatchHandler patcher
, FixupTarget
& target
) const
488 // walk all images in load order, looking only at ones with weak-defs
489 const DyldSharedCache
* dyldCache
= (DyldSharedCache
*)mas_dyldCache();
490 __block
bool foundImpl
= false;
491 this->mas_forEachImage(^(const WrappedMachO
& anImage
, bool hidden
, bool& stop
) {
492 if ( !anImage
._mh
->hasWeakDefs() )
494 // when an image is hidden (RTLD_LOCAL) it can still look up symbols in itself
495 if ( hidden
&& (fromWmo
._mh
!= anImage
._mh
) )
497 FixupTarget tempTarget
;
499 if ( anImage
.findSymbolIn(diag
, symbolName
, addend
, tempTarget
) ) {
500 // ignore symbol re-exports, we will find the real definition later in forEachImage()
501 if ( anImage
._mh
!= tempTarget
.foundInImage
._mh
)
503 if ( foundImpl
&& anImage
._mh
->inDyldCache() && (anImage
._mh
!= target
.foundInImage
._mh
) ) {
504 // we have already found the target, but now we see something in the dyld cache
505 // that also implements this symbol, so we need to change all caches uses of that
506 // to use the found one instead
507 uint32_t cachedDylibIndex
= 0;
508 if ( dyldCache
->findMachHeaderImageIndex(anImage
._mh
, cachedDylibIndex
) ) {
509 uintptr_t exportCacheOffset
= (uint8_t*)tempTarget
.foundInImage
._mh
+ tempTarget
.offsetInImage
- (uint8_t*)dyldCache
;
510 patcher(cachedDylibIndex
, (uint32_t)exportCacheOffset
, target
);
514 // this is first found, so copy this to result
518 else if ( target
.isWeakDef
&& !tempTarget
.isWeakDef
) {
519 // we found a non-weak impl later on, switch to it