]> git.saurik.com Git - apple/ld64.git/blob - src/ld/parsers/generic_dylib_file.hpp
301074f57d3e353b24b81d9fd8e6d8e7200c5666
[apple/ld64.git] / src / ld / parsers / generic_dylib_file.hpp
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 #ifndef __GENERIC_DYLIB_FILE_H__
26 #define __GENERIC_DYLIB_FILE_H__
27
28 #include "ld.hpp"
29 #include "Options.h"
30 #include <unordered_map>
31 #include <unordered_set>
32
33 namespace generic {
34 namespace dylib {
35
36 // forward reference
37 template <typename A> class File;
38
39 //
40 // An ExportAtom has no content. It exists so that the linker can track which
41 // imported symbols came from which dynamic libraries.
42 //
43 template <typename A>
44 class ExportAtom final : public ld::Atom
45 {
46 public:
47 ExportAtom(const File<A>& f, const char* nm, bool weakDef, bool tlv,
48 typename A::P::uint_t address)
49 : ld::Atom(f._importProxySection, ld::Atom::definitionProxy,
50 (weakDef ? ld::Atom::combineByName : ld::Atom::combineNever),
51 ld::Atom::scopeLinkageUnit,
52 (tlv ? ld::Atom::typeTLV : ld::Atom::typeUnclassified),
53 symbolTableNotIn, false, false, false, ld::Atom::Alignment(0)),
54 _file(f),
55 _name(nm),
56 _address(address)
57 {}
58
59 // overrides of ld::Atom
60 virtual const ld::File* file() const override final { return &_file; }
61 virtual const char* name() const override final { return _name; }
62 virtual uint64_t size() const override final { return 0; }
63 virtual uint64_t objectAddress() const override final { return _address; }
64 virtual void copyRawContent(uint8_t buffer[]) const override final { }
65
66 virtual void setScope(Scope) { }
67
68 private:
69 using pint_t = typename A::P::uint_t;
70
71 virtual ~ExportAtom() {}
72
73 const File<A>& _file;
74 const char* _name;
75 pint_t _address;
76 };
77
78
79 //
80 // An ImportAtom has no content. It exists so that when linking a main executable flat-namespace
81 // the imports of all flat dylibs are checked
82 //
83 template <typename A>
84 class ImportAtom final : public ld::Atom
85 {
86 public:
87 ImportAtom(File<A>& f, std::vector<const char*>& imports);
88
89 // overrides of ld::Atom
90 virtual ld::File* file() const override final { return &_file; }
91 virtual const char* name() const override final { return "import-atom"; }
92 virtual uint64_t size() const override final { return 0; }
93 virtual uint64_t objectAddress() const override final { return 0; }
94 virtual ld::Fixup::iterator fixupsBegin() const override final { return &_undefs[0]; }
95 virtual ld::Fixup::iterator fixupsEnd() const override final { return &_undefs[_undefs.size()]; }
96 virtual void copyRawContent(uint8_t buffer[]) const override final { }
97
98 virtual void setScope(Scope) { }
99
100 private:
101 virtual ~ImportAtom() {}
102
103 File<A>& _file;
104 mutable std::vector<ld::Fixup> _undefs;
105 };
106
107 template <typename A>
108 ImportAtom<A>::ImportAtom(File<A>& f, std::vector<const char*>& imports)
109 : ld::Atom(f._flatDummySection, ld::Atom::definitionRegular, ld::Atom::combineNever,
110 ld::Atom::scopeTranslationUnit, ld::Atom::typeUnclassified, symbolTableNotIn, false,
111 false, false, ld::Atom::Alignment(0)),
112 _file(f)
113 {
114 for(auto *name : imports)
115 _undefs.emplace_back(0, ld::Fixup::k1of1, ld::Fixup::kindNone, false, strdup(name));
116 }
117
118 //
119 // A generic representation for the dynamic library files we support (Mach-O and text-based stubs).
120 // Common state and functionality is consolidated in this class.
121 //
122 template <typename A>
123 class File : public ld::dylib::File
124 {
125 public:
126 File(const char* path, time_t mTime, ld::File::Ordinal ordinal, Options::Platform platform,
127 uint32_t linkMinOSVersion, bool linkingFlatNamespace, bool hoistImplicitPublicDylibs,
128 bool allowSimToMacOSX, bool addVers);
129
130 // overrides of ld::File
131 virtual bool forEachAtom(ld::File::AtomHandler&) const override final;
132 virtual bool justInTimeforEachAtom(const char* name, ld::File::AtomHandler&) const override final;
133 virtual ld::File::ObjcConstraint objCConstraint() const override final { return _objcConstraint; }
134 virtual uint8_t swiftVersion() const override final { return _swiftVersion; }
135 virtual uint32_t minOSVersion() const override final { return _minVersionInDylib; }
136 virtual uint32_t platformLoadCommand() const override final { return _platformInDylib; }
137 virtual ld::Bitcode* getBitcode() const override final { return _bitcode.get(); }
138
139
140 // overrides of ld::dylib::File
141 virtual void processIndirectLibraries(ld::dylib::File::DylibHandler*, bool addImplicitDylibs) override final;
142 virtual bool providedExportAtom() const override final { return _providedAtom; }
143 virtual const char* parentUmbrella() const override final { return _parentUmbrella; }
144 virtual const std::vector<const char*>* allowableClients() const override final { return _allowableClients.empty() ? nullptr : &_allowableClients; }
145 virtual bool hasWeakExternals() const override final { return _hasWeakExports; }
146 virtual bool deadStrippable() const override final { return _deadStrippable; }
147 virtual bool hasWeakDefinition(const char* name) const override final;
148 virtual bool hasPublicInstallName() const override final { return _hasPublicInstallName; }
149 virtual bool allSymbolsAreWeakImported() const override final;
150 virtual bool installPathVersionSpecific() const override final { return _installPathOverride; }
151 virtual bool appExtensionSafe() const override final { return _appExtensionSafe; };
152
153
154 bool wrongOS() const { return _wrongOS; }
155
156 private:
157 using pint_t = typename A::P::uint_t;
158
159 friend class ExportAtom<A>;
160 friend class ImportAtom<A>;
161
162 struct CStringHash {
163 std::size_t operator()(const char* __s) const {
164 unsigned long __h = 0;
165 for ( ; *__s; ++__s)
166 __h = 5 * __h + *__s;
167 return size_t(__h);
168 };
169 };
170
171 protected:
172 struct AtomAndWeak { ld::Atom* atom; bool weakDef; bool tlv; pint_t address; };
173 struct Dependent {
174 const char* path;
175 File<A>* dylib;
176 bool reExport;
177
178 Dependent(const char* path, bool reExport)
179 : path(path), dylib(nullptr), reExport(reExport) {}
180 };
181 struct ReExportChain { ReExportChain* prev; const File* file; };
182
183 private:
184 using NameToAtomMap = std::unordered_map<const char*, AtomAndWeak, ld::CStringHash, ld::CStringEquals>;
185 using NameSet = std::unordered_set<const char*, CStringHash, ld::CStringEquals>;
186
187 std::pair<bool, bool> hasWeakDefinitionImpl(const char* name) const;
188 bool containsOrReExports(const char* name, bool& weakDef, bool& tlv, pint_t& addr) const;
189 void assertNoReExportCycles(ReExportChain*) const;
190
191 protected:
192 bool isPublicLocation(const char* path) const;
193 void addSymbol(const char* name, bool weak = false, bool tlv = false, pint_t address = 0);
194
195 private:
196 ld::Section _importProxySection;
197 ld::Section _flatDummySection;
198 mutable bool _providedAtom;
199 bool _indirectDylibsProcessed;
200
201 protected:
202 mutable NameToAtomMap _atoms;
203 NameSet _ignoreExports;
204 std::vector<Dependent> _dependentDylibs;
205 ImportAtom<A>* _importAtom;
206 std::vector<const char*> _allowableClients;
207 const char* _parentUmbrella;
208 std::unique_ptr<ld::Bitcode> _bitcode;
209 const Options::Platform _platform;
210 ld::File::ObjcConstraint _objcConstraint;
211 const uint32_t _linkMinOSVersion;
212 uint32_t _minVersionInDylib;
213 uint32_t _platformInDylib;
214 uint8_t _swiftVersion;
215 bool _wrongOS;
216 bool _linkingFlat;
217 bool _noRexports;
218 bool _explictReExportFound;
219 bool _implicitlyLinkPublicDylibs;
220 bool _installPathOverride;
221 bool _hasWeakExports;
222 bool _deadStrippable;
223 bool _hasPublicInstallName;
224 bool _appExtensionSafe;
225
226 const bool _allowSimToMacOSXLinking;
227 const bool _addVersionLoadCommand;
228
229 static bool _s_logHashtable;
230 };
231
232 template <typename A>
233 bool File<A>::_s_logHashtable = false;
234
235 template <typename A>
236 File<A>::File(const char* path, time_t mTime, ld::File::Ordinal ord, Options::Platform platform,
237 uint32_t linkMinOSVersion, bool linkingFlatNamespace,
238 bool hoistImplicitPublicDylibs,
239 bool allowSimToMacOSX, bool addVers)
240 : ld::dylib::File(path, mTime, ord),
241 _importProxySection("__TEXT", "__import", ld::Section::typeImportProxies, true),
242 _flatDummySection("__LINKEDIT", "__flat_dummy", ld::Section::typeLinkEdit, true),
243 _providedAtom(false),
244 _indirectDylibsProcessed(false),
245 _importAtom(nullptr),
246 _parentUmbrella(nullptr),
247 _platform(platform),
248 _objcConstraint(ld::File::objcConstraintNone),
249 _linkMinOSVersion(linkMinOSVersion),
250 _minVersionInDylib(0),
251 _platformInDylib(Options::kPlatformUnknown),
252 _swiftVersion(0),
253 _wrongOS(false),
254 _linkingFlat(linkingFlatNamespace),
255 _noRexports(false),
256 _explictReExportFound(false),
257 _implicitlyLinkPublicDylibs(hoistImplicitPublicDylibs),
258 _installPathOverride(false),
259 _hasWeakExports(false),
260 _deadStrippable(false),
261 _hasPublicInstallName(false),
262 _appExtensionSafe(false),
263 _allowSimToMacOSXLinking(allowSimToMacOSX),
264 _addVersionLoadCommand(addVers)
265 {
266 }
267
268 template <typename A>
269 std::pair<bool, bool> File<A>::hasWeakDefinitionImpl(const char* name) const
270 {
271 const auto pos = _atoms.find(name);
272 if ( pos != this->_atoms.end() )
273 return std::make_pair(true, pos->second.weakDef);
274
275 // look in re-exported libraries.
276 for (const auto &dep : _dependentDylibs) {
277 if ( dep.reExport ) {
278 auto ret = dep.dylib->hasWeakDefinitionImpl(name);
279 if ( ret.first )
280 return ret;
281 }
282 }
283 return std::make_pair(false, false);
284 }
285
286 template <typename A>
287 bool File<A>::hasWeakDefinition(const char* name) const
288 {
289 // If we are supposed to ignore this export, then pretend we don't have it.
290 if ( _ignoreExports.count(name) != 0 )
291 return false;
292
293 return hasWeakDefinitionImpl(name).second;
294 }
295
296 template <typename A>
297 bool File<A>::containsOrReExports(const char* name, bool& weakDef, bool& tlv, pint_t& addr) const
298 {
299 if ( _ignoreExports.count(name) != 0 )
300 return false;
301
302 // check myself
303 const auto pos = _atoms.find(name);
304 if ( pos != _atoms.end() ) {
305 weakDef = pos->second.weakDef;
306 tlv = pos->second.tlv;
307 addr = pos->second.address;
308 return true;
309 }
310
311 // check dylibs I re-export
312 for (const auto& dep : _dependentDylibs) {
313 if ( dep.reExport && !dep.dylib->implicitlyLinked() ) {
314 if ( dep.dylib->containsOrReExports(name, weakDef, tlv, addr) )
315 return true;
316 }
317 }
318
319 return false;
320 }
321
322 template <typename A>
323 bool File<A>::forEachAtom(ld::File::AtomHandler& handler) const
324 {
325 handler.doFile(*this);
326
327 // if doing flatnamespace and need all this dylib's imports resolve
328 // add atom which references alls undefines in this dylib
329 if ( _importAtom != nullptr ) {
330 handler.doAtom(*_importAtom);
331 return true;
332 }
333 return false;
334 }
335
336 template <typename A>
337 bool File<A>::justInTimeforEachAtom(const char* name, ld::File::AtomHandler& handler) const
338 {
339 // If we are supposed to ignore this export, then pretend we don't have it.
340 if ( _ignoreExports.count(name) != 0 )
341 return false;
342
343
344 AtomAndWeak bucket;
345 if ( containsOrReExports(name, bucket.weakDef, bucket.tlv, bucket.address) ) {
346 bucket.atom = new ExportAtom<A>(*this, name, bucket.weakDef, bucket.tlv, bucket.address);
347 _atoms[name] = bucket;
348 _providedAtom = true;
349 if ( _s_logHashtable )
350 fprintf(stderr, "getJustInTimeAtomsFor: %s found in %s\n", name, this->path());
351 // call handler with new export atom
352 handler.doAtom(*bucket.atom);
353 return true;
354 }
355
356 return false;
357 }
358
359 template <typename A>
360 void File<A>::assertNoReExportCycles(ReExportChain* prev) const
361 {
362 // recursively check my re-exported dylibs
363 ReExportChain chain = { prev, this };
364 for (const auto &dep : _dependentDylibs) {
365 if ( dep.reExport ) {
366 auto* child = dep.dylib;
367 // check child is not already in chain
368 for (auto* p = prev; p != nullptr; p = p->prev) {
369 if ( p->file == child ) {
370 throwf("cycle in dylib re-exports with %s and %s", child->path(), this->path());
371 }
372 }
373 if ( dep.dylib != nullptr )
374 dep.dylib->assertNoReExportCycles(&chain);
375 }
376 }
377 }
378
379 template <typename A>
380 void File<A>::processIndirectLibraries(ld::dylib::File::DylibHandler* handler, bool addImplicitDylibs)
381 {
382 // only do this once
383 if ( _indirectDylibsProcessed )
384 return;
385
386 const static bool log = false;
387 if ( log )
388 fprintf(stderr, "processIndirectLibraries(%s)\n", this->installPath());
389 if ( _linkingFlat ) {
390 for (auto &dep : _dependentDylibs)
391 dep.dylib = (File<A>*)handler->findDylib(dep.path, this->path());
392 }
393 else if ( _noRexports ) {
394 // MH_NO_REEXPORTED_DYLIBS bit set, then nothing to do
395 }
396 else {
397 // two-level, might have re-exports
398 for (auto &dep : this->_dependentDylibs) {
399 if ( dep.reExport ) {
400 if ( log )
401 fprintf(stderr, "processIndirectLibraries() parent=%s, child=%s\n", this->installPath(), dep.path);
402 // a LC_REEXPORT_DYLIB, LC_SUB_UMBRELLA or LC_SUB_LIBRARY says we re-export this child
403 dep.dylib = (File<A>*)handler->findDylib(dep.path, this->path());
404 if ( dep.dylib->hasPublicInstallName() && !dep.dylib->wrongOS() ) {
405 // promote this child to be automatically added as a direct dependent if this already is
406 if ( (this->explicitlyLinked() || this->implicitlyLinked()) && (strcmp(dep.path, dep.dylib->installPath()) == 0) ) {
407 if ( log )
408 fprintf(stderr, "processIndirectLibraries() implicitly linking %s\n", dep.dylib->installPath());
409 dep.dylib->setImplicitlyLinked();
410 }
411 else if ( dep.dylib->explicitlyLinked() || dep.dylib->implicitlyLinked() ) {
412 if ( log )
413 fprintf(stderr, "processIndirectLibraries() parent is not directly linked, but child is, so no need to re-export child\n");
414 }
415 else {
416 if ( log )
417 fprintf(stderr, "processIndirectLibraries() parent is not directly linked, so parent=%s will re-export child=%s\n", this->installPath(), dep.path);
418 }
419 }
420 else {
421 // add all child's symbols to me
422 if ( log )
423 fprintf(stderr, "processIndirectLibraries() child is not public, so parent=%s will re-export child=%s\n", this->installPath(), dep.path);
424 }
425 }
426 else if ( !_explictReExportFound ) {
427 // see if child contains LC_SUB_FRAMEWORK with my name
428 dep.dylib = (File<A>*)handler->findDylib(dep.path, this->path());
429 const char* parentUmbrellaName = dep.dylib->parentUmbrella();
430 if ( parentUmbrellaName != nullptr ) {
431 const char* parentName = this->path();
432 const char* lastSlash = strrchr(parentName, '/');
433 if ( (lastSlash != nullptr) && (strcmp(&lastSlash[1], parentUmbrellaName) == 0) ) {
434 // add all child's symbols to me
435 dep.reExport = true;
436 if ( log )
437 fprintf(stderr, "processIndirectLibraries() umbrella=%s will re-export child=%s\n", this->installPath(), dep.path);
438 }
439 }
440 }
441 }
442 }
443
444 // check for re-export cycles
445 ReExportChain chain = { nullptr, this };
446 this->assertNoReExportCycles(&chain);
447
448 _indirectDylibsProcessed = true;
449 }
450
451 template <typename A>
452 bool File<A>::isPublicLocation(const char* path) const
453 {
454 // -no_implicit_dylibs disables this optimization
455 if ( ! _implicitlyLinkPublicDylibs )
456 return false;
457
458 // /usr/lib is a public location
459 if ( (strncmp(path, "/usr/lib/", 9) == 0) && (strchr(&path[9], '/') == nullptr) )
460 return true;
461
462 // /System/Library/Frameworks/ is a public location
463 if ( strncmp(path, "/System/Library/Frameworks/", 27) == 0 ) {
464 const char* frameworkDot = strchr(&path[27], '.');
465 // but only top level framework
466 // /System/Library/Frameworks/Foo.framework/Versions/A/Foo ==> true
467 // /System/Library/Frameworks/Foo.framework/Resources/libBar.dylib ==> false
468 // /System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/Bar ==> false
469 // /System/Library/Frameworks/Foo.framework/Frameworks/Xfoo.framework/XFoo ==> false
470 if ( frameworkDot != nullptr ) {
471 int frameworkNameLen = frameworkDot - &path[27];
472 if ( strncmp(&path[strlen(path)-frameworkNameLen-1], &path[26], frameworkNameLen+1) == 0 )
473 return true;
474 }
475 }
476
477 return false;
478 }
479
480 template <typename A>
481 void File<A>::addSymbol(const char* name, bool weakDef, bool tlv, pint_t address)
482 {
483 // symbols that start with $ld$ are meta-data to the static linker
484 // <rdar://problem/5182537> need way for ld and dyld to see different exported symbols in a dylib
485 if ( strncmp(name, "$ld$", 4) == 0 ) {
486 // $ld$ <action> $ <condition> $ <symbol-name>
487 const char* symAction = &name[4];
488 const char* symCond = strchr(symAction, '$');
489 if ( symCond != nullptr ) {
490 char curOSVers[16];
491 sprintf(curOSVers, "$os%d.%d$", (_linkMinOSVersion >> 16), ((_linkMinOSVersion >> 8) & 0xFF));
492 if ( strncmp(symCond, curOSVers, strlen(curOSVers)) == 0 ) {
493 const char* symName = strchr(&symCond[1], '$');
494 if ( symName != nullptr ) {
495 ++symName;
496 if ( strncmp(symAction, "hide$", 5) == 0 ) {
497 if ( _s_logHashtable )
498 fprintf(stderr, " adding %s to ignore set for %s\n", symName, this->path());
499 _ignoreExports.insert(strdup(symName));
500 return;
501 }
502 else if ( strncmp(symAction, "add$", 4) == 0 ) {
503 this->addSymbol(symName, weakDef);
504 return;
505 }
506 else if ( strncmp(symAction, "install_name$", 13) == 0 ) {
507 _dylibInstallPath = strdup(symName);
508 _installPathOverride = true;
509 // <rdar://problem/14448206> CoreGraphics redirects to ApplicationServices, but with wrong compat version
510 if ( strcmp(_dylibInstallPath, "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices") == 0 )
511 _dylibCompatibilityVersion = Options::parseVersionNumber32("1.0");
512 return;
513 }
514 else if ( strncmp(symAction, "compatibility_version$", 22) == 0 ) {
515 _dylibCompatibilityVersion = Options::parseVersionNumber32(symName);
516 return;
517 }
518 else {
519 warning("bad symbol action: %s in dylib %s", name, this->path());
520 }
521 }
522 }
523 }
524 else {
525 warning("bad symbol condition: %s in dylib %s", name, this->path());
526 }
527 }
528
529 // add symbol as possible export if we are not supposed to ignore it
530 if ( _ignoreExports.count(name) == 0 ) {
531 AtomAndWeak bucket = { nullptr, weakDef, tlv, address };
532 if ( this->_s_logHashtable )
533 fprintf(stderr, " adding %s to hash table for %s\n", name, this->path());
534 _atoms[strdup(name)] = bucket;
535 }
536 }
537
538 // <rdar://problem/5529626> If only weak_import symbols are used, linker should use LD_LOAD_WEAK_DYLIB
539 template <typename A>
540 bool File<A>::allSymbolsAreWeakImported() const
541 {
542 bool foundNonWeakImport = false;
543 bool foundWeakImport = false;
544 //fprintf(stderr, "%s:\n", this->path());
545 for (const auto &it : _atoms) {
546 auto* atom = it.second.atom;
547 if ( atom != nullptr ) {
548 if ( atom->weakImported() )
549 foundWeakImport = true;
550 else
551 foundNonWeakImport = true;
552 //fprintf(stderr, " weak_import=%d, name=%s\n", atom->weakImported(), it->first);
553 }
554 }
555
556 // don't automatically weak link dylib with no imports
557 // so at least one weak import symbol and no non-weak-imported symbols must be found
558 return foundWeakImport && !foundNonWeakImport;
559 }
560
561
562 } // end namespace dylib
563 } // end namespace generic
564
565 #endif // __GENERIC_DYLIB_FILE_H__