From f2036a15d3714fc9cfc8935634814d1c4e4263fa Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 28 Mar 2020 15:48:38 -0700 Subject: [PATCH] [ELF] Print symbols with non-default versions for better "undefined symbol" diagnostics When reporting an "undefined symbol" diagnostic: * We don't print @ for the reference. * We don't print @ or @@ for the definition. https://bugs.llvm.org/show_bug.cgi?id=45318 This can lead to confusing diagnostics: ``` // foo may be foo@v2 ld.lld: error: undefined symbol: foo >>> referenced by t1.o:(.text+0x1) // foo may be foo@v1 or foo@@v1 >>> did you mean: foo >>> defined in: t.so ``` There are 2 ways a symbol in symtab may get truncated: * A @@ definition may be truncated *early* by SymbolTable::insert(). The name ends with a '\0'. * A @ definition/reference may be truncated *later* by Symbol::parseSymbolVersion(). The name ends with a '@'. This patch detects the second case and improves the diagnostics. The first case is not improved but the second case is sufficient to make diagnostics not confusing. Reviewed By: ruiu Differential Revision: https://reviews.llvm.org/D76999 --- lld/ELF/Symbols.cpp | 13 +++++++- lld/ELF/Symbols.h | 1 + lld/test/ELF/undef-suggest-version.s | 57 ++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 lld/test/ELF/undef-suggest-version.s diff --git a/lld/ELF/Symbols.cpp b/lld/ELF/Symbols.cpp index cb7ee88..42c8a71 100644 --- a/lld/ELF/Symbols.cpp +++ b/lld/ELF/Symbols.cpp @@ -31,7 +31,18 @@ static std::string demangle(StringRef symName) { return std::string(symName); } -std::string toString(const elf::Symbol &b) { return demangle(b.getName()); } +std::string toString(const elf::Symbol &sym) { + StringRef name = sym.getName(); + std::string ret = demangle(name); + + // If sym has a non-default version, its name may have been truncated at '@' + // by Symbol::parseSymbolVersion(). Add the trailing part. This check is safe + // because every symbol name ends with '\0'. + if (name.data()[name.size()] == '@') + ret += name.data() + name.size(); + return ret; +} + std::string toELFString(const Archive::Symbol &b) { return demangle(b.getName()); } diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h index ac60619..ebee4af 100644 --- a/lld/ELF/Symbols.h +++ b/lld/ELF/Symbols.h @@ -21,6 +21,7 @@ #include "llvm/Object/ELF.h" namespace lld { +// Returns a string representation for a symbol for diagnostics. std::string toString(const elf::Symbol &); // There are two different ways to convert an Archive::Symbol to a string: diff --git a/lld/test/ELF/undef-suggest-version.s b/lld/test/ELF/undef-suggest-version.s new file mode 100644 index 0000000..790b9fc --- /dev/null +++ b/lld/test/ELF/undef-suggest-version.s @@ -0,0 +1,57 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o +# RUN: echo 'v1 {bar;};' > %t.ver +# RUN: ld.lld -shared --version-script %t.ver %t.o -o %t.so + +## For an unversioned undefined symbol, check we can suggest the symbol with the +## default version. +# RUN: echo 'call bat' | llvm-mc -filetype=obj -triple=x86_64 - -o %tdef1.o +# RUN: not ld.lld %t.so %tdef1.o -o /dev/null 2>&1 | FileCheck --check-prefix=DEFAULT1 %s + +# DEFAULT1: error: undefined symbol: bat +# DEFAULT1-NEXT: >>> referenced by {{.*}}.o:(.text+0x1) +# DEFAULT1-NEXT: >>> did you mean: bar{{$}} +# DEFAULT1-NEXT: >>> defined in: {{.*}}.so + +## For a versioned undefined symbol, check we can suggest the symbol with the +## default version. +# RUN: echo '.symver bar.v2,bar@v2; call bar.v2' | llvm-mc -filetype=obj -triple=x86_64 - -o %tdef2.o +# RUN: not ld.lld %t.so %tdef2.o -o /dev/null 2>&1 | FileCheck --check-prefix=DEFAULT2 %s + +# DEFAULT2: error: undefined symbol: bar@v2 +# DEFAULT2-NEXT: >>> referenced by {{.*}}.o:(.text+0x1) +# DEFAULT2-NEXT: >>> did you mean: bar{{$}} +# DEFAULT2-NEXT: >>> defined in: {{.*}}.so + +## For an unversioned undefined symbol, check we can suggest a symbol with +## a non-default version. +# RUN: echo 'call foo; call _Z3fooi' | llvm-mc -filetype=obj -triple=x86_64 - -o %thidden1.o +# RUN: not ld.lld %t.so %thidden1.o -o /dev/null 2>&1 | FileCheck --check-prefix=HIDDEN1 %s + +# HIDDEN1: error: undefined symbol: foo +# HIDDEN1-NEXT: >>> referenced by {{.*}}.o:(.text+0x1) +# HIDDEN1-NEXT: >>> did you mean: foo@v1 +# HIDDEN1-NEXT: >>> defined in: {{.*}}.so +# HIDDEN1-EMPTY: +# HIDDEN1-NEXT: error: undefined symbol: foo(int) +# HIDDEN1-NEXT: >>> referenced by {{.*}}.o:(.text+0x6) +# HIDDEN1-NEXT: >>> did you mean: foo(int)@v1 +# HIDDEN1-NEXT: >>> defined in: {{.*}}.so + +## For a versioned undefined symbol, check we can suggest a symbol with +## a different version. +# RUN: echo '.symver foo.v2,foo@v2; call foo.v2' | llvm-mc -filetype=obj -triple=x86_64 - -o %thidden2.o +# RUN: not ld.lld %t.so %thidden2.o -o /dev/null 2>&1 | FileCheck --check-prefix=HIDDEN2 %s + +# HIDDEN2: error: undefined symbol: foo@v2 +# HIDDEN2-NEXT: >>> referenced by {{.*}}.o:(.text+0x1) +# HIDDEN2-NEXT: >>> did you mean: foo@v1 +# HIDDEN2-NEXT: >>> defined in: {{.*}}.so + +## %t.so exports bar@@v1 and two VERSYM_HIDDEN symbols: foo@v1 and _Z3fooi@v1. +.globl foo.v1, _Z3fooi.v1, bar +.symver foo.v1,foo@v1 +.symver _Z3fooi.v1,_Z3fooi@v1 +foo.v1: +_Z3fooi.v1: +bar: -- 2.7.4