Skip to content

Commit

Permalink
lld/elf: Deduplicate undefined symbol diagnostics
Browse files Browse the repository at this point in the history
Before:

```
ld.lld: error: undefined symbol: f()
>>> referenced by test.cc:3
>>>               /var/folders/c5/8d7sdn1x2mg92mj0rndghhdr0000gn/T/test-9c0808.o:(g())

ld.lld: error: undefined symbol: f()
>>> referenced by test.cc:4
>>>               /var/folders/c5/8d7sdn1x2mg92mj0rndghhdr0000gn/T/test-9c0808.o:(h())

ld.lld: error: undefined symbol: f()
>>> referenced by test.cc:5
>>>               /var/folders/c5/8d7sdn1x2mg92mj0rndghhdr0000gn/T/test-9c0808.o:(j())

ld.lld: error: undefined symbol: k()
>>> referenced by test.cc:5
>>>               /var/folders/c5/8d7sdn1x2mg92mj0rndghhdr0000gn/T/test-9c0808.o:(j())

ld.lld: error: undefined symbol: f()
>>> referenced by test2.cc:2
>>>               /var/folders/c5/8d7sdn1x2mg92mj0rndghhdr0000gn/T/test2-07b391.o:(asdf())
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

Now:

```
ld.lld: error: undefined symbol: f()
>>> referenced by test.cc:3
>>>               /var/folders/c5/8d7sdn1x2mg92mj0rndghhdr0000gn/T/test-0e07ba.o:(g())
>>> referenced by test.cc:4
>>>               /var/folders/c5/8d7sdn1x2mg92mj0rndghhdr0000gn/T/test-0e07ba.o:(h())
>>> referenced by test.cc:5
>>>               /var/folders/c5/8d7sdn1x2mg92mj0rndghhdr0000gn/T/test-0e07ba.o:(j())
>>> referenced by test2.cc:2
>>>               /var/folders/c5/8d7sdn1x2mg92mj0rndghhdr0000gn/T/test2-6bdb24.o:(asdf())

ld.lld: error: undefined symbol: k()
>>> referenced by test.cc:5
>>>               /var/folders/c5/8d7sdn1x2mg92mj0rndghhdr0000gn/T/test-0e07ba.o:(j())
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

If there are more than 10 references to an undefined symbol, only the
first 10 are printed.

Fixes PR42260.

Differential Revision: https://reviews.llvm.org/D63344

llvm-svn: 363962
  • Loading branch information
nico committed Jun 20, 2019
1 parent 6d9fb68 commit 2c45043
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 32 deletions.
121 changes: 91 additions & 30 deletions lld/ELF/Relocations.cpp
Expand Up @@ -678,27 +678,24 @@ static std::string maybeReportDiscarded(Undefined &Sym) {
return Msg;
}

// Report an undefined symbol if necessary.
// Returns true if this function printed out an error message.
template <class ELFT>
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<Loc> 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<UndefinedDiag> 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<Undefined>(Sym).DiscardedSecIdx != 0 && Sec.Name == ".toc")
return false;
template <class ELFT>
static void reportUndefinedSymbol(const UndefinedDiag &Undef) {
Symbol &Sym = *Undef.Sym;

auto Visibility = [&]() -> std::string {
switch (Sym.Visibility) {
Expand All @@ -717,24 +714,83 @@ static bool maybeReportUndefined(Symbol &Sym, InputSectionBase &Sec,
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 <class ELFT> void elf::reportUndefinedSymbols() {
// Find the first "undefined symbol" diagnostic for each diagnostic, and
// collect all "referenced from" lines at the first diagnostic.
DenseMap<Symbol *, UndefinedDiag *> 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;
}

error(Msg);
return true;
for (const UndefinedDiag &Undef : Undefs) {
if (!Undef.Locs.empty())
reportUndefinedSymbol<ELFT>(Undef);
}
Undefs.clear();
}

// Report an undefined symbol if necessary.
// Returns true if the undefined symbol will produce an error message.
template <class ELFT>
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<Undefined>(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
Expand Down Expand Up @@ -1734,7 +1790,8 @@ bool ThunkCreator::createThunks(ArrayRef<OutputSection *> OutputSections) {
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;
}
Expand All @@ -1756,3 +1813,7 @@ template void elf::scanRelocations<ELF32LE>(InputSectionBase &);
template void elf::scanRelocations<ELF32BE>(InputSectionBase &);
template void elf::scanRelocations<ELF64LE>(InputSectionBase &);
template void elf::scanRelocations<ELF64BE>(InputSectionBase &);
template void elf::reportUndefinedSymbols<ELF32LE>();
template void elf::reportUndefinedSymbols<ELF32BE>();
template void elf::reportUndefinedSymbols<ELF64LE>();
template void elf::reportUndefinedSymbols<ELF64BE>();
5 changes: 5 additions & 0 deletions lld/ELF/Relocations.h
Expand Up @@ -109,8 +109,13 @@ struct Relocation {
Symbol *Sym;
};

// This function writes undefined symbol diagnostics to an internal buffer.
// Call reportUndefinedSymbols() after calling scanRelocations() to emit
// the diagnostics.
template <class ELFT> void scanRelocations(InputSectionBase &);

template <class ELFT> void reportUndefinedSymbols();

void addIRelativeRelocs();

class ThunkSection;
Expand Down
4 changes: 3 additions & 1 deletion lld/ELF/Writer.cpp
Expand Up @@ -1737,8 +1737,10 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {

// 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<ELFT>);
reportUndefinedSymbols<ELFT>();
}

addIRelativeRelocs();

Expand Down
1 change: 0 additions & 1 deletion lld/test/ELF/debug-line-obj.s
Expand Up @@ -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())

Expand Down
65 changes: 65 additions & 0 deletions lld/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

0 comments on commit 2c45043

Please sign in to comment.