Index: lld/trunk/ELF/Relocations.h =================================================================== --- lld/trunk/ELF/Relocations.h +++ lld/trunk/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/trunk/ELF/Relocations.cpp =================================================================== --- lld/trunk/ELF/Relocations.cpp +++ lld/trunk/ELF/Relocations.cpp @@ -678,27 +678,24 @@ 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; +// 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; +}; - bool CanBeExternal = !Sym.isLocal() && Sym.computeBinding() != STB_LOCAL && - Sym.Visibility == STV_DEFAULT; - if (Config->UnresolvedSymbols == UnresolvedPolicy::Ignore && CanBeExternal) - return false; +static std::vector Undefs; - // 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) { @@ -717,24 +714,83 @@ 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); + const size_t MaxUndefReferences = 10; + size_t I = 0; + for (UndefinedDiag::Loc L : Undef.Locs) { + if (I >= MaxUndefReferences) + break; + 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); + I++; + } + + if (I < Undef.Locs.size()) + Msg += ("\n>>> referenced " + Twine(Undef.Locs.size() - I) + " more times") + .str(); 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); - return false; + 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. + DenseMap FirstRef; + for (UndefinedDiag &Undef : Undefs) { + assert(Undef.Locs.size() == 1); + if (UndefinedDiag *Canon = FirstRef.lookup(Undef.Sym)) { + Canon->Locs.push_back(Undef.Locs[0]); + Undef.Locs.clear(); + } else + FirstRef[Undef.Sym] = &Undef; + } + + for (const UndefinedDiag &Undef : Undefs) { + if (!Undef.Locs.empty()) + reportUndefinedSymbol(Undef); } + Undefs.clear(); +} - error(Msg); - return true; +// Report an undefined symbol if necessary. +// Returns true if the undefined symbol will produce 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; + + bool IsWarning = + (Config->UnresolvedSymbols == UnresolvedPolicy::Warn && CanBeExternal) || + Config->NoinhibitExec; + Undefs.push_back({&Sym, {{&Sec, Offset}}, IsWarning}); + return !IsWarning; } // MIPS N32 ABI treats series of successive relocations with the same offset @@ -1734,7 +1790,8 @@ Rel.Sym = T->getThunkTargetSym(); Rel.Expr = fromPlt(Rel.Expr); - // Addend of R_PPC_PLTREL24 should be ignored after changing to R_PC. + // The addend of R_PPC_PLTREL24 should be ignored after changing to + // R_PC. if (Config->EMachine == EM_PPC && Rel.Type == R_PPC_PLTREL24) Rel.Addend = 0; } @@ -1756,3 +1813,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/trunk/ELF/Writer.cpp =================================================================== --- lld/trunk/ELF/Writer.cpp +++ lld/trunk/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/trunk/test/ELF/debug-line-obj.s =================================================================== --- lld/trunk/test/ELF/debug-line-obj.s +++ lld/trunk/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/trunk/test/ELF/undef-multi.s =================================================================== --- lld/trunk/test/ELF/undef-multi.s +++ lld/trunk/test/ELF/undef-multi.s @@ -0,0 +1,65 @@ +# 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 /dev/null 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 -- but +# at most 10 references are printed. +# RUN: echo ".globl _bar" > %t.moreref.s +# RUN: echo "_bar:" >> %t.moreref.s +# RUN: echo " call zed2" >> %t.moreref.s +# RUN: echo " call zed2" >> %t.moreref.s +# RUN: echo " call zed2" >> %t.moreref.s +# RUN: echo " call zed2" >> %t.moreref.s +# RUN: echo " call zed2" >> %t.moreref.s +# RUN: echo " call zed2" >> %t.moreref.s +# RUN: echo " call zed2" >> %t.moreref.s +# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %t.moreref.s -o %t3.o +# RUN: not ld.lld %t.o %t2.o %t3.o -o /dev/null -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) +# LIMIT-NEXT: >>> referenced by {{.*}}tmp3.o:(.text+0x1) +# LIMIT-NEXT: >>> referenced by {{.*}}tmp3.o:(.text+0x6) +# LIMIT-NEXT: >>> referenced by {{.*}}tmp3.o:(.text+0xB) +# LIMIT-NEXT: >>> referenced by {{.*}}tmp3.o:(.text+0x10) +# LIMIT-NEXT: >>> referenced by {{.*}}tmp3.o:(.text+0x15) +# LIMIT-NEXT: >>> referenced 2 more times + +.file "undef-multi.s" + + .globl _start +_start: + call zed2 + + .globl _f +_f: + call zed2 + + .globl _g +_g: + call zed2 + + .globl _h +_h: + call zed2