]>
Commit | Line | Data |
---|---|---|
eaf282aa A |
1 | /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- |
2 | * | |
3 | * Copyright (c) 2015 Apple Inc. All rights reserved. | |
4 | * | |
5 | * @APPLE_LICENSE_HEADER_START@ | |
6 | * | |
7 | * This file contains Original Code and/or Modifications of Original Code | |
8 | * as defined in and that are subject to the Apple Public Source License | |
9 | * Version 2.0 (the 'License'). You may not use this file except in | |
10 | * compliance with the License. Please obtain a copy of the License at | |
11 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
12 | * file. | |
13 | * | |
14 | * The Original Code and all software distributed under the License are | |
15 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
16 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
17 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
19 | * Please see the License for the specific language governing rights and | |
20 | * limitations under the License. | |
21 | * | |
22 | * @APPLE_LICENSE_HEADER_END@ | |
23 | */ | |
24 | ||
25 | ||
26 | #include <sys/param.h> | |
27 | #include <sys/mman.h> | |
28 | ||
29 | #include <vector> | |
30 | ||
31 | #include "Architectures.hpp" | |
32 | #include "bitcode.hpp" | |
33 | #include "MachOFileAbstraction.hpp" | |
34 | #include "MachOTrie.hpp" | |
35 | #include "textstub_dylib_file.hpp" | |
36 | ||
37 | namespace { | |
38 | ||
39 | /// | |
40 | /// A token is a light-weight reference to the content of an nmap'ed file. It | |
41 | /// doesn't own the data and it doesn't make a copy of it. The referenced data | |
42 | /// is only valid as long as the file is mapped in. | |
43 | /// | |
44 | class Token { | |
45 | const char* _p; | |
46 | size_t _size; | |
47 | ||
48 | int compareMemory(const char* lhs, const char* rhs, size_t size) const { | |
49 | if (size == 0) | |
50 | return 0; | |
51 | return ::memcmp(lhs, rhs, size); | |
52 | } | |
53 | ||
54 | public: | |
55 | Token() : _p(nullptr), _size(0) {} | |
56 | ||
57 | Token(const char* p) : _p(p), _size(0) { | |
58 | if (p) | |
59 | _size = ::strlen(p); | |
60 | } | |
61 | ||
62 | Token(const char* p, size_t s) : _p(p), _size(s) {} | |
63 | ||
64 | const char* data() const { return _p; } | |
65 | ||
66 | size_t size() const { return _size; } | |
67 | ||
68 | std::string str() const { return std::move(std::string(_p, _size)); } | |
69 | ||
70 | bool empty() const { return _size == 0; } | |
71 | ||
72 | bool operator==(Token other) const { | |
73 | if (_size != other._size) | |
74 | return false; | |
75 | return compareMemory(_p, other._p, _size) == 0; | |
76 | } | |
77 | ||
78 | bool operator!=(Token other) const { | |
79 | return !(*this == other); | |
80 | } | |
81 | }; | |
82 | ||
83 | /// | |
84 | /// Simple text-based dynamic library file tokenizer. | |
85 | /// | |
86 | class Tokenizer { | |
87 | const char* _start; | |
88 | const char* _current; | |
89 | const char* _end; | |
90 | Token _currentToken; | |
91 | ||
92 | void fetchNextToken(); | |
93 | void scanToNextToken(); | |
94 | void skip(unsigned distance) { | |
95 | _current += distance; | |
96 | assert(_current <= _end && "Skipped past the end"); | |
97 | } | |
98 | ||
99 | const char* skipLineBreak(const char* pos) const; | |
100 | bool isDelimiter(const char* pos) const; | |
101 | ||
102 | public: | |
103 | Tokenizer(const char* data, uint64_t size) : _start(data), _current(data), _end(data + size) {} | |
104 | ||
105 | void reset() { | |
106 | _current = _start; | |
107 | fetchNextToken(); | |
108 | } | |
109 | ||
110 | Token peek() { return _currentToken; } | |
111 | Token next() { | |
112 | Token token = peek(); | |
113 | fetchNextToken(); | |
114 | return token; | |
115 | } | |
116 | }; | |
117 | ||
118 | const char* Tokenizer::skipLineBreak(const char* pos) const | |
119 | { | |
120 | if ( pos == _end ) | |
121 | return pos; | |
122 | ||
123 | // Carriage return. | |
124 | if ( *pos == 0x0D ) { | |
125 | // line feed. | |
126 | if ( pos + 1 != _end && *(pos + 1) == 0x0A) | |
127 | return pos + 2; | |
128 | return pos + 1; | |
129 | } | |
130 | ||
131 | // line feed. | |
132 | if ( *pos == 0x0A ) | |
133 | return pos + 1; | |
134 | ||
135 | return pos; | |
136 | } | |
137 | ||
138 | void Tokenizer::scanToNextToken() { | |
139 | while (true) { | |
140 | while ( isDelimiter(_current) ) | |
141 | skip(1); | |
142 | ||
143 | const char* i = skipLineBreak(_current); | |
144 | if ( i == _current ) | |
145 | break; | |
146 | ||
147 | _current = i; | |
148 | } | |
149 | } | |
150 | ||
151 | ||
152 | bool Tokenizer::isDelimiter(const char* pos) const { | |
153 | if ( pos == _end ) | |
154 | return false; | |
155 | if ( *pos == ' ' || *pos == '\t' || *pos == '\r' || *pos == '\n' || *pos == ',' || *pos == ':' || *pos == '\'' || *pos == '\"' ) | |
156 | return true; | |
157 | return false; | |
158 | } | |
159 | ||
160 | void Tokenizer::fetchNextToken() { | |
161 | scanToNextToken(); | |
162 | ||
163 | if (_current == _end) { | |
164 | _currentToken = Token(); | |
165 | return; | |
166 | } | |
167 | ||
168 | auto start = _current; | |
169 | while ( !isDelimiter(_current) ) { | |
170 | ++_current; | |
171 | } | |
172 | ||
173 | _currentToken = Token(start, _current - start); | |
174 | } | |
175 | ||
176 | /// | |
177 | /// Representation of a parsed text-based dynamic library file. | |
178 | /// | |
179 | struct DynamicLibrary { | |
180 | Token _installName; | |
181 | uint32_t _currentVersion; | |
182 | uint32_t _compatibilityVersion; | |
183 | uint8_t _swiftVersion; | |
184 | ld::File::ObjcConstraint _objcConstraint; | |
185 | Options::Platform _platform; | |
186 | std::vector<Token> _allowedClients; | |
187 | std::vector<Token> _reexportedLibraries; | |
188 | std::vector<Token> _symbols; | |
189 | std::vector<Token> _classes; | |
190 | std::vector<Token> _ivars; | |
191 | std::vector<Token> _weakDefSymbols; | |
192 | std::vector<Token> _tlvSymbols; | |
193 | ||
194 | DynamicLibrary() : _currentVersion(0x10000), _compatibilityVersion(0x10000), _swiftVersion(0), | |
195 | _objcConstraint(ld::File::objcConstraintNone) {} | |
196 | }; | |
197 | ||
198 | static uint32_t parseVersionNumber32(Token token) { | |
199 | if ( token.size() >= 128 ) | |
200 | throwf("malformed version number"); | |
201 | ||
202 | char buffer[128]; | |
203 | uint32_t x = 0; | |
204 | uint32_t y = 0; | |
205 | uint32_t z = 0; | |
206 | char* end; | |
207 | ||
208 | // Make a null-terminated string. | |
209 | ::memcpy(buffer, token.data(), token.size()); | |
210 | buffer[token.size()] = '\0'; | |
211 | ||
212 | x = strtoul(buffer, &end, 10); | |
213 | if ( *end == '.' ) { | |
214 | y = strtoul(&end[1], &end, 10); | |
215 | if ( *end == '.' ) { | |
216 | z = strtoul(&end[1], &end, 10); | |
217 | } | |
218 | } | |
219 | if ( (x > 0xffff) || (y > 0xff) || (z > 0xff) ) | |
220 | throwf("malformed 32-bit x.y.z version number: %s", buffer); | |
221 | ||
222 | return (x << 16) | ( y << 8 ) | z; | |
223 | } | |
224 | ||
225 | /// | |
226 | /// A simple text-based dynamic library file parser. | |
227 | /// | |
228 | class TBDFile { | |
229 | Tokenizer _tokenizer; | |
230 | ||
231 | Token peek() { return _tokenizer.peek(); } | |
232 | Token next() { return _tokenizer.next(); } | |
233 | ||
234 | void expectToken(Token str) { | |
235 | Token token = next(); | |
236 | if (token != str) | |
237 | throwf("unexpected token: %s", token.str().c_str()); | |
238 | } | |
239 | ||
240 | bool hasOptionalToken(Token str) { | |
241 | auto token = peek(); | |
242 | if ( token == str ) { | |
243 | next(); | |
244 | return true; | |
245 | } | |
246 | return false; | |
247 | } | |
248 | ||
249 | ||
250 | void parseFlowSequence(std::function<void (Token)> func) { | |
251 | expectToken("["); | |
252 | ||
253 | while ( true ) { | |
254 | auto token = peek(); | |
255 | if ( token == "]" ) | |
256 | break; | |
257 | ||
258 | token = next(); | |
259 | func(token); | |
260 | } | |
261 | ||
262 | expectToken("]"); | |
263 | } | |
264 | ||
265 | void parseAllowedClients(DynamicLibrary& lib) { | |
266 | if ( !hasOptionalToken("allowed-clients") ) | |
267 | return; | |
268 | parseFlowSequence([&](Token name) { | |
269 | lib._allowedClients.emplace_back(name); | |
270 | }); | |
271 | } | |
272 | ||
273 | void parseReexportedDylibs(DynamicLibrary& lib) { | |
274 | if ( !hasOptionalToken("re-exports") ) | |
275 | return; | |
276 | parseFlowSequence([&](Token name) { | |
277 | lib._reexportedLibraries.emplace_back(name); | |
278 | }); | |
279 | } | |
280 | ||
281 | void parseSymbols(DynamicLibrary& lib) { | |
282 | if ( hasOptionalToken("symbols") ) { | |
283 | parseFlowSequence([&](Token name) { | |
284 | lib._symbols.emplace_back(name); | |
285 | }); | |
286 | } | |
287 | ||
288 | if ( hasOptionalToken("objc-classes") ) { | |
289 | parseFlowSequence([&](Token name) { | |
290 | lib._classes.emplace_back(name); | |
291 | }); | |
292 | } | |
293 | ||
294 | if ( hasOptionalToken("objc-ivars") ) { | |
295 | parseFlowSequence([&](Token name) { | |
296 | lib._ivars.emplace_back(name); | |
297 | }); | |
298 | } | |
299 | ||
300 | if ( hasOptionalToken("weak-def-symbols") ) { | |
301 | parseFlowSequence([&](Token name) { | |
302 | lib._weakDefSymbols.emplace_back(name); | |
303 | }); | |
304 | } | |
305 | ||
306 | if ( hasOptionalToken("thread-local-symbols") ) { | |
307 | parseFlowSequence([&](Token name) { | |
308 | lib._tlvSymbols.emplace_back(name); | |
309 | }); | |
310 | } | |
311 | } | |
312 | ||
313 | bool parseArchFlowSequence(Token archName) { | |
314 | expectToken("archs"); | |
315 | ||
316 | bool foundArch = false; | |
317 | parseFlowSequence([&](Token name) { | |
318 | if ( name == archName ) | |
319 | foundArch = true; | |
320 | }); | |
321 | ||
322 | return foundArch; | |
323 | } | |
324 | ||
325 | void parsePlatform(DynamicLibrary& lib) { | |
326 | expectToken("platform"); | |
327 | ||
328 | auto token = next(); | |
329 | if (token == "macosx") | |
330 | lib._platform = Options::kPlatformOSX; | |
331 | else if (token == "ios") | |
332 | lib._platform = Options::kPlatformiOS; | |
333 | else if (token == "watchos") | |
334 | lib._platform = Options::kPlatformWatchOS; | |
335 | #if SUPPORT_APPLE_TV | |
336 | else if (token == "tvos") | |
337 | lib._platform = Options::kPlatform_tvOS; | |
338 | #endif | |
339 | else | |
340 | lib._platform = Options::kPlatformUnknown; | |
341 | } | |
342 | ||
343 | void parseInstallName(DynamicLibrary& lib) { | |
344 | expectToken("install-name"); | |
345 | ||
346 | lib._installName = next(); | |
347 | if ( lib._installName.empty() ) | |
348 | throwf("no install name specified"); | |
349 | } | |
350 | ||
351 | void parseCurrentVersion(DynamicLibrary& lib) { | |
352 | if ( !hasOptionalToken("current-version") ) | |
353 | return; | |
354 | lib._currentVersion = parseVersionNumber32(next()); | |
355 | } | |
356 | ||
357 | void parseCompatibilityVersion(DynamicLibrary& lib) { | |
358 | if ( !hasOptionalToken("compatibility-version") ) | |
359 | return; | |
360 | lib._compatibilityVersion = parseVersionNumber32(next()); | |
361 | } | |
362 | ||
363 | void parseSwiftVersion(DynamicLibrary& lib) { | |
364 | if ( !hasOptionalToken("swift-version") ) | |
365 | return; | |
366 | auto token = next(); | |
367 | if ( token == "1.0" ) | |
368 | lib._swiftVersion = 1; | |
369 | else if ( token == "1.1" ) | |
370 | lib._swiftVersion = 2; | |
371 | else if ( token == "2.0" ) | |
372 | lib._swiftVersion = 3; | |
373 | else | |
374 | throwf("unsupported Swift ABI version: %s", token.str().c_str()); | |
375 | } | |
376 | ||
377 | void parseObjCConstraint(DynamicLibrary& lib) { | |
378 | if ( !hasOptionalToken("objc-constraint") ) | |
379 | return; | |
380 | auto token = next(); | |
381 | if ( token == "none" ) | |
382 | lib._objcConstraint = ld::File::objcConstraintNone; | |
383 | else if ( token == "retain_release" ) | |
384 | lib._objcConstraint = ld::File::objcConstraintRetainRelease; | |
385 | else if ( token == "retain_release_for_simulator" ) | |
386 | lib._objcConstraint = ld::File::objcConstraintRetainReleaseForSimulator; | |
387 | else if ( token == "retain_release_or_gc" ) | |
388 | lib._objcConstraint = ld::File::objcConstraintRetainReleaseOrGC; | |
389 | else if ( token == "gc" ) | |
390 | lib._objcConstraint = ld::File::objcConstraintGC; | |
391 | else | |
392 | throwf("unexpected token: %s", token.str().c_str()); | |
393 | } | |
394 | void parseExportsBlock(DynamicLibrary& lib, Token archName) { | |
395 | if ( !hasOptionalToken("exports") ) | |
396 | return; | |
397 | ||
398 | if ( !hasOptionalToken("-") ) | |
399 | return; | |
400 | ||
401 | while ( true ) { | |
402 | if ( !parseArchFlowSequence(archName) ) { | |
403 | Token token; | |
404 | while ( true ) { | |
405 | token = peek(); | |
406 | if ( token == "archs" || token == "..." || token.empty() ) | |
407 | break; | |
408 | next(); | |
409 | } | |
410 | if (token == "..." || token.empty() ) | |
411 | break; | |
412 | ||
413 | continue; | |
414 | } | |
415 | ||
416 | parseAllowedClients(lib); | |
417 | parseReexportedDylibs(lib); | |
418 | parseSymbols(lib); | |
419 | if ( !hasOptionalToken("-") ) | |
420 | break; | |
421 | } | |
422 | } | |
423 | ||
424 | void parseDocument(DynamicLibrary& lib, Token archName) { | |
425 | if ( !parseArchFlowSequence(archName) ) | |
426 | throwf("invalid arch"); | |
427 | ||
428 | parsePlatform(lib); | |
429 | parseInstallName(lib); | |
430 | parseCurrentVersion(lib); | |
431 | parseCompatibilityVersion(lib); | |
432 | parseSwiftVersion(lib); | |
433 | parseObjCConstraint(lib); | |
434 | parseExportsBlock(lib, archName); | |
435 | } | |
436 | ||
437 | public: | |
438 | TBDFile(const char* data, uint64_t size) : _tokenizer(data, size) {} | |
439 | ||
440 | DynamicLibrary parseFileForArch(Token archName) { | |
441 | _tokenizer.reset(); | |
442 | DynamicLibrary lib; | |
443 | expectToken("---"); | |
444 | parseDocument(lib, archName); | |
445 | expectToken("..."); | |
446 | return std::move(lib); | |
447 | } | |
448 | ||
449 | bool validForArch(Token archName) { | |
450 | _tokenizer.reset(); | |
451 | auto token = next(); | |
452 | if ( token != "---" ) | |
453 | return false; | |
454 | return parseArchFlowSequence(archName); | |
455 | } | |
456 | ||
457 | void dumpTokens() { | |
458 | _tokenizer.reset(); | |
459 | Token token; | |
460 | do { | |
461 | token = next(); | |
462 | printf("token: %s\n", token.str().c_str()); | |
463 | } while ( !token.empty() ); | |
464 | } | |
465 | }; | |
466 | ||
467 | } // end anonymous namespace | |
468 | ||
469 | namespace textstub { | |
470 | namespace dylib { | |
471 | ||
472 | // forward reference | |
473 | template <typename A> class File; | |
474 | ||
475 | ||
476 | // | |
477 | // An ExportAtom has no content. It exists so that the linker can track which imported | |
478 | // symbols came from which dynamic libraries. | |
479 | // | |
480 | template <typename A> | |
481 | class ExportAtom : public ld::Atom | |
482 | { | |
483 | public: | |
484 | ExportAtom(const File<A>& f, const char* nm, bool weakDef, bool tlv) | |
485 | : ld::Atom(f._importProxySection, ld::Atom::definitionProxy, | |
486 | (weakDef? ld::Atom::combineByName : ld::Atom::combineNever), | |
487 | ld::Atom::scopeLinkageUnit, | |
488 | (tlv ? ld::Atom::typeTLV : ld::Atom::typeUnclassified), | |
489 | symbolTableNotIn, false, false, false, ld::Atom::Alignment(0)), | |
490 | _file(f), _name(nm) {} | |
491 | // overrides of ld::Atom | |
492 | virtual const ld::File* file() const { return &_file; } | |
493 | virtual const char* name() const { return _name; } | |
494 | virtual uint64_t size() const { return 0; } | |
495 | virtual uint64_t objectAddress() const { return 0; } | |
496 | virtual void copyRawContent(uint8_t buffer[]) const { } | |
497 | virtual void setScope(Scope) { } | |
498 | ||
499 | protected: | |
500 | typedef typename A::P P; | |
501 | typedef typename A::P::uint_t pint_t; | |
502 | ||
503 | virtual ~ExportAtom() {} | |
504 | ||
505 | const File<A>& _file; | |
506 | const char* _name; | |
507 | }; | |
508 | ||
509 | ||
510 | // | |
511 | // The reader for a dylib extracts all exported symbols names from the memory-mapped | |
512 | // dylib, builds a hash table, then unmaps the file. This is an important memory | |
513 | // savings for large dylibs. | |
514 | // | |
515 | template <typename A> | |
516 | class File : public ld::dylib::File | |
517 | { | |
518 | public: | |
519 | static bool validFile(const uint8_t* fileContent, bool executableOrDylib); | |
520 | File(const uint8_t* fileContent, uint64_t fileLength, const char* path, | |
521 | time_t mTime, ld::File::Ordinal ordinal, bool linkingFlatNamespace, | |
522 | bool hoistImplicitPublicDylibs, Options::Platform platform, | |
523 | cpu_type_t cpuType, const char* archName, uint32_t linkMinOSVersion, | |
524 | bool allowSimToMacOSX, bool addVers, bool buildingForSimulator, | |
525 | bool logAllFiles, const char* installPath, bool indirectDylib); | |
526 | virtual ~File() {} | |
527 | ||
528 | // overrides of ld::File | |
529 | virtual bool forEachAtom(ld::File::AtomHandler&) const; | |
530 | virtual bool justInTimeforEachAtom(const char* name, ld::File::AtomHandler&) const; | |
531 | virtual ld::File::ObjcConstraint objCConstraint() const { return _objcConstraint; } | |
532 | virtual uint8_t swiftVersion() const { return _swiftVersion; } | |
533 | ||
534 | // overrides of ld::dylib::File | |
535 | virtual void processIndirectLibraries(ld::dylib::File::DylibHandler*, bool); | |
536 | virtual bool providedExportAtom() const { return _providedAtom; } | |
537 | virtual const char* parentUmbrella() const { return nullptr; } | |
538 | virtual const std::vector<const char*>* allowableClients() const { return _allowableClients.size() != 0 ? &_allowableClients : nullptr; } | |
539 | virtual bool hasWeakExternals() const { return _hasWeakExports; } | |
540 | virtual bool deadStrippable() const { return false; } | |
541 | virtual bool hasPublicInstallName() const{ return _hasPublicInstallName; } | |
542 | virtual bool hasWeakDefinition(const char* name) const; | |
543 | virtual bool allSymbolsAreWeakImported() const; | |
544 | virtual bool installPathVersionSpecific() const { return _installPathOverride; } | |
545 | // All text-based stubs are per definition AppExtensionSafe. | |
546 | virtual bool appExtensionSafe() const { return true; }; | |
547 | virtual ld::Bitcode* getBitcode() const { return _bitcode.get(); } | |
548 | ||
549 | ||
550 | protected: | |
551 | virtual void assertNoReExportCycles(ReExportChain*) const; | |
552 | ||
553 | private: | |
554 | typedef typename A::P P; | |
555 | typedef typename A::P::E E; | |
556 | typedef typename A::P::uint_t pint_t; | |
557 | ||
558 | friend class ExportAtom<A>; | |
559 | ||
560 | struct CStringHash { | |
561 | std::size_t operator()(const char* __s) const { | |
562 | unsigned long __h = 0; | |
563 | for ( ; *__s; ++__s) | |
564 | __h = 5 * __h + *__s; | |
565 | return size_t(__h); | |
566 | }; | |
567 | }; | |
568 | struct AtomAndWeak { ld::Atom* atom; bool weakDef; bool tlv; }; | |
569 | typedef std::unordered_map<const char*, AtomAndWeak, ld::CStringHash, ld::CStringEquals> NameToAtomMap; | |
570 | typedef std::unordered_set<const char*, CStringHash, ld::CStringEquals> NameSet; | |
571 | ||
572 | struct Dependent { const char* path; File<A>* dylib; }; | |
573 | ||
574 | virtual std::pair<bool, bool> hasWeakDefinitionImpl(const char* name) const; | |
575 | virtual bool containsOrReExports(const char* name, bool& weakDef, bool& tlv, uint64_t& address) const; | |
576 | ||
577 | void buildExportHashTable(const DynamicLibrary &lib); | |
578 | bool isPublicLocation(const char* pth); | |
579 | bool wrongOS() { return _wrongOS; } | |
580 | void addSymbol(const char* name, bool weak, bool tlv); | |
581 | ||
582 | const Options::Platform _platform; | |
583 | cpu_type_t _cpuType; | |
584 | const uint32_t _linkMinOSVersion; | |
585 | const bool _allowSimToMacOSXLinking; | |
586 | const bool _addVersionLoadCommand; | |
587 | bool _linkingFlat; | |
588 | bool _implicitlyLinkPublicDylibs; | |
589 | ld::File::ObjcConstraint _objcConstraint; | |
590 | uint8_t _swiftVersion; | |
591 | ld::Section _importProxySection; | |
592 | ld::Section _flatDummySection; | |
593 | std::vector<Dependent> _dependentDylibs; | |
594 | std::vector<const char*> _allowableClients; | |
595 | mutable NameToAtomMap _atoms; | |
596 | NameSet _ignoreExports; | |
597 | bool _noRexports; | |
598 | bool _hasWeakExports; | |
599 | bool _hasPublicInstallName; | |
600 | mutable bool _providedAtom; | |
601 | bool _wrongOS; | |
602 | bool _installPathOverride; | |
603 | bool _indirectDylibsProcessed; | |
604 | std::unique_ptr<ld::Bitcode> _bitcode; | |
605 | static bool _s_logHashtable; | |
606 | }; | |
607 | ||
608 | template <typename A> | |
609 | bool File<A>::_s_logHashtable = false; | |
610 | ||
611 | ||
612 | template <typename A> | |
613 | File<A>::File(const uint8_t* fileContent, uint64_t fileLength, const char* path, time_t mTime, | |
614 | ld::File::Ordinal ord, bool linkingFlatNamespace, bool hoistImplicitPublicDylibs, | |
615 | Options::Platform platform, cpu_type_t cpuType, const char* archName, | |
616 | uint32_t linkMinOSVersion, bool allowSimToMacOSX, bool addVers, | |
617 | bool buildingForSimulator, bool logAllFiles, const char* targetInstallPath, | |
618 | bool indirectDylib) | |
619 | : ld::dylib::File(strdup(path), mTime, ord), _platform(platform), _cpuType(cpuType), | |
620 | _linkMinOSVersion(linkMinOSVersion), _allowSimToMacOSXLinking(allowSimToMacOSX), | |
621 | _addVersionLoadCommand(addVers), _linkingFlat(linkingFlatNamespace), | |
622 | _implicitlyLinkPublicDylibs(hoistImplicitPublicDylibs), | |
623 | _objcConstraint(ld::File::objcConstraintNone), _swiftVersion(0), | |
624 | _importProxySection("__TEXT", "__import", ld::Section::typeImportProxies, true), | |
625 | _flatDummySection("__LINKEDIT", "__flat_dummy", ld::Section::typeLinkEdit, true), | |
626 | _noRexports(false), _hasWeakExports(false), | |
627 | _hasPublicInstallName(false), _providedAtom(false), _wrongOS(false), | |
628 | _installPathOverride(false), _indirectDylibsProcessed(false), | |
629 | _bitcode(new ld::Bitcode(nullptr, 0)) | |
630 | { | |
631 | // write out path for -t option | |
632 | if ( logAllFiles ) | |
633 | printf("%s\n", path); | |
634 | ||
635 | TBDFile stub((const char*)fileContent, fileLength); | |
636 | auto lib = stub.parseFileForArch(archName); | |
637 | ||
638 | _noRexports = lib._reexportedLibraries.empty(); | |
639 | _hasWeakExports = !lib._weakDefSymbols.empty(); | |
640 | _dylibInstallPath = strdup(lib._installName.str().c_str()); | |
641 | _dylibCurrentVersion = lib._currentVersion; | |
642 | _dylibCompatibilityVersion = lib._compatibilityVersion; | |
643 | _swiftVersion = lib._swiftVersion; | |
644 | _objcConstraint = lib._objcConstraint; | |
645 | _hasPublicInstallName = isPublicLocation(_dylibInstallPath); | |
646 | ||
647 | for (auto &client : lib._allowedClients) | |
648 | _allowableClients.push_back(strdup(client.str().c_str())); | |
649 | ||
650 | // <rdar://problem/20659505> [TAPI] Don't hoist "public" (in /usr/lib/) dylibs that should not be directly linked | |
651 | if ( !_allowableClients.empty() ) | |
652 | _hasPublicInstallName = false; | |
653 | ||
654 | if ( (lib._platform != platform) && (platform != Options::kPlatformUnknown) ) { | |
655 | _wrongOS = true; | |
656 | if ( _addVersionLoadCommand && !indirectDylib ) { | |
657 | if ( buildingForSimulator ) { | |
658 | if ( !_allowSimToMacOSXLinking ) | |
659 | throwf("building for %s simulator, but linking against dylib built for %s (%s).", | |
660 | Options::platformName(platform), Options::platformName(lib._platform), path); | |
661 | } else { | |
662 | throwf("building for %s, but linking against dylib built for %s (%s).", | |
663 | Options::platformName(platform), Options::platformName(lib._platform), path); | |
664 | } | |
665 | } | |
666 | } | |
667 | ||
668 | _dependentDylibs.reserve(lib._reexportedLibraries.size()); | |
669 | for ( auto& reexport : lib._reexportedLibraries ) { | |
670 | Dependent entry; | |
671 | entry.path = strdup(reexport.str().c_str()); | |
672 | entry.dylib = nullptr; | |
673 | if ( (targetInstallPath == nullptr) || (strcmp(targetInstallPath, entry.path) != 0) ) | |
674 | _dependentDylibs.push_back(entry); | |
675 | } | |
676 | ||
677 | // build hash table | |
678 | buildExportHashTable(lib); | |
679 | ||
680 | munmap((caddr_t)fileContent, fileLength); | |
681 | } | |
682 | ||
683 | template <typename A> | |
684 | void File<A>::buildExportHashTable(const DynamicLibrary& lib) { | |
685 | if ( _s_logHashtable ) | |
686 | fprintf(stderr, "ld: building hashtable from text-stub info in %s\n", this->path()); | |
687 | ||
688 | for (auto &sym : lib._symbols) | |
689 | addSymbol(sym.str().c_str(), /*weak=*/false, /*tlv=*/false); | |
690 | ||
691 | #if SUPPORT_ARCH_i386 | |
692 | if (_platform == Options::kPlatformOSX && _cpuType == CPU_TYPE_I386) { | |
693 | for (auto &sym : lib._classes) | |
694 | addSymbol((".objc_class_name" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); | |
695 | } else { | |
696 | for (auto &sym : lib._classes) { | |
697 | addSymbol(("_OBJC_CLASS_$" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); | |
698 | addSymbol(("_OBJC_METACLASS_$" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); | |
699 | } | |
700 | } | |
701 | #else | |
702 | for (auto &sym : lib._classes) { | |
703 | addSymbol(("_OBJC_CLASS_$" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); | |
704 | addSymbol(("_OBJC_METACLASS_$" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); | |
705 | } | |
706 | #endif | |
707 | ||
708 | for (auto &sym : lib._ivars) | |
709 | addSymbol(("_OBJC_IVAR_$" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); | |
710 | ||
711 | for (auto &sym : lib._weakDefSymbols) | |
712 | addSymbol(sym.str().c_str(), /*weak=*/true, /*tlv=*/false); | |
713 | ||
714 | for (auto &sym : lib._tlvSymbols) | |
715 | addSymbol(sym.str().c_str(), /*weak=*/false, /*tlv=*/true); | |
716 | } | |
717 | ||
718 | ||
719 | template <typename A> | |
720 | void File<A>::addSymbol(const char* name, bool weakDef, bool tlv) | |
721 | { | |
722 | // symbols that start with $ld$ are meta-data to the static linker | |
723 | // <rdar://problem/5182537> need way for ld and dyld to see different exported symbols in a dylib | |
724 | if ( strncmp(name, "$ld$", 4) == 0 ) { | |
725 | // $ld$ <action> $ <condition> $ <symbol-name> | |
726 | const char* symAction = &name[4]; | |
727 | const char* symCond = strchr(symAction, '$'); | |
728 | if ( symCond != nullptr ) { | |
729 | char curOSVers[16]; | |
730 | sprintf(curOSVers, "$os%d.%d$", (_linkMinOSVersion >> 16), ((_linkMinOSVersion >> 8) & 0xFF)); | |
731 | if ( strncmp(symCond, curOSVers, strlen(curOSVers)) == 0 ) { | |
732 | const char* symName = strchr(&symCond[1], '$'); | |
733 | if ( symName != nullptr ) { | |
734 | ++symName; | |
735 | if ( strncmp(symAction, "hide$", 5) == 0 ) { | |
736 | if ( _s_logHashtable ) | |
737 | fprintf(stderr, " adding %s to ignore set for %s\n", symName, this->path()); | |
738 | _ignoreExports.insert(strdup(symName)); | |
739 | return; | |
740 | } | |
741 | else if ( strncmp(symAction, "add$", 4) == 0 ) { | |
742 | this->addSymbol(symName, weakDef, false); | |
743 | return; | |
744 | } | |
745 | else if ( strncmp(symAction, "install_name$", 13) == 0 ) { | |
746 | _dylibInstallPath = strdup(symName); | |
747 | _installPathOverride = true; | |
748 | return; | |
749 | } | |
750 | else if ( strncmp(symAction, "compatibility_version$", 22) == 0 ) { | |
751 | _dylibCompatibilityVersion = parseVersionNumber32(symName); | |
752 | return; | |
753 | } | |
754 | else { | |
755 | warning("bad symbol action: %s in dylib %s", name, this->path()); | |
756 | } | |
757 | } | |
758 | } | |
759 | } | |
760 | else { | |
761 | warning("bad symbol condition: %s in dylib %s", name, this->path()); | |
762 | } | |
763 | } | |
764 | ||
765 | // add symbol as possible export if we are not supposed to ignore it | |
766 | if ( _ignoreExports.count(name) == 0 ) { | |
767 | AtomAndWeak bucket; | |
768 | bucket.atom = nullptr; | |
769 | bucket.weakDef = weakDef; | |
770 | bucket.tlv = tlv; | |
771 | if ( _s_logHashtable ) | |
772 | fprintf(stderr, " adding %s to hash table for %s\n", name, this->path()); | |
773 | _atoms[strdup(name)] = bucket; | |
774 | } | |
775 | } | |
776 | ||
777 | ||
778 | template <typename A> | |
779 | bool File<A>::forEachAtom(ld::File::AtomHandler& handler) const | |
780 | { | |
781 | handler.doFile(*this); | |
782 | return false; | |
783 | } | |
784 | ||
785 | ||
786 | template <typename A> | |
787 | std::pair<bool, bool> File<A>::hasWeakDefinitionImpl(const char* name) const | |
788 | { | |
789 | const auto pos = _atoms.find(name); | |
790 | if ( pos != _atoms.end() ) | |
791 | return std::make_pair(true, pos->second.weakDef); | |
792 | ||
793 | // look in children that I re-export | |
794 | for (const auto &dep : _dependentDylibs) { | |
795 | auto ret = dep.dylib->hasWeakDefinitionImpl(name); | |
796 | if ( ret.first ) | |
797 | return ret; | |
798 | } | |
799 | return std::make_pair(false, false); | |
800 | } | |
801 | ||
802 | ||
803 | template <typename A> | |
804 | bool File<A>::hasWeakDefinition(const char* name) const | |
805 | { | |
806 | // if supposed to ignore this export, then pretend I don't have it | |
807 | if ( _ignoreExports.count(name) != 0 ) | |
808 | return false; | |
809 | ||
810 | return hasWeakDefinitionImpl(name).second; | |
811 | } | |
812 | ||
813 | ||
814 | // <rdar://problem/5529626> If only weak_import symbols are used, linker should use LD_LOAD_WEAK_DYLIB | |
815 | template <typename A> | |
816 | bool File<A>::allSymbolsAreWeakImported() const | |
817 | { | |
818 | bool foundNonWeakImport = false; | |
819 | bool foundWeakImport = false; | |
820 | for (const auto &it : _atoms) { | |
821 | const ld::Atom* atom = it.second.atom; | |
822 | if ( atom != nullptr ) { | |
823 | if ( atom->weakImported() ) | |
824 | foundWeakImport = true; | |
825 | else | |
826 | foundNonWeakImport = true; | |
827 | } | |
828 | } | |
829 | ||
830 | // don't automatically weak link dylib with no imports | |
831 | // so at least one weak import symbol and no non-weak-imported symbols must be found | |
832 | return foundWeakImport && !foundNonWeakImport; | |
833 | } | |
834 | ||
835 | ||
836 | template <typename A> | |
837 | bool File<A>::containsOrReExports(const char* name, bool& weakDef, bool& tlv, uint64_t& addr) const | |
838 | { | |
839 | if ( _ignoreExports.count(name) != 0 ) | |
840 | return false; | |
841 | ||
842 | // check myself | |
843 | const auto pos = _atoms.find(name); | |
844 | if ( pos != _atoms.end() ) { | |
845 | weakDef = pos->second.weakDef; | |
846 | tlv = pos->second.tlv; | |
847 | addr = 0; | |
848 | return true; | |
849 | } | |
850 | ||
851 | // check dylibs I re-export | |
852 | for (const auto& lib : _dependentDylibs) { | |
853 | if ( !lib.dylib->implicitlyLinked() ) { | |
854 | if ( lib.dylib->containsOrReExports(name, weakDef, tlv, addr) ) | |
855 | return true; | |
856 | } | |
857 | } | |
858 | ||
859 | return false; | |
860 | } | |
861 | ||
862 | ||
863 | template <typename A> | |
864 | bool File<A>::justInTimeforEachAtom(const char* name, ld::File::AtomHandler& handler) const | |
865 | { | |
866 | // if supposed to ignore this export, then pretend I don't have it | |
867 | if ( _ignoreExports.count(name) != 0 ) | |
868 | return false; | |
869 | ||
870 | ||
871 | AtomAndWeak bucket; | |
872 | uint64_t addr; | |
873 | if ( this->containsOrReExports(name, bucket.weakDef, bucket.tlv, addr) ) { | |
874 | bucket.atom = new ExportAtom<A>(*this, name, bucket.weakDef, bucket.tlv); | |
875 | _atoms[name] = bucket; | |
876 | _providedAtom = true; | |
877 | if ( _s_logHashtable ) | |
878 | fprintf(stderr, "getJustInTimeAtomsFor: %s found in %s\n", name, this->path()); | |
879 | // call handler with new export atom | |
880 | handler.doAtom(*bucket.atom); | |
881 | return true; | |
882 | } | |
883 | ||
884 | return false; | |
885 | } | |
886 | ||
887 | ||
888 | ||
889 | template <typename A> | |
890 | bool File<A>::isPublicLocation(const char* path) | |
891 | { | |
892 | // -no_implicit_dylibs disables this optimization | |
893 | if ( ! _implicitlyLinkPublicDylibs ) | |
894 | return false; | |
895 | ||
896 | // /usr/lib is a public location | |
897 | if ( (strncmp(path, "/usr/lib/", 9) == 0) && (strchr(&path[9], '/') == nullptr) ) | |
898 | return true; | |
899 | ||
900 | // /System/Library/Frameworks/ is a public location | |
901 | if ( strncmp(path, "/System/Library/Frameworks/", 27) == 0 ) { | |
902 | const char* frameworkDot = strchr(&path[27], '.'); | |
903 | // but only top level framework | |
904 | // /System/Library/Frameworks/Foo.framework/Versions/A/Foo ==> true | |
905 | // /System/Library/Frameworks/Foo.framework/Resources/libBar.dylib ==> false | |
906 | // /System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/Bar ==> false | |
907 | // /System/Library/Frameworks/Foo.framework/Frameworks/Xfoo.framework/XFoo ==> false | |
908 | if ( frameworkDot != nullptr ) { | |
909 | int frameworkNameLen = frameworkDot - &path[27]; | |
910 | if ( strncmp(&path[strlen(path)-frameworkNameLen-1], &path[26], frameworkNameLen+1) == 0 ) | |
911 | return true; | |
912 | } | |
913 | } | |
914 | ||
915 | return false; | |
916 | } | |
917 | ||
918 | template <typename A> | |
919 | void File<A>::processIndirectLibraries(ld::dylib::File::DylibHandler* handler, bool addImplicitDylibs) | |
920 | { | |
921 | // only do this once | |
922 | if ( _indirectDylibsProcessed ) | |
923 | return; | |
924 | ||
925 | const static bool log = false; | |
926 | if ( log ) fprintf(stderr, "processIndirectLibraries(%s)\n", this->installPath()); | |
927 | if ( _linkingFlat ) { | |
928 | for (auto& lib : _dependentDylibs) { | |
929 | lib.dylib = (File<A>*)handler->findDylib(lib.path, this->path()); | |
930 | } | |
931 | } | |
932 | else if ( _noRexports ) { | |
933 | // MH_NO_REEXPORTED_DYLIBS bit set, then nothing to do | |
934 | } | |
935 | else { | |
936 | // two-level, might have re-exports | |
937 | for (auto& lib : _dependentDylibs) { | |
938 | if ( log ) | |
939 | fprintf(stderr, "processIndirectLibraries() parent=%s, child=%s\n", this->installPath(), lib.path); | |
940 | // a LC_REEXPORT_DYLIB, LC_SUB_UMBRELLA or LC_SUB_LIBRARY says we re-export this child | |
941 | lib.dylib = (File<A>*)handler->findDylib(lib.path, this->path()); | |
942 | if ( lib.dylib->hasPublicInstallName() && !lib.dylib->wrongOS() ) { | |
943 | // promote this child to be automatically added as a direct dependent if this already is | |
944 | if ( (this->explicitlyLinked() || this->implicitlyLinked()) && (strcmp(lib.path, lib.dylib->installPath()) == 0) ) { | |
945 | if ( log ) | |
946 | fprintf(stderr, "processIndirectLibraries() implicitly linking %s\n", lib.dylib->installPath()); | |
947 | lib.dylib->setImplicitlyLinked(); | |
948 | } | |
949 | else if ( lib.dylib->explicitlyLinked() || lib.dylib->implicitlyLinked() ) { | |
950 | if ( log ) | |
951 | fprintf(stderr, "processIndirectLibraries() parent is not directly linked, but child is, so no need to re-export child\n"); | |
952 | } else { | |
953 | if ( log ) | |
954 | fprintf(stderr, "processIndirectLibraries() parent is not directly linked, so parent=%s will re-export child=%s\n", this->installPath(), lib.path); | |
955 | } | |
956 | } else { | |
957 | // add all child's symbols to me | |
958 | if ( log ) | |
959 | fprintf(stderr, "processIndirectLibraries() child is not public, so parent=%s will re-export child=%s\n", this->installPath(), lib.path); | |
960 | } | |
961 | } | |
962 | } | |
963 | ||
964 | // check for re-export cycles | |
965 | ReExportChain chain; | |
966 | chain.prev = nullptr; | |
967 | chain.file = this; | |
968 | this->assertNoReExportCycles(&chain); | |
969 | ||
970 | _indirectDylibsProcessed = true; | |
971 | } | |
972 | ||
973 | template <typename A> | |
974 | void File<A>::assertNoReExportCycles(ReExportChain* prev) const | |
975 | { | |
976 | // recursively check my re-exported dylibs | |
977 | ReExportChain chain; | |
978 | chain.prev = prev; | |
979 | chain.file = this; | |
980 | for (const auto& dep : _dependentDylibs) { | |
981 | ld::File* child = dep.dylib; | |
982 | // check child is not already in chain | |
983 | for (ReExportChain* p = prev; p != nullptr; p = p->prev) { | |
984 | if ( p->file == child ) | |
985 | throwf("cycle in dylib re-exports with %s and %s", child->path(), this->path()); | |
986 | } | |
987 | if ( dep.dylib != nullptr ) | |
988 | dep.dylib->assertNoReExportCycles(&chain); | |
989 | } | |
990 | } | |
991 | ||
992 | ||
993 | template <typename A> | |
994 | class Parser | |
995 | { | |
996 | public: | |
997 | typedef typename A::P P; | |
998 | ||
999 | static bool validFile(const uint8_t* fileContent, uint64_t fileLength, const std::string &path, const char* archName); | |
1000 | static ld::dylib::File* parse(const uint8_t* fileContent, uint64_t fileLength, const char* path, | |
1001 | time_t mTime, ld::File::Ordinal ordinal, const Options& opts, | |
1002 | bool indirectDylib) { | |
1003 | return new File<A>(fileContent, fileLength, path, mTime, ordinal, | |
1004 | opts.flatNamespace(), | |
1005 | opts.implicitlyLinkIndirectPublicDylibs(), | |
1006 | opts.platform(), | |
1007 | opts.architecture(), | |
1008 | opts.architectureName(), | |
1009 | opts.minOSversion(), | |
1010 | opts.allowSimulatorToLinkWithMacOSX(), | |
1011 | opts.addVersionLoadCommand(), | |
1012 | opts.targetIOSSimulator(), | |
1013 | opts.logAllFiles(), | |
1014 | opts.installPath(), | |
1015 | indirectDylib); | |
1016 | } | |
1017 | }; | |
1018 | ||
1019 | template <typename A> | |
1020 | bool Parser<A>::validFile(const uint8_t* fileContent, uint64_t fileLength, const std::string &path, const char* archName) | |
1021 | { | |
1022 | if ( path.find(".tbd", path.size()-4) == std::string::npos ) | |
1023 | return false; | |
1024 | ||
1025 | TBDFile stub((const char*)fileContent, fileLength); | |
1026 | if ( !stub.validForArch(archName) ) | |
1027 | throwf("missing required architecture %s in file %s", archName, path.c_str()); | |
1028 | ||
1029 | return true; | |
1030 | } | |
1031 | ||
1032 | // | |
1033 | // main function used by linker to instantiate ld::Files | |
1034 | // | |
1035 | ld::dylib::File* parse(const uint8_t* fileContent, uint64_t fileLength, const char* path, | |
1036 | time_t modTime, const Options& opts, ld::File::Ordinal ordinal, | |
1037 | bool bundleLoader, bool indirectDylib) | |
1038 | { | |
1039 | switch ( opts.architecture() ) { | |
1040 | #if SUPPORT_ARCH_x86_64 | |
1041 | case CPU_TYPE_X86_64: | |
1042 | if ( Parser<x86_64>::validFile(fileContent, fileLength, path, opts.architectureName()) ) | |
1043 | return Parser<x86_64>::parse(fileContent, fileLength, path, modTime, ordinal, opts, indirectDylib); | |
1044 | break; | |
1045 | #endif | |
1046 | #if SUPPORT_ARCH_i386 | |
1047 | case CPU_TYPE_I386: | |
1048 | if ( Parser<x86>::validFile(fileContent, fileLength, path, opts.architectureName()) ) | |
1049 | return Parser<x86>::parse(fileContent, fileLength, path, modTime, ordinal, opts, indirectDylib); | |
1050 | break; | |
1051 | #endif | |
1052 | #if SUPPORT_ARCH_arm_any | |
1053 | case CPU_TYPE_ARM: | |
1054 | if ( Parser<arm>::validFile(fileContent, fileLength, path, opts.architectureName()) ) | |
1055 | return Parser<arm>::parse(fileContent, fileLength, path, modTime, ordinal, opts, indirectDylib); | |
1056 | break; | |
1057 | #endif | |
1058 | #if SUPPORT_ARCH_arm64 | |
1059 | case CPU_TYPE_ARM64: | |
1060 | if ( Parser<arm64>::validFile(fileContent, fileLength, path, opts.architectureName()) ) | |
1061 | return Parser<arm64>::parse(fileContent, fileLength, path, modTime, ordinal, opts, indirectDylib); | |
1062 | break; | |
1063 | #endif | |
1064 | } | |
1065 | return nullptr; | |
1066 | } | |
1067 | ||
1068 | ||
1069 | } // namespace dylib | |
1070 | } // namespace textstub | |
1071 | ||
1072 |