]> git.saurik.com Git - apple/ld64.git/blobdiff - src/ld/parsers/generic_dylib_file.hpp
ld64-264.3.101.tar.gz
[apple/ld64.git] / src / ld / parsers / generic_dylib_file.hpp
diff --git a/src/ld/parsers/generic_dylib_file.hpp b/src/ld/parsers/generic_dylib_file.hpp
new file mode 100644 (file)
index 0000000..301074f
--- /dev/null
@@ -0,0 +1,565 @@
+/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
+ *
+ * Copyright (c) 2015 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@
+ */
+
+#ifndef __GENERIC_DYLIB_FILE_H__
+#define __GENERIC_DYLIB_FILE_H__
+
+#include "ld.hpp"
+#include "Options.h"
+#include <unordered_map>
+#include <unordered_set>
+
+namespace generic {
+namespace dylib {
+
+// forward reference
+template <typename A> class File;
+
+//
+// An ExportAtom has no content.  It exists so that the linker can track which
+// imported symbols came from which dynamic libraries.
+//
+template <typename A>
+class ExportAtom final : public ld::Atom
+{
+public:
+       ExportAtom(const File<A>& f, const char* nm, bool weakDef, bool tlv,
+                          typename A::P::uint_t address)
+               : ld::Atom(f._importProxySection, ld::Atom::definitionProxy,
+                                  (weakDef ? ld::Atom::combineByName : ld::Atom::combineNever),
+                                  ld::Atom::scopeLinkageUnit,
+                                  (tlv ? ld::Atom::typeTLV : ld::Atom::typeUnclassified),
+                                  symbolTableNotIn, false, false, false, ld::Atom::Alignment(0)),
+                 _file(f),
+                 _name(nm),
+                 _address(address)
+       {}
+
+       // overrides of ld::Atom
+       virtual const ld::File* file() const override final { return &_file; }
+       virtual const char*             name() const override final { return _name; }
+       virtual uint64_t                size() const override final { return 0; }
+       virtual uint64_t                objectAddress() const override final { return _address; }
+       virtual void                    copyRawContent(uint8_t buffer[]) const override final { }
+
+       virtual void                    setScope(Scope) { }
+
+private:
+       using pint_t = typename A::P::uint_t;
+
+       virtual                                 ~ExportAtom() {}
+
+       const File<A>&                  _file;
+       const char*                             _name;
+       pint_t                                  _address;
+};
+
+
+//
+// An ImportAtom has no content.  It exists so that when linking a main executable flat-namespace
+// the imports of all flat dylibs are checked
+//
+template <typename A>
+class ImportAtom final : public ld::Atom
+{
+public:
+       ImportAtom(File<A>& f, std::vector<const char*>& imports);
+
+       // overrides of ld::Atom
+       virtual ld::File*                               file() const override final { return &_file; }
+       virtual const char*                             name() const override final { return "import-atom"; }
+       virtual uint64_t                                size() const override final { return 0; }
+       virtual uint64_t                                objectAddress() const override final { return 0; }
+       virtual ld::Fixup::iterator             fixupsBegin() const     override final { return &_undefs[0]; }
+       virtual ld::Fixup::iterator             fixupsEnd()     const override final { return &_undefs[_undefs.size()]; }
+       virtual void                                    copyRawContent(uint8_t buffer[]) const override final { }
+
+       virtual void                                    setScope(Scope)         { }
+
+private:
+       virtual                                                 ~ImportAtom() {}
+
+       File<A>&                                                _file;
+       mutable std::vector<ld::Fixup>  _undefs;
+};
+
+template <typename A>
+ImportAtom<A>::ImportAtom(File<A>& f, std::vector<const char*>& imports)
+       : ld::Atom(f._flatDummySection, ld::Atom::definitionRegular, ld::Atom::combineNever,
+                          ld::Atom::scopeTranslationUnit, ld::Atom::typeUnclassified, symbolTableNotIn, false,
+                          false, false, ld::Atom::Alignment(0)),
+         _file(f)
+{
+       for(auto *name : imports)
+               _undefs.emplace_back(0, ld::Fixup::k1of1, ld::Fixup::kindNone, false, strdup(name));
+}
+
+//
+// A generic representation for the dynamic library files we support (Mach-O and text-based stubs).
+// Common state and functionality is consolidated in this class.
+//
+template <typename A>
+class File : public ld::dylib::File
+{
+public:
+       File(const char* path, time_t mTime, ld::File::Ordinal ordinal, Options::Platform platform,
+                uint32_t linkMinOSVersion, bool linkingFlatNamespace, bool hoistImplicitPublicDylibs,
+                bool allowSimToMacOSX, bool addVers);
+
+       // overrides of ld::File
+       virtual bool                                                    forEachAtom(ld::File::AtomHandler&) const override final;
+       virtual bool                                                    justInTimeforEachAtom(const char* name, ld::File::AtomHandler&) const override final;
+       virtual ld::File::ObjcConstraint                objCConstraint() const override final { return _objcConstraint; }
+       virtual uint8_t                                                 swiftVersion() const override final { return _swiftVersion; }
+       virtual uint32_t                                                minOSVersion() const override final { return _minVersionInDylib; }
+       virtual uint32_t                                                platformLoadCommand() const     override final { return _platformInDylib; }
+       virtual ld::Bitcode*                                    getBitcode() const override final { return _bitcode.get(); }
+
+
+       // overrides of ld::dylib::File
+       virtual void                                                    processIndirectLibraries(ld::dylib::File::DylibHandler*, bool addImplicitDylibs) override final;
+       virtual bool                                                    providedExportAtom() const      override final { return _providedAtom; }
+       virtual const char*                                             parentUmbrella() const override final { return _parentUmbrella; }
+       virtual const std::vector<const char*>* allowableClients() const override final { return _allowableClients.empty() ? nullptr : &_allowableClients; }
+       virtual bool                                                    hasWeakExternals() const override final { return _hasWeakExports; }
+       virtual bool                                                    deadStrippable() const override final { return _deadStrippable; }
+       virtual bool                                                    hasWeakDefinition(const char* name) const override final;
+       virtual bool                                                    hasPublicInstallName() const override final { return _hasPublicInstallName; }
+       virtual bool                                                    allSymbolsAreWeakImported() const override final;
+       virtual bool                                                    installPathVersionSpecific() const override final { return _installPathOverride; }
+       virtual bool                                                    appExtensionSafe() const override final { return _appExtensionSafe; };
+
+
+       bool                                                                    wrongOS() const { return _wrongOS; }
+
+private:
+       using pint_t = typename A::P::uint_t;
+
+       friend class ExportAtom<A>;
+       friend class ImportAtom<A>;
+
+       struct CStringHash {
+               std::size_t operator()(const char* __s) const {
+                       unsigned long __h = 0;
+                       for ( ; *__s; ++__s)
+                               __h = 5 * __h + *__s;
+                       return size_t(__h);
+               };
+       };
+
+protected:
+       struct AtomAndWeak { ld::Atom* atom; bool weakDef; bool tlv; pint_t address; };
+       struct Dependent {
+        const char* path;
+        File<A>* dylib;
+        bool reExport;
+
+        Dependent(const char* path, bool reExport)
+            : path(path), dylib(nullptr), reExport(reExport) {}
+    };
+       struct ReExportChain { ReExportChain* prev; const File* file; };
+
+private:
+       using NameToAtomMap = std::unordered_map<const char*, AtomAndWeak, ld::CStringHash, ld::CStringEquals>;
+       using NameSet = std::unordered_set<const char*, CStringHash, ld::CStringEquals>;
+
+       std::pair<bool, bool>           hasWeakDefinitionImpl(const char* name) const;
+       bool                                            containsOrReExports(const char* name, bool& weakDef, bool& tlv, pint_t& addr) const;
+       void                                            assertNoReExportCycles(ReExportChain*) const;
+
+protected:
+       bool                                            isPublicLocation(const char* path) const;
+       void                                            addSymbol(const char* name, bool weak = false, bool tlv = false, pint_t address = 0);
+
+private:
+       ld::Section                                                     _importProxySection;
+       ld::Section                                                     _flatDummySection;
+       mutable bool                                            _providedAtom;
+       bool                                                            _indirectDylibsProcessed;
+
+protected:
+       mutable NameToAtomMap                           _atoms;
+       NameSet                                                         _ignoreExports;
+       std::vector<Dependent>                          _dependentDylibs;
+       ImportAtom<A>*                                          _importAtom;
+       std::vector<const char*>                        _allowableClients;
+       const char*                                                     _parentUmbrella;
+       std::unique_ptr<ld::Bitcode>            _bitcode;
+       const Options::Platform                         _platform;
+       ld::File::ObjcConstraint                        _objcConstraint;
+       const uint32_t                                          _linkMinOSVersion;
+       uint32_t                                                        _minVersionInDylib;
+       uint32_t                                                        _platformInDylib;
+       uint8_t                                                         _swiftVersion;
+       bool                                                            _wrongOS;
+       bool                                                            _linkingFlat;
+       bool                                                            _noRexports;
+       bool                                                            _explictReExportFound;
+       bool                                                            _implicitlyLinkPublicDylibs;
+       bool                                                            _installPathOverride;
+       bool                                                            _hasWeakExports;
+       bool                                                            _deadStrippable;
+       bool                                                            _hasPublicInstallName;
+       bool                                                            _appExtensionSafe;
+
+       const bool                                                      _allowSimToMacOSXLinking;
+       const bool                                                      _addVersionLoadCommand;
+
+       static bool                                                     _s_logHashtable;
+};
+
+template <typename A>
+bool File<A>::_s_logHashtable = false;
+
+template <typename A>
+File<A>::File(const char* path, time_t mTime, ld::File::Ordinal ord,  Options::Platform platform,
+                         uint32_t linkMinOSVersion, bool linkingFlatNamespace,
+                         bool hoistImplicitPublicDylibs,
+                         bool allowSimToMacOSX, bool addVers)
+       : ld::dylib::File(path, mTime, ord),
+         _importProxySection("__TEXT", "__import", ld::Section::typeImportProxies, true),
+         _flatDummySection("__LINKEDIT", "__flat_dummy", ld::Section::typeLinkEdit, true),
+         _providedAtom(false),
+         _indirectDylibsProcessed(false),
+         _importAtom(nullptr),
+         _parentUmbrella(nullptr),
+         _platform(platform),
+         _objcConstraint(ld::File::objcConstraintNone),
+         _linkMinOSVersion(linkMinOSVersion),
+         _minVersionInDylib(0),
+         _platformInDylib(Options::kPlatformUnknown),
+         _swiftVersion(0),
+         _wrongOS(false),
+         _linkingFlat(linkingFlatNamespace),
+         _noRexports(false),
+         _explictReExportFound(false),
+         _implicitlyLinkPublicDylibs(hoistImplicitPublicDylibs),
+         _installPathOverride(false),
+         _hasWeakExports(false),
+         _deadStrippable(false),
+         _hasPublicInstallName(false),
+         _appExtensionSafe(false),
+         _allowSimToMacOSXLinking(allowSimToMacOSX),
+         _addVersionLoadCommand(addVers)
+{
+}
+
+template <typename A>
+std::pair<bool, bool> File<A>::hasWeakDefinitionImpl(const char* name) const
+{
+       const auto pos = _atoms.find(name);
+       if ( pos != this->_atoms.end() )
+               return std::make_pair(true, pos->second.weakDef);
+
+       // look in re-exported libraries.
+       for (const auto &dep : _dependentDylibs) {
+               if ( dep.reExport ) {
+                       auto ret = dep.dylib->hasWeakDefinitionImpl(name);
+                       if ( ret.first )
+                               return ret;
+               }
+       }
+       return std::make_pair(false, false);
+}
+
+template <typename A>
+bool File<A>::hasWeakDefinition(const char* name) const
+{
+       // If we are supposed to ignore this export, then pretend we don't have it.
+       if ( _ignoreExports.count(name) != 0 )
+               return false;
+
+       return hasWeakDefinitionImpl(name).second;
+}
+
+template <typename A>
+bool File<A>::containsOrReExports(const char* name, bool& weakDef, bool& tlv, pint_t& addr) const
+{
+       if ( _ignoreExports.count(name) != 0 )
+               return false;
+
+       // check myself
+       const auto pos = _atoms.find(name);
+       if ( pos != _atoms.end() ) {
+               weakDef = pos->second.weakDef;
+               tlv = pos->second.tlv;
+               addr = pos->second.address;
+               return true;
+       }
+
+       // check dylibs I re-export
+       for (const auto& dep : _dependentDylibs) {
+               if ( dep.reExport && !dep.dylib->implicitlyLinked() ) {
+                       if ( dep.dylib->containsOrReExports(name, weakDef, tlv, addr) )
+                               return true;
+               }
+       }
+       
+       return false;
+}
+
+template <typename A>
+bool File<A>::forEachAtom(ld::File::AtomHandler& handler) const
+{
+       handler.doFile(*this);
+
+       // if doing flatnamespace and need all this dylib's imports resolve
+       // add atom which references alls undefines in this dylib
+       if ( _importAtom != nullptr ) {
+               handler.doAtom(*_importAtom);
+               return true;
+       }
+       return false;
+}
+
+template <typename A>
+bool File<A>::justInTimeforEachAtom(const char* name, ld::File::AtomHandler& handler) const
+{
+       // If we are supposed to ignore this export, then pretend we don't have it.
+       if ( _ignoreExports.count(name) != 0 )
+               return false;
+
+
+       AtomAndWeak bucket;
+       if ( containsOrReExports(name, bucket.weakDef, bucket.tlv, bucket.address) ) {
+               bucket.atom = new ExportAtom<A>(*this, name, bucket.weakDef, bucket.tlv, bucket.address);
+               _atoms[name] = bucket;
+               _providedAtom = true;
+               if ( _s_logHashtable )
+                       fprintf(stderr, "getJustInTimeAtomsFor: %s found in %s\n", name, this->path());
+               // call handler with new export atom
+               handler.doAtom(*bucket.atom);
+               return true;
+       }
+
+       return false;
+}
+
+template <typename A>
+void File<A>::assertNoReExportCycles(ReExportChain* prev) const
+{
+       // recursively check my re-exported dylibs
+       ReExportChain chain = { prev, this };
+       for (const auto &dep : _dependentDylibs) {
+               if ( dep.reExport ) {
+                       auto* child = dep.dylib;
+                       // check child is not already in chain
+                       for (auto* p = prev; p != nullptr; p = p->prev) {
+                               if ( p->file == child ) {
+                                       throwf("cycle in dylib re-exports with %s and %s", child->path(), this->path());
+                               }
+                       }
+                       if ( dep.dylib != nullptr )
+                               dep.dylib->assertNoReExportCycles(&chain);
+               }
+       }
+}
+
+template <typename A>
+void File<A>::processIndirectLibraries(ld::dylib::File::DylibHandler* handler, bool addImplicitDylibs)
+{
+       // only do this once
+       if ( _indirectDylibsProcessed )
+               return;
+
+       const static bool log = false;
+       if ( log )
+               fprintf(stderr, "processIndirectLibraries(%s)\n", this->installPath());
+       if ( _linkingFlat ) {
+               for (auto &dep : _dependentDylibs)
+                       dep.dylib = (File<A>*)handler->findDylib(dep.path, this->path());
+       }
+       else if ( _noRexports ) {
+               // MH_NO_REEXPORTED_DYLIBS bit set, then nothing to do
+       }
+       else {
+               // two-level, might have re-exports
+               for (auto &dep : this->_dependentDylibs) {
+                       if ( dep.reExport ) {
+                               if ( log )
+                                       fprintf(stderr, "processIndirectLibraries() parent=%s, child=%s\n", this->installPath(), dep.path);
+                               // a LC_REEXPORT_DYLIB, LC_SUB_UMBRELLA or LC_SUB_LIBRARY says we re-export this child
+                               dep.dylib = (File<A>*)handler->findDylib(dep.path, this->path());
+                               if ( dep.dylib->hasPublicInstallName() && !dep.dylib->wrongOS() ) {
+                                       // promote this child to be automatically added as a direct dependent if this already is
+                                       if ( (this->explicitlyLinked() || this->implicitlyLinked()) && (strcmp(dep.path, dep.dylib->installPath()) == 0) ) {
+                                               if ( log )
+                                                       fprintf(stderr, "processIndirectLibraries() implicitly linking %s\n", dep.dylib->installPath());
+                                               dep.dylib->setImplicitlyLinked();
+                                       }
+                                       else if ( dep.dylib->explicitlyLinked() || dep.dylib->implicitlyLinked() ) {
+                                               if ( log )
+                                                       fprintf(stderr, "processIndirectLibraries() parent is not directly linked, but child is, so no need to re-export child\n");
+                                       }
+                                       else {
+                                               if ( log )
+                                                       fprintf(stderr, "processIndirectLibraries() parent is not directly linked, so parent=%s will re-export child=%s\n", this->installPath(), dep.path);
+                                       }
+                               }
+                               else {
+                                       // add all child's symbols to me
+                                       if ( log )
+                                               fprintf(stderr, "processIndirectLibraries() child is not public, so parent=%s will re-export child=%s\n", this->installPath(), dep.path);
+                               }
+                       }
+                       else if ( !_explictReExportFound ) {
+                               // see if child contains LC_SUB_FRAMEWORK with my name
+                               dep.dylib = (File<A>*)handler->findDylib(dep.path, this->path());
+                               const char* parentUmbrellaName = dep.dylib->parentUmbrella();
+                               if ( parentUmbrellaName != nullptr ) {
+                                       const char* parentName = this->path();
+                                       const char* lastSlash = strrchr(parentName, '/');
+                                       if ( (lastSlash != nullptr) && (strcmp(&lastSlash[1], parentUmbrellaName) == 0) ) {
+                                               // add all child's symbols to me
+                                               dep.reExport = true;
+                                               if ( log )
+                                                       fprintf(stderr, "processIndirectLibraries() umbrella=%s will re-export child=%s\n", this->installPath(), dep.path);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       // check for re-export cycles
+       ReExportChain chain = { nullptr, this };
+       this->assertNoReExportCycles(&chain);
+       
+       _indirectDylibsProcessed = true;
+}
+
+template <typename A>
+bool File<A>::isPublicLocation(const char* path) const
+{
+       // -no_implicit_dylibs disables this optimization
+       if ( ! _implicitlyLinkPublicDylibs )
+               return false;
+
+       // /usr/lib is a public location
+       if ( (strncmp(path, "/usr/lib/", 9) == 0) && (strchr(&path[9], '/') == nullptr) )
+               return true;
+
+       // /System/Library/Frameworks/ is a public location
+       if ( strncmp(path, "/System/Library/Frameworks/", 27) == 0 ) {
+               const char* frameworkDot = strchr(&path[27], '.');
+               // but only top level framework
+               // /System/Library/Frameworks/Foo.framework/Versions/A/Foo                 ==> true
+               // /System/Library/Frameworks/Foo.framework/Resources/libBar.dylib         ==> false
+               // /System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/Bar   ==> false
+               // /System/Library/Frameworks/Foo.framework/Frameworks/Xfoo.framework/XFoo ==> false
+               if ( frameworkDot != nullptr ) {
+                       int frameworkNameLen = frameworkDot - &path[27];
+                       if ( strncmp(&path[strlen(path)-frameworkNameLen-1], &path[26], frameworkNameLen+1) == 0 )
+                               return true;
+               }
+       }
+       
+       return false;
+}
+
+template <typename A>
+void File<A>::addSymbol(const char* name, bool weakDef, bool tlv, pint_t address)
+{
+       // symbols that start with $ld$ are meta-data to the static linker
+       // <rdar://problem/5182537> need way for ld and dyld to see different exported symbols in a dylib
+       if ( strncmp(name, "$ld$", 4) == 0 ) {
+               //    $ld$ <action> $ <condition> $ <symbol-name>
+               const char* symAction = &name[4];
+               const char* symCond = strchr(symAction, '$');
+               if ( symCond != nullptr ) {
+                       char curOSVers[16];
+                       sprintf(curOSVers, "$os%d.%d$", (_linkMinOSVersion >> 16), ((_linkMinOSVersion >> 8) & 0xFF));
+                       if ( strncmp(symCond, curOSVers, strlen(curOSVers)) == 0 ) {
+                               const char* symName = strchr(&symCond[1], '$');
+                               if ( symName != nullptr ) {
+                                       ++symName;
+                                       if ( strncmp(symAction, "hide$", 5) == 0 ) {
+                                               if ( _s_logHashtable )
+                                                       fprintf(stderr, "  adding %s to ignore set for %s\n", symName, this->path());
+                                               _ignoreExports.insert(strdup(symName));
+                                               return;
+                                       }
+                                       else if ( strncmp(symAction, "add$", 4) == 0 ) {
+                                               this->addSymbol(symName, weakDef);
+                                               return;
+                                       }
+                                       else if ( strncmp(symAction, "install_name$", 13) == 0 ) {
+                                               _dylibInstallPath = strdup(symName);
+                                               _installPathOverride = true;
+                                               // <rdar://problem/14448206> CoreGraphics redirects to ApplicationServices, but with wrong compat version
+                                               if ( strcmp(_dylibInstallPath, "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices") == 0 )
+                                                       _dylibCompatibilityVersion = Options::parseVersionNumber32("1.0");
+                                               return;
+                                       }
+                                       else if ( strncmp(symAction, "compatibility_version$", 22) == 0 ) {
+                                               _dylibCompatibilityVersion = Options::parseVersionNumber32(symName);
+                                               return;
+                                       }
+                                       else {
+                                               warning("bad symbol action: %s in dylib %s", name, this->path());
+                                       }
+                               }
+                       }
+               }
+               else {
+                       warning("bad symbol condition: %s in dylib %s", name, this->path());
+               }
+       }
+
+       // add symbol as possible export if we are not supposed to ignore it
+       if ( _ignoreExports.count(name) == 0 ) {
+               AtomAndWeak bucket = { nullptr, weakDef, tlv, address };
+               if ( this->_s_logHashtable )
+                       fprintf(stderr, "  adding %s to hash table for %s\n", name, this->path());
+               _atoms[strdup(name)] = bucket;
+       }
+}
+
+// <rdar://problem/5529626> If only weak_import symbols are used, linker should use LD_LOAD_WEAK_DYLIB
+template <typename A>
+bool File<A>::allSymbolsAreWeakImported() const
+{
+       bool foundNonWeakImport = false;
+       bool foundWeakImport = false;
+       //fprintf(stderr, "%s:\n", this->path());
+       for (const auto &it : _atoms) {
+               auto* atom = it.second.atom;
+               if ( atom != nullptr ) {
+                       if ( atom->weakImported() )
+                               foundWeakImport = true;
+                       else
+                               foundNonWeakImport = true;
+                       //fprintf(stderr, "  weak_import=%d, name=%s\n", atom->weakImported(), it->first);
+               }
+       }
+
+       // don't automatically weak link dylib with no imports
+       // so at least one weak import symbol and no non-weak-imported symbols must be found
+       return foundWeakImport && !foundNonWeakImport;
+}
+
+
+} // end namespace dylib
+} // end namespace generic
+
+#endif // __GENERIC_DYLIB_FILE_H__