From 843c2b2303004c1a7e4fa8037905fbc70601b155 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Tue, 1 Dec 2020 08:59:54 -0800 Subject: [PATCH] [ELF] Error for undefined foo@v1 If an object file has an undefined foo@v1, we emit a dynamic symbol foo. This is incorrect if at runtime a shared object provides the non-default version foo@v1 (the undefined foo may bind to foo@@v2, for example). GNU ld issues an error for this case, even if foo@v1 is undefined weak (https://sourceware.org/bugzilla/show_bug.cgi?id=3351). This behavior makes sense because to represent an undefined foo@v1, we have to construct a Verneed entry. However, without knowing the defining filename, we cannot construct a Verneed entry (Verneed::vn_file is unavailable). This patch implements the error. Depends on D92258 Reviewed By: grimar Differential Revision: https://reviews.llvm.org/D92260 --- lld/ELF/Relocations.cpp | 10 ++++++++- lld/ELF/Symbols.h | 2 +- lld/test/ELF/lto/version-script2.ll | 23 +++++++++++++++---- lld/test/ELF/symver.s | 35 ++++++++++++++++++----------- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp index 7860aeeefcc3..c545f1c81918 100644 --- a/lld/ELF/Relocations.cpp +++ b/lld/ELF/Relocations.cpp @@ -945,7 +945,15 @@ template void elf::reportUndefinedSymbols() { // Returns true if the undefined symbol will produce an error message. static bool maybeReportUndefined(Symbol &sym, InputSectionBase &sec, uint64_t offset) { - if (!sym.isUndefined() || sym.isWeak()) + if (!sym.isUndefined()) + return false; + // If versioned, issue an error (even if the symbol is weak) because we don't + // know the defining filename which is required to construct a Verneed entry. + if (*sym.getVersionSuffix() == '@') { + undefs.push_back({&sym, {{&sec, offset}}, false}); + return true; + } + if (sym.isWeak()) return false; bool canBeExternal = !sym.isLocal() && sym.visibility == STV_DEFAULT; diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h index 48428c24f9c8..bb87ac75f9c6 100644 --- a/lld/ELF/Symbols.h +++ b/lld/ELF/Symbols.h @@ -183,7 +183,7 @@ public: // For @@, the name has been truncated by insert(). For @, the name has been // truncated by Symbol::parseSymbolVersion(). const char *getVersionSuffix() const { - assert(nameSize != (uint32_t)-1); + (void)getName(); return nameData + nameSize; } diff --git a/lld/test/ELF/lto/version-script2.ll b/lld/test/ELF/lto/version-script2.ll index b8c8b56b4317..154e9facd450 100644 --- a/lld/test/ELF/lto/version-script2.ll +++ b/lld/test/ELF/lto/version-script2.ll @@ -4,13 +4,21 @@ ;; otherwise we may get a symbol named "foo@@VER1", but not "foo" with the ;; version VER1. -; RUN: llvm-as %s -o %t.o +; RUN: split-file %s %t +; RUN: llvm-as %t/ir -o %t.o +; RUN: llvm-mc -filetype=obj -triple=x86_64 %t/asm -o %tbar.o +; RUN: ld.lld %tbar.o -shared --soname=tbar --version-script %t.script -o %tbar.so ; RUN: echo "VER1 {};" > %t.script -; RUN: ld.lld %t.o -o %t.so -shared --version-script %t.script + +;; Emit an error if bar@VER1 is not defined. +; RUN: not ld.lld %t.o -o /dev/null -shared --version-script %t.script 2>&1 | FileCheck %s --check-prefix=UNDEF + +; UNDEF: error: undefined symbol: bar@VER1 + +; RUN: ld.lld %t.o %tbar.so -o %t.so -shared --version-script %t.script ; RUN: llvm-readelf --dyn-syms %t.so | FileCheck %s -;; For non-relocatable output, @ in symbol names has no effect on undefined symbols. -; CHECK: UND bar{{$}} +; CHECK: UND bar@VER1 ; CHECK-NEXT: {{[1-9]}} foo@@VER1 ;; For relocatable output, @ should be retained in the symbol name. @@ -21,6 +29,7 @@ ; RELOCATABLE: {{[1-9]}} foo@@VER1 ; RELOCATABLE-NEXT: UND bar@VER1 +;--- ir target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" @@ -28,3 +37,9 @@ module asm ".global foo" module asm "foo: call bar" module asm ".symver foo,foo@@@VER1" module asm ".symver bar,bar@@@VER1" + +;--- asm +.globl bar +.symver bar,bar@@@VER1 +bar: + ret diff --git a/lld/test/ELF/symver.s b/lld/test/ELF/symver.s index 2858e835622a..7111f4264f3d 100644 --- a/lld/test/ELF/symver.s +++ b/lld/test/ELF/symver.s @@ -5,6 +5,7 @@ # RUN: llvm-mc -filetype=obj -triple=x86_64 %t/ref.s -o %t/ref.o # RUN: llvm-mc -filetype=obj -triple=x86_64 %t/ref1.s -o %t/ref1.o # RUN: llvm-mc -filetype=obj -triple=x86_64 %t/ref1p.s -o %t/ref1p.o +# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/ref1w.s -o %t/ref1w.o # RUN: llvm-mc -filetype=obj -triple=x86_64 %t/def1.s -o %t/def1.o # RUN: llvm-mc -filetype=obj -triple=x86_64 %t/def1w.s -o %t/def1w.o # RUN: llvm-mc -filetype=obj -triple=x86_64 %t/hid1.s -o %t/hid1.o @@ -45,13 +46,16 @@ # CHECK-EMPTY: ## foo@@v2 does not resolve undefined foo@v1. -## TODO Undefined 'foo@v1' should be errored. -# RUN: ld.lld -shared --soname=t --version-script=%t/ver %t/ref.o %t/ref1.o %t/def2.o -o %t2 -# RUN: llvm-readelf --dyn-syms %t2 | FileCheck %s --check-prefix=CHECK2 +# RUN: not ld.lld -shared --soname=t --version-script=%t/ver %t/ref.o %t/ref1.o %t/def2.o \ +# RUN: -o /dev/null 2>&1 | FileCheck %s --check-prefix=UNDEF -# CHECK2: 1: {{.*}} NOTYPE GLOBAL DEFAULT UND foo{{$}} -# CHECK2-NEXT: 2: {{.*}} NOTYPE GLOBAL DEFAULT [[#]] foo@@v2 -# CHECK2-EMPTY: +# UNDEF: error: undefined symbol: foo@v1 + +## An undefined weak unversioned symbol is not errored. However, an undefined +## weak versioned symbol should still be errored because we cannot construct +## a Verneed entry (Verneed::vn_file is unavailable). +# RUN: not ld.lld -shared --version-script=%t/ver %t/ref1w.o -o /dev/null 2>&1 | \ +# RUN: FileCheck %s --check-prefix=UNDEF ## foo@@v2 resolves undefined foo while foo@v1 resolves undefined foo@v1. # RUN: ld.lld -shared --soname=t --version-script=%t/ver %t/ref.o %t/ref1.o %t/hid1.o %t/def2.o -o %t3 @@ -128,15 +132,15 @@ ## Test --wrap on @ and @@. -## TODO Error because __wrap_foo@v1 is not defined. +## Error because __wrap_foo@v1 is not defined. ## Note: GNU ld errors "no symbol version section for versioned symbol `__wrap_foo@v1'". -# RUN: ld.lld -shared --soname=t --version-script=%t/ver --wrap=foo@v1 %t/ref.o %t/ref1.o %t/def1.o %t/wrap.o -o %t.w3 -# RUN: llvm-readobj -r %t.w3 | FileCheck %s --check-prefix=W3REL +# RUN: not ld.lld -shared --soname=t --version-script=%t/ver --wrap=foo@v1 %t/ref.o %t/ref1.o %t/def1.o %t/wrap.o \ +# RUN: -o /dev/null 2>&1 | FileCheck %s --check-prefix=W3 -# W3REL: .rela.plt { -# W3REL-NEXT: R_X86_64_JUMP_SLOT foo@@v1 0x0 -# W3REL-NEXT: R_X86_64_JUMP_SLOT - 0x0 -# W3REL-NEXT: } +# W3: error: undefined symbol: __wrap_foo@v1 +# W3-NEXT: >>> referenced by {{.*}}ref1.o:(.text+0x1) +# W3-NEXT: >>> did you mean: __wrap_foo{{$}} +# W3-NEXT: >>> defined in: {{.*}}wrap.o ## foo@v1 is correctly wrapped. # RUN: ld.lld -shared --soname=t --version-script=%t/ver --wrap=foo@v1 %t/ref.o %t/ref1.o %t/def1.o %t/wrap1.o -o %t.w4 @@ -183,6 +187,11 @@ call foo .symver foo, foo@@@v1 call foo +#--- ref1w.s +.weak foo +.symver foo, foo@@@v1 +call foo + #--- def1.s .globl foo .symver foo, foo@@@v1