[ELF] Better error reporting for undefined symbols

This patch make lld show following details for undefined symbol errors:
- file (line)
- file (function name)
- file (section name + offset)

Differential revision: https://reviews.llvm.org/D25826

llvm-svn: 285186
This commit is contained in:
Eugene Leviant 2016-10-26 11:07:09 +00:00
parent 9bcb064f19
commit b380b24e6e
15 changed files with 149 additions and 28 deletions

View File

@ -18,6 +18,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/Bitcode/ReaderWriter.h"
#include "llvm/CodeGen/Analysis.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/LTO/LTO.h"
@ -35,6 +36,39 @@ using namespace lld::elf;
std::vector<InputFile *> InputFile::Pool;
template <class ELFT> DIHelper<ELFT>::DIHelper(elf::InputFile *F) {
Expected<std::unique_ptr<object::ObjectFile>> Obj =
object::ObjectFile::createObjectFile(F->MB);
if (!Obj)
return;
DWARFContextInMemory Dwarf(*Obj.get());
DwarfLine.reset(new DWARFDebugLine(&Dwarf.getLineSection().Relocs));
DataExtractor LineData(Dwarf.getLineSection().Data,
ELFT::TargetEndianness == support::little,
ELFT::Is64Bits ? 8 : 4);
// The second parameter is offset in .debug_line section
// for compilation unit (CU) of interest. We have only one
// CU (object file), so offset is always 0.
DwarfLine->getOrParseLineTable(LineData, 0);
}
template <class ELFT> std::string DIHelper<ELFT>::getLineInfo(uintX_t Offset) {
if (!DwarfLine)
return "";
DILineInfo LineInfo;
DILineInfoSpecifier Spec;
// The offset to CU is 0 (see DIHelper constructor).
const DWARFDebugLine::LineTable *LineTbl = DwarfLine->getLineTable(0);
if (!LineTbl)
return "";
LineTbl->getFileLineInfoForAddress(Offset, nullptr, Spec.FLIKind, LineInfo);
return LineInfo.Line != 0
? LineInfo.FileName + " (" + std::to_string(LineInfo.Line) + ")"
: "";
}
// Deletes all InputFile instances created so far.
void InputFile::freePool() {
// Files are freed in reverse order so that files created
@ -132,6 +166,13 @@ ArrayRef<SymbolBody *> elf::ObjectFile<ELFT>::getSymbols() {
return makeArrayRef(this->SymbolBodies).slice(1);
}
template <class ELFT> DIHelper<ELFT> *elf::ObjectFile<ELFT>::getDIHelper() {
if (!DIH)
DIH.reset(new DIHelper<ELFT>(this));
return DIH.get();
}
template <class ELFT> uint32_t elf::ObjectFile<ELFT>::getMipsGp0() const {
if (ELFT::Is64Bits && MipsOptions && MipsOptions->Reginfo)
return MipsOptions->Reginfo->ri_gp_value;
@ -432,6 +473,8 @@ SymbolBody *elf::ObjectFile<ELFT>::createSymbolBody(const Elf_Sym *Sym) {
int Binding = Sym->getBinding();
InputSectionBase<ELFT> *Sec = getSection(*Sym);
if (Binding == STB_LOCAL) {
if (Sym->getType() == STT_FILE)
SourceFile = check(Sym->getName(this->StringTable));
if (Sym->st_shndx == SHN_UNDEF)
return new (this->Alloc)
Undefined(Sym->st_name, Sym->st_other, Sym->getType(), this);
@ -897,3 +940,8 @@ template InputFile *BinaryFile::createELF<ELF32LE>();
template InputFile *BinaryFile::createELF<ELF32BE>();
template InputFile *BinaryFile::createELF<ELF64LE>();
template InputFile *BinaryFile::createELF<ELF64BE>();
template class elf::DIHelper<ELF32LE>;
template class elf::DIHelper<ELF32BE>;
template class elf::DIHelper<ELF64LE>;
template class elf::DIHelper<ELF64BE>;

View File

@ -29,6 +29,7 @@
#include <map>
namespace llvm {
class DWARFDebugLine;
namespace lto {
class InputFile;
}
@ -43,6 +44,21 @@ class InputFile;
class Lazy;
class SymbolBody;
// Debugging information helper class. The main purpose is to
// retrieve source file and line for error reporting. Linker may
// find reasonable number of errors in a single object file, so
// we cache debugging information in order to parse it only once
// for each object file we link.
template <class ELFT> class DIHelper {
public:
typedef typename ELFT::uint uintX_t;
DIHelper(InputFile *F);
std::string getLineInfo(uintX_t Offset);
private:
std::unique_ptr<llvm::DWARFDebugLine> DwarfLine;
};
// The root class of input files.
class InputFile {
public:
@ -171,6 +187,10 @@ public:
const Elf_Shdr *getSymbolTable() const { return this->Symtab; };
// DI helper allows manipilating debugging information for this
// object file. Used for error reporting.
DIHelper<ELFT> *getDIHelper();
// Get MIPS GP0 value defined by this file. This value represents the gp value
// used to create the relocatable object and required to support
// R_MIPS_GPREL16 / R_MIPS_GPREL32 relocations.
@ -184,6 +204,11 @@ public:
// using this buffer.
llvm::BumpPtrAllocator Alloc;
// Name of source file obtained from STT_FILE symbol value,
// or empty string if there is no such symbol in object file
// symbol table.
StringRef SourceFile;
private:
void
initializeSections(llvm::DenseSet<llvm::CachedHashStringRef> &ComdatGroups);
@ -211,6 +236,7 @@ private:
llvm::SpecificBumpPtrAllocator<InputSection<ELFT>> IAlloc;
llvm::SpecificBumpPtrAllocator<MergeInputSection<ELFT>> MAlloc;
llvm::SpecificBumpPtrAllocator<EhInputSection<ELFT>> EHAlloc;
std::unique_ptr<DIHelper<ELFT>> DIH;
};
// LazyObjectFile is analogous to ArchiveFile in the sense that

View File

@ -522,7 +522,48 @@ static typename ELFT::uint computeAddend(const elf::ObjectFile<ELFT> &File,
return Addend;
}
static void reportUndefined(SymbolBody &Sym) {
// Find symbol that encloses given offset. Used for error reporting.
template <class ELFT>
static DefinedRegular<ELFT> *getSymbolAt(InputSectionBase<ELFT> *S,
typename ELFT::uint Offset) {
for (SymbolBody *B : S->getFile()->getSymbols())
if (auto *D = dyn_cast<DefinedRegular<ELFT>>(B))
if (D->Value <= Offset && D->Value + D->Size > Offset && D->Section == S)
return D;
return nullptr;
}
template <class ELFT>
static std::string getLocation(SymbolBody &Sym, InputSectionBase<ELFT> &S,
typename ELFT::uint Offset) {
ObjectFile<ELFT> *File = S.getFile();
// First check if we can get desired values from debugging information.
std::string LineInfo = File->getDIHelper()->getLineInfo(Offset);
if (!LineInfo.empty())
return LineInfo;
// If don't have STT_FILE typed symbol in object file then
// use object file name.
std::string SrcFile = File->SourceFile;
if (SrcFile.empty())
SrcFile = Sym.File ? getFilename(Sym.File) : getFilename(File);
DefinedRegular<ELFT> *Encl = getSymbolAt(&S, Offset);
if (Encl && Encl->Type == STT_FUNC) {
StringRef Func = getSymbolName(*File, *Encl);
return SrcFile + " (function " +
(Config->Demangle ? demangle(Func) : Func.str()) + ")";
}
return (SrcFile + " (" + S.Name + "+0x" + Twine::utohexstr(Offset) + ")")
.str();
}
template <class ELFT>
static void reportUndefined(SymbolBody &Sym, InputSectionBase<ELFT> &S,
typename ELFT::uint Offset) {
if (Config->UnresolvedSymbols == UnresolvedPolicy::Ignore)
return;
@ -530,11 +571,10 @@ static void reportUndefined(SymbolBody &Sym) {
Config->UnresolvedSymbols != UnresolvedPolicy::NoUndef)
return;
std::string Msg = "undefined symbol: ";
Msg += Config->Demangle ? demangle(Sym.getName()) : Sym.getName().str();
std::string Msg =
getLocation(Sym, S, Offset) + ": undefined symbol '" +
(Config->Demangle ? demangle(Sym.getName()) : Sym.getName().str()) + "'";
if (Sym.File)
Msg += " in " + getFilename(Sym.File);
if (Config->UnresolvedSymbols == UnresolvedPolicy::Warn)
warn(Msg);
else
@ -583,7 +623,7 @@ static void scanRelocs(InputSectionBase<ELFT> &C, ArrayRef<RelTy> Rels) {
// We only report undefined symbols if they are referenced somewhere in the
// code.
if (!Body.isLocal() && Body.isUndefined() && !Body.symbol()->isWeak())
reportUndefined(Body);
reportUndefined(Body, C, RI.r_offset);
RelExpr Expr = Target->getRelExpr(Type, Body);
bool Preemptible = isPreemptible(Body, Type);

View File

@ -0,0 +1,3 @@
.file 1 "undef-debug.s"
.loc 1 3
.quad zed3

View File

@ -22,7 +22,7 @@
// Should not link because of undefined symbol _bar
// RUN: not ld.lld -o %t3 %t.o %tbar.o 2>&1 \
// RUN: | FileCheck --check-prefix=UNDEFINED %s
// UNDEFINED: undefined symbol: _bar
// UNDEFINED: error: {{.*}} (.bar+0x0): undefined symbol '_bar'
// Should fail if cannot find specified library (without -L switch)
// RUN: not ld.lld -o %t3 %t.o -lls 2>&1 \

View File

@ -2,9 +2,9 @@
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
# RUN: echo "SECTIONS { }" > %t.script
# RUN: not ld.lld %t.o -script %t.script -o %t 2>&1 | FileCheck %s
# CHECK: undefined symbol: _edata
# CHECK: undefined symbol: _etext
# CHECK: undefined symbol: _end
# CHECK: error: {{.*}} (.text+0x0): undefined symbol '_edata'
# CHECK: error: {{.*}} (.text+0x8): undefined symbol '_etext'
# CHECK: error: {{.*}} (.text+0x10): undefined symbol '_end'
.global _start,_end,_etext,_edata
.text

View File

@ -3,7 +3,7 @@
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.o
# RUN: echo "SECTIONS { }" > %t.script
# RUN: not ld.lld %t.o -script %t.script -o %t 2>&1 | FileCheck %s
# CHECK: undefined symbol: __ehdr_start
# CHECK: error: {{.*}} (.text+0x0): undefined symbol '__ehdr_start'
.text
.global _start, __ehdr_start

View File

@ -11,4 +11,4 @@ define void @_start() {
ret void
}
; CHECK: undefined symbol: foo in {{.*}}combined-lto-object-name.ll.tmp.o
; CHECK: error: ld-temp.o (function _start): undefined symbol 'foo'

View File

@ -9,7 +9,7 @@
// Should not link because of undefined symbol _bar
// RUN: not ld.lld -o %t/r %t/m.o 2>&1 \
// RUN: | FileCheck --check-prefix=UNDEFINED %s
// UNDEFINED: undefined symbol: _bar
// UNDEFINED: error: {{.*}} (.text+0x1): undefined symbol '_bar'
// We need to be sure that there is no suitable library in the /lib directory
// RUN: not ld.lld -o %t/r %t/m.o -L/lib -l:libls.a 2>&1 \

View File

@ -10,4 +10,4 @@
_start:
call __tls_get_addr
// CHECK: undefined symbol: __tls_get_addr
// CHECK: error: {{.*}} (.text+0x1): undefined symbol '__tls_get_addr'

View File

@ -1,15 +1,15 @@
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.o
# RUN: not ld.lld %t.o -o %t.so -shared 2>&1 | FileCheck %s
# CHECK: undefined symbol: hidden in {{.*}}
# CHECK: error: {{.*}} (.data+0x0): undefined symbol 'hidden'
.global hidden
.hidden hidden
# CHECK: undefined symbol: internal in {{.*}}
# CHECK: error: {{.*}} (.data+0x8): undefined symbol 'internal'
.global internal
.internal internal
# CHECK: undefined symbol: protected in {{.*}}
# CHECK: error: {{.*}} (.data+0x10): undefined symbol 'protected'
.global protected
.protected protected

View File

@ -1,17 +1,21 @@
# 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: llvm-mc -filetype=obj -triple=x86_64-pc-linux %p/Inputs/undef-debug.s -o %t3.o
# RUN: llvm-ar rc %t2.a %t2.o
# RUN: not ld.lld %t.o %t2.a -o %t.exe 2>&1 | FileCheck %s
# RUN: not ld.lld -pie %t.o %t2.a -o %t.exe 2>&1 | FileCheck %s
# CHECK: undefined symbol: foo in
# CHECK: undefined symbol: bar in
# CHECK: undefined symbol: foo(int) in
# CHECK: undefined symbol: zed2 in {{.*}}2.a({{.*}}.o)
# RUN: not ld.lld %t.o %t2.a %t3.o -o %t.exe 2>&1 | FileCheck %s
# RUN: not ld.lld -pie %t.o %t2.a %t3.o -o %t.exe 2>&1 | FileCheck %s
# CHECK: error: undef.s (.text+0x1): undefined symbol 'foo'
# CHECK: error: undef.s (.text+0x6): undefined symbol 'bar'
# CHECK: error: undef.s (.text+0x10): undefined symbol 'foo(int)'
# CHECK: error: {{.*}}2.a({{.*}}.o) (.text+0x0): undefined symbol 'zed2'
# CHECK: error: undef-debug.s (3): undefined symbol 'zed3'
# RUN: not ld.lld %t.o %t2.a -o %t.exe -no-demangle 2>&1 | \
# RUN: FileCheck -check-prefix=NO-DEMANGLE %s
# NO-DEMANGLE: undefined symbol: _Z3fooi in
# NO-DEMANGLE: error: undef.s (.text+0x10): undefined symbol '_Z3fooi'
.file "undef.s"
.globl _start
_start:

View File

@ -6,7 +6,7 @@
## Check that %t2.o contains undefined symbol undef.
# RUN: not ld.lld %t1.o %t2.o -o %t 2>&1 | \
# RUN: FileCheck -check-prefix=UNDCHECK %s
# UNDCHECK: undefined symbol: undef in {{.*}}2.o
# UNDCHECK: error: {{.*}}2.o (.text+0x1): undefined symbol 'undef'
## Error out if unknown option value was set.
# RUN: not ld.lld %t1.o %t2.o -o %t --unresolved-symbols=xxx 2>&1 | \
@ -19,7 +19,7 @@
# RUN: llvm-readobj %t1_1 > /dev/null 2>&1
# RUN: not ld.lld %t2.o -o %t1_2 --unresolved-symbols=ignore-all --no-undefined 2>&1 | \
# RUN: FileCheck -check-prefix=ERRUND %s
# ERRUND: undefined symbol: undef
# ERRUND: error: {{.*}} (.text+0x1): undefined symbol 'undef'
## Also ignore all should not produce error for symbols from DSOs.
# RUN: ld.lld %t1.o %t.so -o %t1_3 --unresolved-symbols=ignore-all
# RUN: llvm-readobj %t1_3 > /dev/null 2>&1

View File

@ -2,7 +2,7 @@
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
# RUN: not ld.lld %t.o %S/Inputs/verneed1.so -o %t 2>&1 | FileCheck %s
# CHECK: undefined symbol: f3 in
# CHECK: error: {{.*}} (.text+0x1): undefined symbol 'f3'
.globl _start
_start:
call f3

View File

@ -2,6 +2,6 @@
# RUN: ld.lld -shared %t.o -o %t1.so
# RUN: not ld.lld -z defs -shared %t.o -o %t1.so 2>&1 | FileCheck -check-prefix=ERR %s
# ERR: undefined symbol: foo
# ERR: error: {{.*}} (.text+0x1): undefined symbol 'foo'
callq foo@PLT