Index: lld/ELF/Relocations.h =================================================================== --- lld/ELF/Relocations.h +++ lld/ELF/Relocations.h @@ -109,8 +109,13 @@ Symbol *Sym; }; +// This function writes undefined symbol diagnostics to an internal buffer. +// Call reportUndefinedSymbols() after calling scanRelocations() to emit +// the diagnostics. template void scanRelocations(InputSectionBase &); +template void reportUndefinedSymbols(); + void addIRelativeRelocs(); class ThunkSection; Index: lld/ELF/Relocations.cpp =================================================================== --- lld/ELF/Relocations.cpp +++ lld/ELF/Relocations.cpp @@ -705,27 +705,27 @@ return Msg; } -// Report an undefined symbol if necessary. -// Returns true if this function printed out an error message. -template -static bool maybeReportUndefined(Symbol &Sym, InputSectionBase &Sec, - uint64_t Offset) { - if (!Sym.isUndefined() || Sym.isWeak()) - return false; - - bool CanBeExternal = !Sym.isLocal() && Sym.computeBinding() != STB_LOCAL && - Sym.Visibility == STV_DEFAULT; - if (Config->UnresolvedSymbols == UnresolvedPolicy::Ignore && CanBeExternal) - return false; +// Undefined diagnostics are collected in a vector and emitted once all of +// them are known, so that some postprocessing on the list of undefined symbols +// can happen before lld emits diagnostics. +struct UndefinedDiag { + Symbol *Sym; + struct Loc { + InputSectionBase *Sec; + uint64_t Offset; + }; + std::vector Locs; + bool IsWarning; +}; +static std::vector& Undefs() { + // Function-local static to not require a global initializer. + static std::vector UndefsVector; + return UndefsVector; +} - // clang (as of 2019-06-12) / gcc (as of 8.2.1) PPC64 may emit a .rela.toc - // which references a switch table in a discarded .rodata/.text section. The - // .toc and the .rela.toc are incorrectly not placed in the comdat. The ELF - // spec says references from outside the group to a STB_LOCAL symbol are not - // allowed. Work around the bug. - if (Config->EMachine == EM_PPC64 && - cast(Sym).DiscardedSecIdx != 0 && Sec.Name == ".toc") - return false; +template +static void reportUndefinedSymbol(const UndefinedDiag &Undef) { + Symbol &Sym = *Undef.Sym; auto Visibility = [&]() -> std::string { switch (Sym.Visibility) { @@ -744,24 +744,84 @@ if (Msg.empty()) Msg = "undefined " + Visibility() + "symbol: " + toString(Sym); - Msg += "\n>>> referenced by "; - std::string Src = Sec.getSrcMsg(Sym, Offset); - if (!Src.empty()) - Msg += Src + "\n>>> "; - Msg += Sec.getObjMsg(Offset); + for (UndefinedDiag::Loc L : Undef.Locs) { + InputSectionBase &Sec = *L.Sec; + uint64_t Offset = L.Offset; + + Msg += "\n>>> referenced by "; + std::string Src = Sec.getSrcMsg(Sym, Offset); + if (!Src.empty()) + Msg += Src + "\n>>> "; + Msg += Sec.getObjMsg(Offset); + } if (Sym.getName().startswith("_ZTV")) Msg += "\nthe vtable symbol may be undefined because the class is missing " "its key function (see https://lld.llvm.org/missingkeyfunction)"; - if ((Config->UnresolvedSymbols == UnresolvedPolicy::Warn && CanBeExternal) || - Config->NoinhibitExec) { + if (Undef.IsWarning) warn(Msg); + else + error(Msg); +} + +template +void elf::reportUndefinedSymbols() { + // Find the first "undefined symbol" diagnostic for each diagnostic, and + // collect all "referenced from" lines at the first diagnostic. + std::map FirstRef; + for (size_t i = 0; i < Undefs().size(); ++i) { + auto& Undef = Undefs()[i]; + auto it = FirstRef.find(Undef.Sym); + if (it == FirstRef.end()) { + FirstRef[Undef.Sym] = i; + } else { + Undefs()[it->second].Locs.push_back(Undef.Locs[0]); + Undef.Locs.clear(); + } + } + + for (const auto& Undef : Undefs()) { + if (!Undef.Locs.empty()) + reportUndefinedSymbol(Undef); + } +} + +// Report an undefined symbol if necessary. +// Returns true if this function printed out an error message. +template +static bool maybeReportUndefined(Symbol &Sym, InputSectionBase &Sec, + uint64_t Offset) { + if (!Sym.isUndefined() || Sym.isWeak()) return false; + + bool CanBeExternal = !Sym.isLocal() && Sym.computeBinding() != STB_LOCAL && + Sym.Visibility == STV_DEFAULT; + if (Config->UnresolvedSymbols == UnresolvedPolicy::Ignore && CanBeExternal) + return false; + + // clang (as of 2019-06-12) / gcc (as of 8.2.1) PPC64 may emit a .rela.toc + // which references a switch table in a discarded .rodata/.text section. The + // .toc and the .rela.toc are incorrectly not placed in the comdat. The ELF + // spec says references from outside the group to a STB_LOCAL symbol are not + // allowed. Work around the bug. + if (Config->EMachine == EM_PPC64 && + cast(Sym).DiscardedSecIdx != 0 && Sec.Name == ".toc") + return false; + + + UndefinedDiag Undef = { &Sym, {{&Sec, Offset}}, /*IsWarning=*/false }; + if ((Config->UnresolvedSymbols == UnresolvedPolicy::Warn && CanBeExternal) || + Config->NoinhibitExec) + Undef.IsWarning = true; + + { + static std::mutex Mu; + std::lock_guard Lock(Mu); + Undefs().push_back(Undef); } - error(Msg); - return true; + return !Undef.IsWarning; } // MIPS N32 ABI treats series of successive relocations with the same offset @@ -1789,3 +1849,7 @@ template void elf::scanRelocations(InputSectionBase &); template void elf::scanRelocations(InputSectionBase &); template void elf::scanRelocations(InputSectionBase &); +template void elf::reportUndefinedSymbols(); +template void elf::reportUndefinedSymbols(); +template void elf::reportUndefinedSymbols(); +template void elf::reportUndefinedSymbols(); Index: lld/ELF/Writer.cpp =================================================================== --- lld/ELF/Writer.cpp +++ lld/ELF/Writer.cpp @@ -1737,8 +1737,10 @@ // Scan relocations. This must be done after every symbol is declared so that // we can correctly decide if a dynamic relocation is needed. - if (!Config->Relocatable) + if (!Config->Relocatable) { forEachRelSec(scanRelocations); + reportUndefinedSymbols(); + } addIRelativeRelocs(); Index: lld/test/ELF/debug-line-obj.s =================================================================== --- lld/test/ELF/debug-line-obj.s +++ lld/test/ELF/debug-line-obj.s @@ -10,7 +10,6 @@ # CHECK: error: undefined symbol: foo() # CHECK-NEXT: >>> referenced by test.cpp:2 # CHECK-NEXT: >>> {{.*}}.o:(bar()) -# CHECK: error: undefined symbol: foo() # CHECK-NEXT: >>> referenced by test.cpp:3 # CHECK-NEXT: >>> {{.*}}.o:(baz()) Index: lld/test/ELF/undef-multi.s =================================================================== --- /dev/null +++ lld/test/ELF/undef-multi.s @@ -0,0 +1,48 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %p/Inputs/undef.s -o %t2.o +# RUN: not ld.lld %t.o %t2.o -o %t.exe 2>&1 | FileCheck %s + +# CHECK: error: undefined symbol: zed2 +# CHECK-NEXT: >>> referenced by undef-multi.s +# CHECK-NEXT: >>> {{.*}}:(.text+0x1) +# CHECK-NEXT: >>> referenced by undef-multi.s +# CHECK-NEXT: >>> {{.*}}:(.text+0x6) +# CHECK-NEXT: >>> referenced by undef-multi.s +# CHECK-NEXT: >>> {{.*}}:(.text+0xB) +# CHECK-NEXT: >>> referenced by undef-multi.s +# CHECK-NEXT: >>> {{.*}}:(.text+0x10) +# CHECK-NEXT: >>> referenced by {{.*}}tmp2.o:(.text+0x0) + +# All references to a single undefined symbol count as a single error. +# RUN: not ld.lld %t.o %t2.o -o %t.exe -error-limit=2 2>&1 | \ +# RUN: FileCheck --check-prefix=LIMIT %s + +# LIMIT: error: undefined symbol: zed2 +# LIMIT-NEXT: >>> referenced by undef-multi.s +# LIMIT-NEXT: >>> {{.*}}:(.text+0x1) +# LIMIT-NEXT: >>> referenced by undef-multi.s +# LIMIT-NEXT: >>> {{.*}}:(.text+0x6) +# LIMIT-NEXT: >>> referenced by undef-multi.s +# LIMIT-NEXT: >>> {{.*}}:(.text+0xB) +# LIMIT-NEXT: >>> referenced by undef-multi.s +# LIMIT-NEXT: >>> {{.*}}:(.text+0x10) +# LIMIT-NEXT: >>> referenced by {{.*}}tmp2.o:(.text+0x0) + +.file "undef-multi.s" + + .globl _start +_start: + call zed2 + + .globl _f +_f: + call zed2 + + .globl _g +_g: + call zed2 + + .globl _h +_h: + call zed2