X-Git-Url: https://git.saurik.com/apple/ld64.git/blobdiff_plain/b2fa67a80bc53211e4d1ea81f23e9f953ee1dd6c..ebf6f43431fe84b7b17822014a6d1f0169516e93:/src/ld/SymbolTable.cpp diff --git a/src/ld/SymbolTable.cpp b/src/ld/SymbolTable.cpp index 66ff358..406556a 100644 --- a/src/ld/SymbolTable.cpp +++ b/src/ld/SymbolTable.cpp @@ -34,6 +34,8 @@ #include #include +#include +#include #include #include #include @@ -57,14 +59,12 @@ namespace tool { // HACK, I can't find a way to pass values in the compare classes (e.g. ContentFuncs) // so use global variable to pass info. static ld::IndirectBindingTable* _s_indirectBindingTable = NULL; -bool SymbolTable::_s_doDemangle = false; SymbolTable::SymbolTable(const Options& opts, std::vector& ibt) - : _options(opts), _cstringTable(6151), _indirectBindingTable(ibt), _hasExternalTentativeDefinitions(false) + : _options(opts), _cstringTable(6151), _indirectBindingTable(ibt), _hasExternalTentativeDefinitions(false) { _s_indirectBindingTable = this; - _s_doDemangle = _options.demangleSymbols(); } @@ -119,251 +119,295 @@ bool SymbolTable::ReferencesHashFuncs::operator()(const ld::Atom* left, const ld } +void SymbolTable::addDuplicateSymbol(const char *name, const ld::Atom *atom) +{ + // Look up or create the file list for name. + DuplicateSymbols::iterator symbolsIterator = _duplicateSymbols.find(name); + DuplicatedSymbolAtomList *atoms = NULL; + if (symbolsIterator != _duplicateSymbols.end()) { + atoms = symbolsIterator->second; + } else { + atoms = new std::vector; + _duplicateSymbols.insert(std::pair(name, atoms)); + } + + // check if file is already in the list, add it if not + bool found = false; + for (DuplicatedSymbolAtomList::iterator it = atoms->begin(); !found && it != atoms->end(); it++) + if (strcmp((*it)->file()->path(), atom->file()->path()) == 0) + found = true; + if (!found) + atoms->push_back(atom); +} -bool SymbolTable::addByName(const ld::Atom& newAtom, bool ignoreDuplicates) +void SymbolTable::checkDuplicateSymbols() const { - bool useNew = true; - bool checkVisibilityMismatch = false; - assert(newAtom.name() != NULL); - const char* name = newAtom.name(); - IndirectBindingSlot slot = this->findSlotForName(name); - const ld::Atom* existingAtom = _indirectBindingTable[slot]; - //fprintf(stderr, "addByName(%p) name=%s, slot=%u, existing=%p\n", &newAtom, newAtom.name(), slot, existingAtom); - if ( existingAtom != NULL ) { - assert(&newAtom != existingAtom); - switch ( existingAtom->definition() ) { - case ld::Atom::definitionRegular: - switch ( newAtom.definition() ) { - case ld::Atom::definitionRegular: - if ( existingAtom->combine() == ld::Atom::combineByName ) { - if ( newAtom.combine() == ld::Atom::combineByName ) { - // always choose mach-o over llvm bit code, otherwise LTO may eliminate the llvm atom - const bool existingIsLTO = (existingAtom->contentType() == ld::Atom::typeLTOtemporary); - const bool newIsLTO = (newAtom.contentType() == ld::Atom::typeLTOtemporary); - if ( existingIsLTO != newIsLTO ) { - useNew = existingIsLTO; - } - else { - // both weak, prefer non-auto-hide one - if ( newAtom.autoHide() != existingAtom->autoHide() ) { - // support auto hidden weak symbols: .weak_def_can_be_hidden - useNew = existingAtom->autoHide(); - // don't check for visibility mismatch - } - else if ( newAtom.autoHide() && existingAtom->autoHide() ) { - // both have auto-hide, so use one with greater alignment - useNew = ( newAtom.alignment().trailingZeros() > existingAtom->alignment().trailingZeros() ); - } - else { - // neither auto-hide, check visibility - if ( newAtom.scope() != existingAtom->scope() ) { - // use more visible weak def symbol - useNew = (newAtom.scope() == ld::Atom::scopeGlobal); - } - else { - // both have same visibility, use one with greater alignment - useNew = ( newAtom.alignment().trailingZeros() > existingAtom->alignment().trailingZeros() ); - } - } - } - } - else { - // existing weak, new is not-weak - useNew = true; - } + bool foundDuplicate = false; + for (DuplicateSymbols::const_iterator symbolIt = _duplicateSymbols.begin(); symbolIt != _duplicateSymbols.end(); symbolIt++) { + DuplicatedSymbolAtomList *atoms = symbolIt->second; + bool reportDuplicate; + if (_options.deadCodeStrip()) { + // search for a live atom + reportDuplicate = false; + for (DuplicatedSymbolAtomList::iterator it = atoms->begin(); !reportDuplicate && it != atoms->end(); it++) { + if ((*it)->live()) + reportDuplicate = true; + } + } else { + reportDuplicate = true; + } + if (reportDuplicate) { + foundDuplicate = true; + fprintf(stderr, "duplicate symbol %s in:\n", symbolIt->first); + for (DuplicatedSymbolAtomList::iterator atomIt = atoms->begin(); atomIt != atoms->end(); atomIt++) { + fprintf(stderr, " %s\n", (*atomIt)->file()->path()); + } + } + } + if (foundDuplicate) + throwf("%d duplicate symbol%s", (int)_duplicateSymbols.size(), _duplicateSymbols.size()==1?"":"s"); +} + +// AtomPicker encapsulates the logic for picking which atom to use when adding an atom by name results in a collision +class NameCollisionResolution { +public: + NameCollisionResolution(const ld::Atom& a, const ld::Atom& b, bool ignoreDuplicates, const Options& options) : _atomA(a), _atomB(b), _options(options), _reportDuplicate(false), _ignoreDuplicates(ignoreDuplicates) { + pickAtom(); + } + + // Returns which atom to use + const ld::Atom& chosen() { return *_chosen; } + bool choseAtom(const ld::Atom& atom) { return _chosen == &atom; } + + // Returns true if the two atoms should be reported as a duplicate symbol + bool reportDuplicate() { return _reportDuplicate; } + +private: + const ld::Atom& _atomA; + const ld::Atom& _atomB; + const Options& _options; + const ld::Atom* _chosen; + bool _reportDuplicate; + bool _ignoreDuplicates; + + void pickAtom(const ld::Atom& atom) { _chosen = &atom; } // primitive to set which atom is picked + void pickAtomA() { pickAtom(_atomA); } // primitive to pick atom A + void pickAtomB() { pickAtom(_atomB); } // primitive to pick atom B + + // use atom A if pickA, otherwise use atom B + void pickAOrB(bool pickA) { if (pickA) pickAtomA(); else pickAtomB(); } + + void pickHigherOrdinal() { + pickAOrB(_atomA.file()->ordinal() < _atomB.file()->ordinal()); + } + + void pickLowerOrdinal() { + pickAOrB(_atomA.file()->ordinal() > _atomB.file()->ordinal()); + } + + void pickLargerSize() { + if (_atomA.size() == _atomB.size()) + pickLowerOrdinal(); + else + pickAOrB(_atomA.size() > _atomB.size()); + } + + void pickGreaterAlignment() { + pickAOrB(_atomA.alignment().trailingZeros() > _atomB.alignment().trailingZeros()); + } + + void pickBetweenRegularAtoms() { + if ( _atomA.combine() == ld::Atom::combineByName ) { + if ( _atomB.combine() == ld::Atom::combineByName ) { + // always choose mach-o over llvm bit code, otherwise LTO may eliminate the llvm atom + const bool aIsLTO = (_atomA.contentType() == ld::Atom::typeLTOtemporary); + const bool bIsLTO = (_atomB.contentType() == ld::Atom::typeLTOtemporary); + // always choose mach-o over llvm bit code, otherwise LTO may eliminate the llvm atom + if ( aIsLTO != bIsLTO ) { + pickAOrB(!aIsLTO); + } + else { + // both weak, prefer non-auto-hide one + if ( _atomA.autoHide() != _atomB.autoHide() ) { + // support auto hidden weak symbols: .weak_def_can_be_hidden + pickAOrB(!_atomA.autoHide()); + } + else if ( _atomA.autoHide() && _atomB.autoHide() ) { + // both have auto-hide, so use one with greater alignment + pickGreaterAlignment(); + } + else { + // neither auto-hide, check visibility + if ( _atomA.scope() != _atomB.scope() ) { + // use more visible weak def symbol + pickAOrB(_atomA.scope() == ld::Atom::scopeGlobal); } else { - if ( newAtom.combine() == ld::Atom::combineByName ) { - // existing not-weak, new is weak - useNew = false; - } - else { - // existing not-weak, new is not-weak - if ( newAtom.section().type() == ld::Section::typeMachHeader ) { - warning("ignoring override of built-in symbol %s from %s", newAtom.name(), existingAtom->file()->path()); - useNew = true; - } - else if ( existingAtom->section().type() == ld::Section::typeMachHeader ) { - warning("ignoring override of built-in symbol %s from %s", newAtom.name(), newAtom.file()->path()); - useNew = false; - } - else { - if ( ignoreDuplicates ) { - useNew = false; - static bool fullWarning = false; - if ( ! fullWarning ) { - warning("-dead_strip with lazy loaded static (library) archives " - "has resulted in a duplicate symbol. You can change your " - "source code to rename symbols to avoid the collision. " - "This will be an error in a future linker."); - fullWarning = true; - } - warning("duplicate symbol %s originally in %s now lazily loaded from %s", - SymbolTable::demangle(name), existingAtom->file()->path(), newAtom.file()->path()); - } - else { - throwf("duplicate symbol %s in %s and %s", - SymbolTable::demangle(name), newAtom.file()->path(), existingAtom->file()->path()); - } - } - } + // both have same visibility, use one with greater alignment + pickGreaterAlignment(); } + } + } + } + else { + pickAtomB(); // pick not-weak + + } + } + else { + if ( _atomB.combine() == ld::Atom::combineByName ) { + pickAtomA(); // pick not-weak + + } + else { + // both are not-weak + if ( _atomA.section().type() == ld::Section::typeMachHeader ) { + pickAtomA(); + } + else if ( _atomB.section().type() == ld::Section::typeMachHeader ) { + pickAtomB(); + } + else { + if ( _ignoreDuplicates ) { + pickLowerOrdinal(); + } + else { + _reportDuplicate = true; + } + } + } + } + } + + void pickCommonsMode(const ld::Atom& dylib, const ld::Atom& proxy) { + assert(dylib.definition() == ld::Atom::definitionTentative); + assert(proxy.definition() == ld::Atom::definitionProxy); + switch ( _options.commonsMode() ) { + case Options::kCommonsIgnoreDylibs: + if ( _options.warnCommons() ) + warning("using common symbol %s from %s and ignoring defintion from dylib %s", + proxy.name(), proxy.file()->path(), dylib.file()->path()); + pickAtom(dylib); + break; + case Options::kCommonsOverriddenByDylibs: + if ( _options.warnCommons() ) + warning("replacing common symbol %s from %s with true definition from dylib %s", + proxy.name(), proxy.file()->path(), dylib.file()->path()); + pickAtom(proxy); + break; + case Options::kCommonsConflictsDylibsError: + throwf("common symbol %s from %s conflicts with defintion from dylib %s", + proxy.name(), proxy.file()->path(), dylib.file()->path()); + } + } + + void pickProxyAtom() { + // both atoms are definitionProxy + // ld should keep looking when it finds a weak definition in a dylib + if ( _atomA.combine() == ld::Atom::combineByName ) { + pickAtomB(); + } else if ( _atomB.combine() == ld::Atom::combineByName ) { + pickAtomA(); + } else { + throwf("symbol %s exported from both %s and %s\n", _atomA.name(), _atomA.file()->path(), _atomB.file()->path()); + } + } + + void pickAtom() { + // First, discriminate by definition + switch (_atomA.definition()) { + case ld::Atom::definitionRegular: + switch (_atomB.definition()) { + case ld::Atom::definitionRegular: + pickBetweenRegularAtoms(); break; case ld::Atom::definitionTentative: - // ignore new tentative atom, because we already have a regular one - useNew = false; - checkVisibilityMismatch = true; - if ( newAtom.size() > existingAtom->size() ) { - warning("for symbol %s tentative definition of size %llu from %s is " - "is smaller than the real definition of size %llu from %s", - newAtom.name(), newAtom.size(), newAtom.file()->path(), - existingAtom->size(), existingAtom->file()->path()); - } + pickAtomA(); break; case ld::Atom::definitionAbsolute: - throwf("duplicate symbol %s in %s and %s", name, newAtom.file()->path(), existingAtom->file()->path()); + _reportDuplicate = true; + pickHigherOrdinal(); + break; case ld::Atom::definitionProxy: - // ignore external atom, because we already have a one - useNew = false; + pickAtomA(); break; } break; case ld::Atom::definitionTentative: - switch ( newAtom.definition() ) { + switch (_atomB.definition()) { case ld::Atom::definitionRegular: - // replace existing tentative atom with regular one - if ( newAtom.section().type() == ld::Section::typeMachHeader ) { - // silently replace tentative __dso_handle with real linker created symbol - useNew = true; - } - else if ( existingAtom->section().type() == ld::Section::typeMachHeader ) { - // silently replace tentative __dso_handle with real linker created symbol - useNew = false; - } - else { - checkVisibilityMismatch = true; - if ( newAtom.size() < existingAtom->size() ) { - warning("for symbol %s tentative definition of size %llu from %s is " - "being replaced by a real definition of size %llu from %s", - newAtom.name(), existingAtom->size(), existingAtom->file()->path(), - newAtom.size(), newAtom.file()->path()); - } - if ( newAtom.section().type() == ld::Section::typeCode ) { - warning("for symbol %s tentative (data) defintion from %s is " - "being replaced by code from %s", newAtom.name(), existingAtom->file()->path(), - newAtom.file()->path()); - } - } + pickAtomB(); break; case ld::Atom::definitionTentative: - // new and existing are both tentative definitions, use largest - checkVisibilityMismatch = true; - if ( newAtom.size() < existingAtom->size() ) { - useNew = false; - } - else { - if ( newAtom.alignment().trailingZeros() < existingAtom->alignment().trailingZeros() ) - warning("alignment lost in merging tentative definition %s", newAtom.name()); - } + pickLargerSize(); break; case ld::Atom::definitionAbsolute: - // replace tentative with absolute - useNew = true; + pickHigherOrdinal(); break; case ld::Atom::definitionProxy: - // a tentative definition and a dylib definition, so commons-mode decides how to handle - switch ( _options.commonsMode() ) { - case Options::kCommonsIgnoreDylibs: - if ( _options.warnCommons() ) - warning("using common symbol %s from %s and ignoring defintion from dylib %s", - existingAtom->name(), existingAtom->file()->path(), newAtom.file()->path()); - useNew = false; - break; - case Options::kCommonsOverriddenByDylibs: - if ( _options.warnCommons() ) - warning("replacing common symbol %s from %s with true definition from dylib %s", - existingAtom->name(), existingAtom->file()->path(), newAtom.file()->path()); - break; - case Options::kCommonsConflictsDylibsError: - throwf("common symbol %s from %s conflicts with defintion from dylib %s", - existingAtom->name(), existingAtom->file()->path(), newAtom.file()->path()); - } + pickCommonsMode(_atomA, _atomB); break; } break; case ld::Atom::definitionAbsolute: - switch ( newAtom.definition() ) { + switch (_atomB.definition()) { case ld::Atom::definitionRegular: - throwf("duplicate symbol %s in %s and %s", name, newAtom.file()->path(), existingAtom->file()->path()); + _reportDuplicate = true; + pickHigherOrdinal(); + break; case ld::Atom::definitionTentative: - // ignore new tentative atom, because we already have a regular one - useNew = false; + pickAtomA(); break; case ld::Atom::definitionAbsolute: - throwf("duplicate symbol %s in %s and %s", name, newAtom.file()->path(), existingAtom->file()->path()); + _reportDuplicate = true; + pickHigherOrdinal(); + break; case ld::Atom::definitionProxy: - // ignore external atom, because we already have a one - useNew = false; + pickAtomA(); break; } break; case ld::Atom::definitionProxy: - switch ( newAtom.definition() ) { + switch (_atomB.definition()) { case ld::Atom::definitionRegular: - // replace external atom with regular one - useNew = true; + pickAtomB(); break; case ld::Atom::definitionTentative: - // a tentative definition and a dylib definition, so commons-mode decides how to handle - switch ( _options.commonsMode() ) { - case Options::kCommonsIgnoreDylibs: - if ( _options.warnCommons() ) - warning("using common symbol %s from %s and ignoring defintion from dylib %s", - newAtom.name(), newAtom.file()->path(), existingAtom->file()->path()); - break; - case Options::kCommonsOverriddenByDylibs: - if ( _options.warnCommons() ) - warning("replacing defintion of %s from dylib %s with common symbol from %s", - newAtom.name(), existingAtom->file()->path(), newAtom.file()->path()); - useNew = false; - break; - case Options::kCommonsConflictsDylibsError: - throwf("common symbol %s from %s conflicts with defintion from dylib %s", - newAtom.name(), newAtom.file()->path(), existingAtom->file()->path()); - } + pickCommonsMode(_atomB, _atomA); break; case ld::Atom::definitionAbsolute: - // replace external atom with absolute one - useNew = true; + pickAtomB(); break; case ld::Atom::definitionProxy: - // ld should keep looking when it finds a weak definition in a dylib - if ( newAtom.combine() == ld::Atom::combineByName ) { - useNew = false; - } - else { - if ( existingAtom->combine() == ld::Atom::combineByName ) - useNew = true; - else - throwf("symbol %s exported from both %s and %s\n", name, newAtom.file()->path(), existingAtom->file()->path()); - } + pickProxyAtom(); break; } break; - } + } } - if ( (existingAtom != NULL) && checkVisibilityMismatch && (newAtom.scope() != existingAtom->scope()) ) { - warning("%s has different visibility (%s) in %s and (%s) in %s", - SymbolTable::demangle(newAtom.name()), (newAtom.scope() == 1 ? "hidden" : "default"), newAtom.file()->path(), (existingAtom->scope() == 1 ? "hidden" : "default"), existingAtom->file()->path()); +}; + +bool SymbolTable::addByName(const ld::Atom& newAtom, bool ignoreDuplicates) +{ + bool useNew = true; + assert(newAtom.name() != NULL); + const char* name = newAtom.name(); + IndirectBindingSlot slot = this->findSlotForName(name); + const ld::Atom* existingAtom = _indirectBindingTable[slot]; + //fprintf(stderr, "addByName(%p) name=%s, slot=%u, existing=%p\n", &newAtom, newAtom.name(), slot, existingAtom); + if ( existingAtom != NULL ) { + assert(&newAtom != existingAtom); + NameCollisionResolution picker(newAtom, *existingAtom, ignoreDuplicates, _options); + if (picker.reportDuplicate()) { + addDuplicateSymbol(name, existingAtom); + addDuplicateSymbol(name, &newAtom); + } + useNew = picker.choseAtom(newAtom); } if ( useNew ) { _indirectBindingTable[slot] = &newAtom; if ( existingAtom != NULL ) { markCoalescedAway(existingAtom); -// if ( fOwner.fInitialLoadsDone ) { -// //fprintf(stderr, "existing %p %s overridden by %p\n", existingAtom, existingAtom->name(), &newAtom); -// fOwner.fAtomsOverriddenByLateLoads.insert(existingAtom); -// } } if ( newAtom.scope() == ld::Atom::scopeGlobal ) { if ( newAtom.definition() == ld::Atom::definitionTentative ) { @@ -474,6 +518,16 @@ void SymbolTable::markCoalescedAway(const ld::Atom* atom) } + +struct StrcmpSorter { + bool operator() (const char* i,const char* j) { + if (i==NULL) + return true; + if (j==NULL) + return false; + return strcmp(i, j)<0;} +}; + void SymbolTable::undefines(std::vector& undefs) { // return all names in _byNameTable that have no associated atom @@ -483,7 +537,8 @@ void SymbolTable::undefines(std::vector& undefs) undefs.push_back(it->first); } // sort so that undefines are in a stable order (not dependent on hashing functions) - std::sort(undefs.begin(), undefs.end()); + struct StrcmpSorter strcmpSorter; + std::sort(undefs.begin(), undefs.end(), strcmpSorter); } @@ -684,35 +739,6 @@ const ld::Atom* SymbolTable::indirectAtom(IndirectBindingSlot slot) const return _indirectBindingTable[slot]; } -extern "C" char* __cxa_demangle (const char* mangled_name, - char* buf, - size_t* n, - int* status); - -const char* SymbolTable::demangle(const char* sym) -{ - // only try to demangle symbols if -demangle on command line - if ( !_s_doDemangle ) - return sym; - - // only try to demangle symbols that look like C++ symbols - if ( strncmp(sym, "__Z", 3) != 0 ) - return sym; - - static size_t size = 1024; - static char* buff = (char*)malloc(size); - int status; - - char* result = __cxa_demangle(&sym[1], buff, &size, &status); - if ( result != NULL ) { - // if demangling succesful, keep buffer for next demangle - buff = result; - return buff; - } - return sym; -} - - void SymbolTable::printStatistics() { // fprintf(stderr, "cstring table size: %lu, bucket count: %lu, hash func called %u times\n",