Index: COFF/Chunks.h =================================================================== --- COFF/Chunks.h +++ COFF/Chunks.h @@ -213,10 +213,11 @@ // The COMDAT leader symbol if this is a COMDAT chunk. DefinedRegular *Sym = nullptr; + ArrayRef Relocs; + private: StringRef SectionName; std::vector AssocChildren; - ArrayRef Relocs; // Used by the garbage collector. bool Live; Index: COFF/InputFiles.cpp =================================================================== --- COFF/InputFiles.cpp +++ COFF/InputFiles.cpp @@ -170,8 +170,8 @@ // CodeView needs a linker support. We need to interpret and debug // info, and then write it to a separate .pdb file. - // Ignore debug info unless /debug is given. - if (!Config->Debug && Name.startswith(".debug")) + // Ignore DWARF debug info unless /debug is given. + if (!Config->Debug && Name.startswith(".debug_")) return nullptr; if (Sec->Characteristics & llvm::COFF::IMAGE_SCN_LNK_REMOVE) Index: COFF/PDB.h =================================================================== --- COFF/PDB.h +++ COFF/PDB.h @@ -22,12 +22,16 @@ namespace lld { namespace coff { class OutputSection; +class SectionChunk; class SymbolTable; void createPDB(SymbolTable *Symtab, llvm::ArrayRef OutputSections, llvm::ArrayRef SectionTable, const llvm::codeview::DebugInfo &BuildId); + +std::pair getFileLine(const SectionChunk *C, + uint32_t Addr); } } Index: COFF/PDB.cpp =================================================================== --- COFF/PDB.cpp +++ COFF/PDB.cpp @@ -1170,3 +1170,145 @@ // Write to a file. ExitOnErr(Builder.commit(Config->PDBPath)); } + +static Expected +getFileName(const DebugStringTableSubsectionRef &Strings, + const DebugChecksumsSubsectionRef &Checksums, uint32_t FileID) { + auto Iter = Checksums.getArray().at(FileID); + if (Iter == Checksums.getArray().end()) + return make_error(cv_error_code::no_records); + uint32_t Offset = Iter->FileNameOffset; + return Strings.getString(Offset); +} + +static uint32_t getSecrelReloc() { + switch (Config->Machine) { + case AMD64: + return COFF::IMAGE_REL_AMD64_SECREL; + case I386: + return COFF::IMAGE_REL_I386_SECREL; + case ARMNT: + return COFF::IMAGE_REL_ARM_SECREL; + case ARM64: + return COFF::IMAGE_REL_ARM64_SECREL; + default: + llvm_unreachable("unknown machine type"); + } +} + +// Try to find a line table for the given offset Addr into the given chunk C. +// If a line table was found, the line table, the string and checksum tables +// that are used to interpret the line table, and the offset of Addr in the line +// table are stored in the output arguments. Returns whether a line table was +// found. +static bool findLineTable(const SectionChunk *C, uint32_t Addr, + DebugStringTableSubsectionRef &CVStrTab, + DebugChecksumsSubsectionRef &Checksums, + DebugLinesSubsectionRef &Lines, + uint32_t &OffsetInLinetable) { + ExitOnError ExitOnErr; + uint32_t SecrelReloc = getSecrelReloc(); + + for (SectionChunk *DbgC : C->File->getDebugChunks()) { + if (DbgC->getSectionName() != ".debug$S") + continue; + + // Build a mapping of SECREL relocations in DbgC that refer to C. + DenseMap Secrels; + for (const coff_relocation &R : DbgC->Relocs) { + if (R.Type != SecrelReloc) + continue; + + if (auto *S = dyn_cast_or_null( + C->File->getSymbols()[R.SymbolTableIndex])) + if (S->getChunk() == C) + Secrels[R.VirtualAddress] = S->getValue(); + } + + ArrayRef Contents = + consumeDebugMagic(DbgC->getContents(), ".debug$S"); + DebugSubsectionArray Subsections; + BinaryStreamReader Reader(Contents, support::little); + ExitOnErr(Reader.readArray(Subsections, Contents.size())); + + for (const DebugSubsectionRecord &SS : Subsections) { + switch (SS.kind()) { + case DebugSubsectionKind::StringTable: { + assert(!CVStrTab.valid() && + "Encountered multiple string table subsections!"); + ExitOnErr(CVStrTab.initialize(SS.getRecordData())); + break; + } + case DebugSubsectionKind::FileChecksums: + assert(!Checksums.valid() && + "Encountered multiple checksum subsections!"); + ExitOnErr(Checksums.initialize(SS.getRecordData())); + break; + case DebugSubsectionKind::Lines: { + ArrayRef Bytes; + auto Ref = SS.getRecordData(); + ExitOnErr(Ref.readLongestContiguousChunk(0, Bytes)); + size_t OffsetInDbgC = Bytes.data() - DbgC->getContents().data(); + + // Check whether this line table refers to C. + auto I = Secrels.find(OffsetInDbgC); + if (I == Secrels.end()) + break; + + // Check whether this line table covers Addr in C. + DebugLinesSubsectionRef LinesTmp; + ExitOnErr(LinesTmp.initialize(BinaryStreamReader(Ref))); + uint32_t OffsetInC = I->second + LinesTmp.header()->RelocOffset; + if (Addr < OffsetInC || Addr >= OffsetInC + LinesTmp.header()->CodeSize) + break; + + assert(!Lines.header() && + "Encountered multiple line tables for function!"); + ExitOnErr(Lines.initialize(BinaryStreamReader(Ref))); + OffsetInLinetable = Addr - OffsetInC; + break; + } + default: + break; + } + + if (CVStrTab.valid() && Checksums.valid() && Lines.header()) + return true; + } + } + + return false; +} + +// Use CodeView line tables to resolve a file and line number for the given +// offset into the given chunk and return them, or {"", 0} if a line table was +// not found. +std::pair coff::getFileLine(const SectionChunk *C, + uint32_t Addr) { + ExitOnError ExitOnErr; + + DebugStringTableSubsectionRef CVStrTab; + DebugChecksumsSubsectionRef Checksums; + DebugLinesSubsectionRef Lines; + uint32_t OffsetInLinetable; + + if (!findLineTable(C, Addr, CVStrTab, Checksums, Lines, OffsetInLinetable)) + return {"", 0}; + + uint32_t NameIndex; + uint32_t LineNumber; + for (LineColumnEntry &Entry : Lines) { + for (const LineNumberEntry &LN : Entry.LineNumbers) { + if (LN.Offset > OffsetInLinetable) { + StringRef Filename = + ExitOnErr(getFileName(CVStrTab, Checksums, NameIndex)); + return {Filename, LineNumber}; + } + LineInfo LI(LN.Flags); + NameIndex = Entry.NameIndex; + LineNumber = LI.getStartLine(); + } + } + StringRef Filename = ExitOnErr(getFileName(CVStrTab, Checksums, NameIndex)); + return {Filename, LineNumber}; +} Index: COFF/SymbolTable.cpp =================================================================== --- COFF/SymbolTable.cpp +++ COFF/SymbolTable.cpp @@ -11,6 +11,7 @@ #include "Config.h" #include "Driver.h" #include "LTO.h" +#include "PDB.h" #include "Symbols.h" #include "lld/Common/ErrorHandler.h" #include "lld/Common/Memory.h" @@ -64,6 +65,66 @@ error(S); } +// Returns the name of the symbol in SC whose value is <= Addr that is closest +// to Addr. This is generally the name of the global variable or function whose +// definition contains Addr. +static StringRef getSymbolName(SectionChunk *SC, uint32_t Addr) { + DefinedRegular *Candidate = nullptr; + + for (Symbol *S : SC->File->getSymbols()) { + auto *D = dyn_cast_or_null(S); + if (!D || D->getChunk() != SC || D->getValue() > Addr || + (Candidate && D->getValue() < Candidate->getValue())) + continue; + + Candidate = D; + } + + if (!Candidate) + return ""; + return Candidate->getName(); +} + +static std::string getSymbolLocations(ObjFile *File, uint32_t SymIndex) { + struct Location { + StringRef SymName; + std::pair FileLine; + }; + std::vector Locations; + + for (Chunk *C : File->getChunks()) { + auto *SC = dyn_cast(C); + if (!SC) + continue; + for (const coff_relocation &R : SC->Relocs) { + if (R.SymbolTableIndex != SymIndex) + continue; + std::pair FileLine = + getFileLine(SC, R.VirtualAddress); + StringRef SymName = getSymbolName(SC, R.VirtualAddress); + if (!FileLine.first.empty() || !SymName.empty()) + Locations.push_back({SymName, FileLine}); + } + } + + if (Locations.empty()) + return "\n>>> referenced by " + toString(File) + "\n"; + + std::string Out; + llvm::raw_string_ostream OS(Out); + for (Location Loc : Locations) { + OS << "\n>>> referenced by "; + if (!Loc.FileLine.first.empty()) + OS << Loc.FileLine.first << ":" << Loc.FileLine.second + << "\n>>> "; + OS << toString(File); + if (!Loc.SymName.empty()) + OS << ":(" << Loc.SymName << ')'; + } + OS << '\n'; + return OS.str(); +} + void SymbolTable::reportRemainingUndefines() { SmallPtrSet Undefs; DenseMap LocalImports; @@ -127,11 +188,14 @@ } for (ObjFile *File : ObjFile::Instances) { + size_t SymIndex = -1ull; for (Symbol *Sym : File->getSymbols()) { + ++SymIndex; if (!Sym) continue; if (Undefs.count(Sym)) - errorOrWarn(toString(File) + ": undefined symbol: " + Sym->getName()); + errorOrWarn("undefined symbol: " + Sym->getName() + + getSymbolLocations(File, SymIndex)); if (Config->WarnLocallyDefinedImported) if (Symbol *Imp = LocalImports.lookup(Sym)) warn(toString(File) + ": locally defined symbol imported: " + Index: test/COFF/filename-casing.s =================================================================== --- test/COFF/filename-casing.s +++ test/COFF/filename-casing.s @@ -6,8 +6,10 @@ # RUN: llvm-lib /out:%T/MixedCase.lib %T/MixedCase.obj # RUN: not lld-link /machine:x64 /entry:main %T/MixedCase.lib 2>&1 | FileCheck -check-prefix=ARCHIVE %s -# OBJECT: MixedCase.obj: undefined symbol: f -# ARCHIVE: MixedCase.lib(MixedCase.obj): undefined symbol: f +# OBJECT: undefined symbol: f +# OBJECT-NEXT: >>> referenced by {{.*}}MixedCase.obj:(main) +# ARCHIVE: undefined symbol: f +# ARCHIVE-NEXT: >>> referenced by {{.*}}MixedCase.lib(MixedCase.obj):(main) .globl main main: Index: test/COFF/force.test =================================================================== --- test/COFF/force.test +++ test/COFF/force.test @@ -4,8 +4,10 @@ # RUN: lld-link /out:%t.exe /entry:main %t.obj /force >& %t.log # RUN: FileCheck -check-prefix=WARN %s < %t.log -# ERROR: error: {{.*}}.obj: undefined symbol: foo -# WARN: warning: {{.*}}.obj: undefined symbol: foo +# ERROR: error: undefined symbol: foo +# ERROR-NEXT: >>> referenced by {{.*}}.obj +# WARN: warning: undefined symbol: foo +# WARN-NEXT: >>> referenced by {{.*}}.obj --- !COFF header: Index: test/COFF/nodefaultlib.test =================================================================== --- test/COFF/nodefaultlib.test +++ test/COFF/nodefaultlib.test @@ -21,7 +21,8 @@ CHECK1: error: could not open hello64.obj: {{[Nn]}}o such file or directory CHECK2: error: could not open hello64: {{[Nn]}}o such file or directory -CHECK3: error: {{.*}}hello64.obj: undefined symbol: MessageBoxA +CHECK3: error: undefined symbol: MessageBoxA +CHECK3-NEXT: >>> referenced by {{.*}}hello64.obj:(main) # RUN: lld-link /libpath:%T /out:%t.exe /entry:main \ # RUN: /subsystem:console hello64.obj /defaultlib:std64.lib Index: test/COFF/undefined-symbol-cv.s =================================================================== --- test/COFF/undefined-symbol-cv.s +++ test/COFF/undefined-symbol-cv.s @@ -0,0 +1,61 @@ +# RUN: llvm-mc -triple=x86_64-windows-msvc -filetype=obj -o %t.obj %s +# RUN: not lld-link /out:%t.exe %t.obj 2>&1 | FileCheck %s + +# CHECK: error: undefined symbol: ?foo@@YAHXZ +# CHECK-NEXT: >>> referenced by file1.cpp:1 +# CHECK-NEXT: >>> {{.*}}.obj:(main) +# CHECK-NEXT: >>> referenced by file1.cpp:2 +# CHECK-NEXT: >>> {{.*}}.obj:(main) + +# CHECK: error: undefined symbol: ?bar@@YAHXZ +# CHECK-NEXT: >>> referenced by file2.cpp:3 +# CHECK-NEXT: >>> {{.*}}.obj:(main) +# CHECK-NEXT: >>> referenced by file1.cpp:4 +# CHECK-NEXT: >>> {{.*}}.obj:(f1) + +# CHECK: error: undefined symbol: ?baz@@YAHXZ +# CHECK-NEXT: >>> referenced by file1.cpp:5 +# CHECK-NEXT: >>> {{.*}}.obj:(f2) + + .cv_file 1 "file1.cpp" "EDA15C78BB573E49E685D8549286F33C" 1 + .cv_file 2 "file2.cpp" "EDA15C78BB573E49E685D8549286F33D" 1 + + .section .text,"xr",one_only,main +.globl main +main: + .cv_func_id 0 + .cv_loc 0 1 1 0 is_stmt 0 + call "?foo@@YAHXZ" + .cv_loc 0 1 2 0 + call "?foo@@YAHXZ" + .cv_loc 0 2 3 0 + call "?bar@@YAHXZ" +.Lfunc_end0: + +f1: + .cv_func_id 1 + .cv_loc 1 1 4 0 is_stmt 0 + call "?bar@@YAHXZ" +.Lfunc_end1: + + .section .text,"xr",one_only,f2 +.globl f2 +f2: + .cv_func_id 2 + .cv_loc 2 1 5 0 is_stmt 0 + call "?baz@@YAHXZ" +.Lfunc_end2: + + .section .debug$S,"dr",associative,main + .long 4 + .cv_linetable 0, main, .Lfunc_end0 + .cv_linetable 1, f1, .Lfunc_end1 + + .section .debug$S,"dr",associative,f2 + .long 4 + .cv_linetable 2, f2, .Lfunc_end2 + + .section .debug$S,"dr" + .long 4 + .cv_filechecksums + .cv_stringtable Index: test/COFF/undefined-symbol.s =================================================================== --- test/COFF/undefined-symbol.s +++ test/COFF/undefined-symbol.s @@ -0,0 +1,29 @@ +# RUN: llvm-mc -triple=x86_64-windows-msvc -filetype=obj -o %t.obj %s +# RUN: not lld-link /out:%t.exe %t.obj 2>&1 | FileCheck %s + +# CHECK: error: undefined symbol: ?foo@@YAHXZ +# CHECK-NEXT: >>> referenced by {{.*}}.obj:(main) +# CHECK-NEXT: >>> referenced by {{.*}}.obj:(main) + +# CHECK: error: undefined symbol: ?bar@@YAHXZ +# CHECK-NEXT: >>> referenced by {{.*}}.obj:(main) +# CHECK-NEXT: >>> referenced by {{.*}}.obj:(f1) + +# CHECK: error: undefined symbol: ?baz@@YAHXZ +# CHECK-NEXT: >>> referenced by {{.*}}.obj:(f2) + + .section .text,"xr",one_only,main +.globl main +main: + call "?foo@@YAHXZ" + call "?foo@@YAHXZ" + call "?bar@@YAHXZ" + +f1: + call "?bar@@YAHXZ" +.Lfunc_end1: + + .section .text,"xr",one_only,f2 +.globl f2 +f2: + call "?baz@@YAHXZ"