Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -119,6 +119,7 @@ uint64_t> CallGraphProfile; bool AllowMultipleDefinition; + bool AllowShlibUndefined; bool AndroidPackDynRelocs; bool ARMHasBlx = false; bool ARMHasMovtMovw = false; Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -756,6 +756,8 @@ Args.hasFlag(OPT_allow_multiple_definition, OPT_no_allow_multiple_definition, false) || hasZOption(Args, "muldefs"); + Config->AllowShlibUndefined = Args.hasFlag( + OPT_allow_shlib_undefined, OPT_no_allow_shlib_undefined, true); Config->AuxiliaryList = args::getStrings(Args, OPT_auxiliary); Config->Bsymbolic = Args.hasArg(OPT_Bsymbolic); Config->BsymbolicFunctions = Args.hasArg(OPT_Bsymbolic_functions); Index: ELF/InputFiles.h =================================================================== --- ELF/InputFiles.h +++ ELF/InputFiles.h @@ -332,6 +332,7 @@ public: std::vector Verdefs; + std::vector DtNeeded; std::string SoName; static bool classof(const InputFile *F) { @@ -340,7 +341,7 @@ SharedFile(MemoryBufferRef M, StringRef DefaultSoName); - void parseSoName(); + void parseDynamic(); void parseRest(); uint32_t getAlignment(ArrayRef Sections, const Elf_Sym &Sym); std::vector parseVerdefs(); Index: ELF/InputFiles.cpp =================================================================== --- ELF/InputFiles.cpp +++ ELF/InputFiles.cpp @@ -862,7 +862,7 @@ // Partially parse the shared object file so that we can call // getSoName on this object. -template void SharedFile::parseSoName() { +template void SharedFile::parseDynamic() { const Elf_Shdr *DynamicSec = nullptr; const ELFFile Obj = this->getObj(); ArrayRef Sections = CHECK(Obj.sections(), this); @@ -899,12 +899,16 @@ ArrayRef Arr = CHECK(Obj.template getSectionContentsAsArray(DynamicSec), this); for (const Elf_Dyn &Dyn : Arr) { - if (Dyn.d_tag == DT_SONAME) { + if (Dyn.d_tag == DT_NEEDED) { + uint64_t Val = Dyn.getVal(); + if (Val >= this->StringTable.size()) + fatal(toString(this) + ": invalid DT_NEEDED entry"); + DtNeeded.push_back(this->StringTable.data() + Val); + } else if (Dyn.d_tag == DT_SONAME) { uint64_t Val = Dyn.getVal(); if (Val >= this->StringTable.size()) fatal(toString(this) + ": invalid DT_SONAME entry"); SoName = this->StringTable.data() + Val; - return; } } } @@ -972,7 +976,7 @@ return (Ret > UINT32_MAX) ? 0 : Ret; } -// Fully parse the shared object file. This must be called after parseSoName(). +// Fully parse the shared object file. This must be called after parseDynamic(). // // This function parses symbol versions. If a DSO has version information, // the file has a ".gnu.version_d" section which contains symbol version Index: ELF/Options.td =================================================================== --- ELF/Options.td +++ ELF/Options.td @@ -63,6 +63,10 @@ "Allow multiple definitions", "Do not allow multiple definitions (default)">; +defm allow_shlib_undefined: B<"allow-shlib-undefined", + "Allow unresolved references in shared libraries (default)", + "Do not allow unresolved references in shared libraries">; + defm apply_dynamic_relocs: B<"apply-dynamic-relocs", "Apply link-time values for dynamic relocations", "Do not apply link-time values for dynamic relocations (default)">; @@ -492,12 +496,10 @@ def plugin_opt_slash: J<"plugin-opt=/">; // Options listed below are silently ignored for now for compatibility. -def: F<"allow-shlib-undefined">; def: F<"detect-odr-violations">; def: Flag<["-"], "g">; def: F<"long-plt">; def: F<"no-add-needed">; -def: F<"no-allow-shlib-undefined">; def: F<"no-copy-dt-needed-entries">; def: F<"no-ctors-in-init-array">; def: F<"no-keep-memory">; Index: ELF/SymbolTable.h =================================================================== --- ELF/SymbolTable.h +++ ELF/SymbolTable.h @@ -79,6 +79,9 @@ void handleDynamicList(); + // Set of .so files to not link the same shared object file more than once. + llvm::DenseMap SoNames; + private: std::pair insertName(StringRef Name); @@ -106,9 +109,6 @@ // is used to uniquify them. llvm::DenseSet ComdatGroups; - // Set of .so files to not link the same shared object file more than once. - llvm::DenseMap SoNames; - // A map from demangled symbol names to their symbol objects. // This mapping is 1:N because two symbols with different versions // can have the same name. We use this map to handle "extern C++ {}" Index: ELF/SymbolTable.cpp =================================================================== --- ELF/SymbolTable.cpp +++ ELF/SymbolTable.cpp @@ -92,7 +92,7 @@ // .so file if (auto *F = dyn_cast>(File)) { // DSOs are uniquified not by filename but by soname. - F->parseSoName(); + F->parseDynamic(); if (errorCount()) return; Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -1682,6 +1682,30 @@ if (In.Iplt && !In.Iplt->empty()) In.Iplt->addSymbols(); + if (!Config->AllowShlibUndefined) { + // Error on undefined symbols in a shared object, if all of its DT_NEEDED + // entires are seen. These cases would otherwise lead to runtime errors + // reported by the dynamic linker. + // + // ld.bfd traces all DT_NEEDED to emulate the logic of the dynamic linker to + // catch more cases. That is too much for us. Our approach resembles the one + // used in gold, achieves a good balance to be useful but not too smart. + DenseSet AllNeededIsKnown; + for (InputFile *File : SharedFiles) { + SharedFile *F = cast>(File); + if (llvm::all_of(F->DtNeeded, [&](StringRef Needed) { + return Symtab->SoNames.count(Needed); + })) + AllNeededIsKnown.insert(File); + } + for (Symbol *Sym : Symtab->getSymbols()) { + auto *File = dyn_cast_or_null>(Sym->File); + if (File && Sym->isUndefined() && Sym->Binding != STB_WEAK && + AllNeededIsKnown.count(File)) + error(File->getName() + ": undefined reference to " + toString(*Sym)); + } + } + // Now that we have defined all possible global symbols including linker- // synthesized ones. Visit all symbols to give the finishing touches. for (Symbol *Sym : Symtab->getSymbols()) { Index: test/ELF/allow-shlib-undefined.s =================================================================== --- test/ELF/allow-shlib-undefined.s +++ test/ELF/allow-shlib-undefined.s @@ -1,26 +1,28 @@ # REQUIRES: x86 -# --allow-shlib-undefined and --no-allow-shlib-undefined are fully -# ignored in linker implementation. # --allow-shlib-undefined is set by default + +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.o # RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \ -# RUN: %p/Inputs/allow-shlib-undefined.s -o %t -# RUN: ld.lld -shared %t -o %t.so -# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t1 +# RUN: %p/Inputs/allow-shlib-undefined.s -o %t1.o +# RUN: ld.lld -shared %t1.o -o %t.so -# Executable: should link with DSO containing undefined symbols in any case. -# RUN: ld.lld %t1 %t.so -o %t2 -# RUN: ld.lld --no-allow-shlib-undefined %t1 %t.so -o %t2 -# RUN: ld.lld --allow-shlib-undefined %t1 %t.so -o %t2 +# RUN: ld.lld %t.o %t.so -o /dev/null +# RUN: ld.lld --allow-shlib-undefined %t.o %t.so -o /dev/null +# RUN: not ld.lld --no-allow-shlib-undefined %t.o %t.so -o /dev/null 2>&1 | FileCheck %s + +# RUN: echo | llvm-mc -filetype=obj -triple=x86_64-unknown-linux -o %tempty.o +# RUN: ld.lld -shared %tempty.o -o %tempty.so +# RUN: ld.lld -shared %t1.o %tempty.so -o %t2.so +# RUN: ld.lld --no-allow-shlib-undefined %t.o %t2.so -o /dev/null # DSO with undefines: # should link with or without any of these options. -# RUN: ld.lld -shared %t -o %t.so -# RUN: ld.lld -shared --allow-shlib-undefined %t -o %t.so -# RUN: ld.lld -shared --no-allow-shlib-undefined %t -o %t.so - -# Executable still should not link when have undefines inside. -# RUN: not ld.lld %t -o %t.so +# RUN: ld.lld -shared %t1.o -o /dev/null +# RUN: ld.lld -shared --allow-shlib-undefined %t1.o -o /dev/null +# RUN: ld.lld -shared --no-allow-shlib-undefined %t1.o -o /dev/null .globl _start _start: callq _shared@PLT + +# CHECK: undefined reference to _unresolved