[lld] r330235 - COFF: Friendlier undefined symbol errors.

Peter Collingbourne via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 17 16:32:33 PDT 2018


Author: pcc
Date: Tue Apr 17 16:32:33 2018
New Revision: 330235

URL: http://llvm.org/viewvc/llvm-project?rev=330235&view=rev
Log:
COFF: Friendlier undefined symbol errors.

Summary:
This change does three things:
- Try to find the file and line number of an undefined symbol
  reference by reading codeview debug info.
- Try to find the name of the function or global variable with the
  undefined symbol reference by searching the object file's symbol
  table.
- Prints the information in the same style as the ELF linker.

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

Added:
    lld/trunk/test/COFF/undefined-symbol-cv.s
    lld/trunk/test/COFF/undefined-symbol.s
Modified:
    lld/trunk/COFF/Chunks.h
    lld/trunk/COFF/InputFiles.cpp
    lld/trunk/COFF/PDB.cpp
    lld/trunk/COFF/PDB.h
    lld/trunk/COFF/SymbolTable.cpp
    lld/trunk/test/COFF/filename-casing.s
    lld/trunk/test/COFF/force.test
    lld/trunk/test/COFF/nodefaultlib.test

Modified: lld/trunk/COFF/Chunks.h
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/Chunks.h?rev=330235&r1=330234&r2=330235&view=diff
==============================================================================
--- lld/trunk/COFF/Chunks.h (original)
+++ lld/trunk/COFF/Chunks.h Tue Apr 17 16:32:33 2018
@@ -213,10 +213,11 @@ public:
   // The COMDAT leader symbol if this is a COMDAT chunk.
   DefinedRegular *Sym = nullptr;
 
+  ArrayRef<coff_relocation> Relocs;
+
 private:
   StringRef SectionName;
   std::vector<SectionChunk *> AssocChildren;
-  ArrayRef<coff_relocation> Relocs;
 
   // Used by the garbage collector.
   bool Live;

Modified: lld/trunk/COFF/InputFiles.cpp
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/InputFiles.cpp?rev=330235&r1=330234&r2=330235&view=diff
==============================================================================
--- lld/trunk/COFF/InputFiles.cpp (original)
+++ lld/trunk/COFF/InputFiles.cpp Tue Apr 17 16:32:33 2018
@@ -170,8 +170,8 @@ SectionChunk *ObjFile::readSection(uint3
   // 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)

Modified: lld/trunk/COFF/PDB.cpp
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/PDB.cpp?rev=330235&r1=330234&r2=330235&view=diff
==============================================================================
--- lld/trunk/COFF/PDB.cpp (original)
+++ lld/trunk/COFF/PDB.cpp Tue Apr 17 16:32:33 2018
@@ -1170,3 +1170,145 @@ void PDBLinker::commit() {
   // Write to a file.
   ExitOnErr(Builder.commit(Config->PDBPath));
 }
+
+static Expected<StringRef>
+getFileName(const DebugStringTableSubsectionRef &Strings,
+            const DebugChecksumsSubsectionRef &Checksums, uint32_t FileID) {
+  auto Iter = Checksums.getArray().at(FileID);
+  if (Iter == Checksums.getArray().end())
+    return make_error<CodeViewError>(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<uint32_t, uint32_t> Secrels;
+    for (const coff_relocation &R : DbgC->Relocs) {
+      if (R.Type != SecrelReloc)
+        continue;
+
+      if (auto *S = dyn_cast_or_null<DefinedRegular>(
+              C->File->getSymbols()[R.SymbolTableIndex]))
+        if (S->getChunk() == C)
+          Secrels[R.VirtualAddress] = S->getValue();
+    }
+
+    ArrayRef<uint8_t> 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<uint8_t> 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<StringRef, uint32_t> 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};
+}

Modified: lld/trunk/COFF/PDB.h
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/PDB.h?rev=330235&r1=330234&r2=330235&view=diff
==============================================================================
--- lld/trunk/COFF/PDB.h (original)
+++ lld/trunk/COFF/PDB.h Tue Apr 17 16:32:33 2018
@@ -22,12 +22,16 @@ union DebugInfo;
 namespace lld {
 namespace coff {
 class OutputSection;
+class SectionChunk;
 class SymbolTable;
 
 void createPDB(SymbolTable *Symtab,
                llvm::ArrayRef<OutputSection *> OutputSections,
                llvm::ArrayRef<uint8_t> SectionTable,
                const llvm::codeview::DebugInfo &BuildId);
+
+std::pair<llvm::StringRef, uint32_t> getFileLine(const SectionChunk *C,
+                                                 uint32_t Addr);
 }
 }
 

Modified: lld/trunk/COFF/SymbolTable.cpp
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/SymbolTable.cpp?rev=330235&r1=330234&r2=330235&view=diff
==============================================================================
--- lld/trunk/COFF/SymbolTable.cpp (original)
+++ lld/trunk/COFF/SymbolTable.cpp Tue Apr 17 16:32:33 2018
@@ -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 @@ static void errorOrWarn(const Twine &S)
     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<DefinedRegular>(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<StringRef, uint32_t> FileLine;
+  };
+  std::vector<Location> Locations;
+
+  for (Chunk *C : File->getChunks()) {
+    auto *SC = dyn_cast<SectionChunk>(C);
+    if (!SC)
+      continue;
+    for (const coff_relocation &R : SC->Relocs) {
+      if (R.SymbolTableIndex != SymIndex)
+        continue;
+      std::pair<StringRef, uint32_t> 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<Symbol *, 8> Undefs;
   DenseMap<Symbol *, Symbol *> LocalImports;
@@ -127,11 +188,14 @@ void SymbolTable::reportRemainingUndefin
   }
 
   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: " +

Modified: lld/trunk/test/COFF/filename-casing.s
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/filename-casing.s?rev=330235&r1=330234&r2=330235&view=diff
==============================================================================
--- lld/trunk/test/COFF/filename-casing.s (original)
+++ lld/trunk/test/COFF/filename-casing.s Tue Apr 17 16:32:33 2018
@@ -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:

Modified: lld/trunk/test/COFF/force.test
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/force.test?rev=330235&r1=330234&r2=330235&view=diff
==============================================================================
--- lld/trunk/test/COFF/force.test (original)
+++ lld/trunk/test/COFF/force.test Tue Apr 17 16:32:33 2018
@@ -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:

Modified: lld/trunk/test/COFF/nodefaultlib.test
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/nodefaultlib.test?rev=330235&r1=330234&r2=330235&view=diff
==============================================================================
--- lld/trunk/test/COFF/nodefaultlib.test (original)
+++ lld/trunk/test/COFF/nodefaultlib.test Tue Apr 17 16:32:33 2018
@@ -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

Added: lld/trunk/test/COFF/undefined-symbol-cv.s
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/undefined-symbol-cv.s?rev=330235&view=auto
==============================================================================
--- lld/trunk/test/COFF/undefined-symbol-cv.s (added)
+++ lld/trunk/test/COFF/undefined-symbol-cv.s Tue Apr 17 16:32:33 2018
@@ -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

Added: lld/trunk/test/COFF/undefined-symbol.s
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/undefined-symbol.s?rev=330235&view=auto
==============================================================================
--- lld/trunk/test/COFF/undefined-symbol.s (added)
+++ lld/trunk/test/COFF/undefined-symbol.s Tue Apr 17 16:32:33 2018
@@ -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"




More information about the llvm-commits mailing list