Skip to content

Commit b4744d3

Browse files
committed
[ELF] Support --{,no-}allow-shlib-undefined
Summary: In ld.bfd/gold, --no-allow-shlib-undefined is the default when linking an executable. This patch implements a check to error on undefined symbols in a shared object, if all of its DT_NEEDED entries are seen. Our approach resembles the one used in gold, achieves a good balance to be useful but not too smart (ld.bfd traces all DSOs and emulates the behavior of a dynamic linker to catch more cases). The error is issued based on the symbol table, different from undefined reference errors issued for relocations. It is most effective when there are DSOs that were not linked with -z defs (e.g. when static sanitizers runtime is used). gold has a comment that some system libraries on GNU/Linux may have spurious undefined references and thus system libraries should be excluded (https://sourceware.org/bugzilla/show_bug.cgi?id=6811). The story may have changed now but we make --allow-shlib-undefined the default for now. Its interaction with -shared can be discussed in the future. Reviewers: ruiu, grimar, pcc, espindola Reviewed By: ruiu Subscribers: joerg, emaste, arichardson, llvm-commits Differential Revision: https://reviews.llvm.org/D57385 llvm-svn: 352826
1 parent dfbd190 commit b4744d3

File tree

9 files changed

+62
-26
lines changed

9 files changed

+62
-26
lines changed

lld/ELF/Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ struct Configuration {
119119
uint64_t>
120120
CallGraphProfile;
121121
bool AllowMultipleDefinition;
122+
bool AllowShlibUndefined;
122123
bool AndroidPackDynRelocs;
123124
bool ARMHasBlx = false;
124125
bool ARMHasMovtMovw = false;

lld/ELF/Driver.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,8 @@ void LinkerDriver::readConfigs(opt::InputArgList &Args) {
756756
Args.hasFlag(OPT_allow_multiple_definition,
757757
OPT_no_allow_multiple_definition, false) ||
758758
hasZOption(Args, "muldefs");
759+
Config->AllowShlibUndefined = Args.hasFlag(
760+
OPT_allow_shlib_undefined, OPT_no_allow_shlib_undefined, true);
759761
Config->AuxiliaryList = args::getStrings(Args, OPT_auxiliary);
760762
Config->Bsymbolic = Args.hasArg(OPT_Bsymbolic);
761763
Config->BsymbolicFunctions = Args.hasArg(OPT_Bsymbolic_functions);

lld/ELF/InputFiles.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,7 @@ SharedFile<ELFT>::SharedFile(MemoryBufferRef M, StringRef DefaultSoName)
862862

863863
// Partially parse the shared object file so that we can call
864864
// getSoName on this object.
865-
template <class ELFT> void SharedFile<ELFT>::parseSoName() {
865+
template <class ELFT> void SharedFile<ELFT>::parseDynamic() {
866866
const Elf_Shdr *DynamicSec = nullptr;
867867
const ELFFile<ELFT> Obj = this->getObj();
868868
ArrayRef<Elf_Shdr> Sections = CHECK(Obj.sections(), this);
@@ -899,12 +899,16 @@ template <class ELFT> void SharedFile<ELFT>::parseSoName() {
899899
ArrayRef<Elf_Dyn> Arr =
900900
CHECK(Obj.template getSectionContentsAsArray<Elf_Dyn>(DynamicSec), this);
901901
for (const Elf_Dyn &Dyn : Arr) {
902-
if (Dyn.d_tag == DT_SONAME) {
902+
if (Dyn.d_tag == DT_NEEDED) {
903+
uint64_t Val = Dyn.getVal();
904+
if (Val >= this->StringTable.size())
905+
fatal(toString(this) + ": invalid DT_NEEDED entry");
906+
DtNeeded.push_back(this->StringTable.data() + Val);
907+
} else if (Dyn.d_tag == DT_SONAME) {
903908
uint64_t Val = Dyn.getVal();
904909
if (Val >= this->StringTable.size())
905910
fatal(toString(this) + ": invalid DT_SONAME entry");
906911
SoName = this->StringTable.data() + Val;
907-
return;
908912
}
909913
}
910914
}
@@ -972,7 +976,7 @@ uint32_t SharedFile<ELFT>::getAlignment(ArrayRef<Elf_Shdr> Sections,
972976
return (Ret > UINT32_MAX) ? 0 : Ret;
973977
}
974978

975-
// Fully parse the shared object file. This must be called after parseSoName().
979+
// Fully parse the shared object file. This must be called after parseDynamic().
976980
//
977981
// This function parses symbol versions. If a DSO has version information,
978982
// the file has a ".gnu.version_d" section which contains symbol version

lld/ELF/InputFiles.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ template <class ELFT> class SharedFile : public ELFFileBase<ELFT> {
332332

333333
public:
334334
std::vector<const Elf_Verdef *> Verdefs;
335+
std::vector<StringRef> DtNeeded;
335336
std::string SoName;
336337

337338
static bool classof(const InputFile *F) {
@@ -340,7 +341,7 @@ template <class ELFT> class SharedFile : public ELFFileBase<ELFT> {
340341

341342
SharedFile(MemoryBufferRef M, StringRef DefaultSoName);
342343

343-
void parseSoName();
344+
void parseDynamic();
344345
void parseRest();
345346
uint32_t getAlignment(ArrayRef<Elf_Shdr> Sections, const Elf_Sym &Sym);
346347
std::vector<const Elf_Verdef *> parseVerdefs();
@@ -358,6 +359,9 @@ template <class ELFT> class SharedFile : public ELFFileBase<ELFT> {
358359
// data structures in the output file.
359360
std::map<const Elf_Verdef *, NeededVer> VerdefMap;
360361

362+
// Used for --no-allow-shlib-undefined.
363+
bool AllNeededIsKnown;
364+
361365
// Used for --as-needed
362366
bool IsNeeded;
363367
};

lld/ELF/Options.td

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ defm allow_multiple_definition: B<"allow-multiple-definition",
6363
"Allow multiple definitions",
6464
"Do not allow multiple definitions (default)">;
6565

66+
defm allow_shlib_undefined: B<"allow-shlib-undefined",
67+
"Allow unresolved references in shared libraries (default)",
68+
"Do not allow unresolved references in shared libraries">;
69+
6670
defm apply_dynamic_relocs: B<"apply-dynamic-relocs",
6771
"Apply link-time values for dynamic relocations",
6872
"Do not apply link-time values for dynamic relocations (default)">;
@@ -492,12 +496,10 @@ def plugin_opt_thinlto: J<"plugin-opt=thinlto">;
492496
def plugin_opt_slash: J<"plugin-opt=/">;
493497

494498
// Options listed below are silently ignored for now for compatibility.
495-
def: F<"allow-shlib-undefined">;
496499
def: F<"detect-odr-violations">;
497500
def: Flag<["-"], "g">;
498501
def: F<"long-plt">;
499502
def: F<"no-add-needed">;
500-
def: F<"no-allow-shlib-undefined">;
501503
def: F<"no-copy-dt-needed-entries">;
502504
def: F<"no-ctors-in-init-array">;
503505
def: F<"no-keep-memory">;

lld/ELF/SymbolTable.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ template <class ELFT> void SymbolTable::addFile(InputFile *File) {
9292
// .so file
9393
if (auto *F = dyn_cast<SharedFile<ELFT>>(File)) {
9494
// DSOs are uniquified not by filename but by soname.
95-
F->parseSoName();
95+
F->parseDynamic();
9696
if (errorCount())
9797
return;
9898

lld/ELF/SymbolTable.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ class SymbolTable {
7979

8080
void handleDynamicList();
8181

82+
// Set of .so files to not link the same shared object file more than once.
83+
llvm::DenseMap<StringRef, InputFile *> SoNames;
84+
8285
private:
8386
std::pair<Symbol *, bool> insertName(StringRef Name);
8487

@@ -106,9 +109,6 @@ class SymbolTable {
106109
// is used to uniquify them.
107110
llvm::DenseSet<llvm::CachedHashStringRef> ComdatGroups;
108111

109-
// Set of .so files to not link the same shared object file more than once.
110-
llvm::DenseMap<StringRef, InputFile *> SoNames;
111-
112112
// A map from demangled symbol names to their symbol objects.
113113
// This mapping is 1:N because two symbols with different versions
114114
// can have the same name. We use this map to handle "extern C++ {}"

lld/ELF/Writer.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,6 +1682,27 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
16821682
if (In.Iplt && !In.Iplt->empty())
16831683
In.Iplt->addSymbols();
16841684

1685+
if (!Config->AllowShlibUndefined) {
1686+
// Error on undefined symbols in a shared object, if all of its DT_NEEDED
1687+
// entires are seen. These cases would otherwise lead to runtime errors
1688+
// reported by the dynamic linker.
1689+
//
1690+
// ld.bfd traces all DT_NEEDED to emulate the logic of the dynamic linker to
1691+
// catch more cases. That is too much for us. Our approach resembles the one
1692+
// used in ld.gold, achieves a good balance to be useful but not too smart.
1693+
for (InputFile *File : SharedFiles) {
1694+
SharedFile<ELFT> *F = cast<SharedFile<ELFT>>(File);
1695+
F->AllNeededIsKnown = llvm::all_of(F->DtNeeded, [&](StringRef Needed) {
1696+
return Symtab->SoNames.count(Needed);
1697+
});
1698+
}
1699+
for (Symbol *Sym : Symtab->getSymbols())
1700+
if (Sym->isUndefined() && !Sym->isWeak())
1701+
if (auto *F = dyn_cast_or_null<SharedFile<ELFT>>(Sym->File))
1702+
if (F->AllNeededIsKnown)
1703+
error(toString(F) + ": undefined reference to " + toString(*Sym));
1704+
}
1705+
16851706
// Now that we have defined all possible global symbols including linker-
16861707
// synthesized ones. Visit all symbols to give the finishing touches.
16871708
for (Symbol *Sym : Symtab->getSymbols()) {

lld/test/ELF/allow-shlib-undefined.s

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
# REQUIRES: x86
2-
# --allow-shlib-undefined and --no-allow-shlib-undefined are fully
3-
# ignored in linker implementation.
42
# --allow-shlib-undefined is set by default
3+
4+
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.o
55
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
6-
# RUN: %p/Inputs/allow-shlib-undefined.s -o %t
7-
# RUN: ld.lld -shared %t -o %t.so
8-
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t1
6+
# RUN: %p/Inputs/allow-shlib-undefined.s -o %t1.o
7+
# RUN: ld.lld -shared %t1.o -o %t.so
8+
9+
# RUN: ld.lld %t.o %t.so -o /dev/null
10+
# RUN: ld.lld --allow-shlib-undefined %t.o %t.so -o /dev/null
11+
# RUN: not ld.lld --no-allow-shlib-undefined %t.o %t.so -o /dev/null 2>&1 | FileCheck %s
912

10-
# Executable: should link with DSO containing undefined symbols in any case.
11-
# RUN: ld.lld %t1 %t.so -o %t2
12-
# RUN: ld.lld --no-allow-shlib-undefined %t1 %t.so -o %t2
13-
# RUN: ld.lld --allow-shlib-undefined %t1 %t.so -o %t2
13+
# RUN: echo | llvm-mc -filetype=obj -triple=x86_64-unknown-linux -o %tempty.o
14+
# RUN: ld.lld -shared %tempty.o -o %tempty.so
15+
# RUN: ld.lld -shared %t1.o %tempty.so -o %t2.so
16+
# RUN: ld.lld --no-allow-shlib-undefined %t.o %t2.so -o /dev/null
1417

1518
# DSO with undefines:
1619
# should link with or without any of these options.
17-
# RUN: ld.lld -shared %t -o %t.so
18-
# RUN: ld.lld -shared --allow-shlib-undefined %t -o %t.so
19-
# RUN: ld.lld -shared --no-allow-shlib-undefined %t -o %t.so
20-
21-
# Executable still should not link when have undefines inside.
22-
# RUN: not ld.lld %t -o %t.so
20+
# RUN: ld.lld -shared %t1.o -o /dev/null
21+
# RUN: ld.lld -shared --allow-shlib-undefined %t1.o -o /dev/null
22+
# RUN: ld.lld -shared --no-allow-shlib-undefined %t1.o -o /dev/null
2323

2424
.globl _start
2525
_start:
2626
callq _shared@PLT
27+
28+
# CHECK: undefined reference to _unresolved

0 commit comments

Comments
 (0)