[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
This commit is contained in:
Fangrui Song 2020-12-01 08:59:54 -08:00
parent 624af932a8
commit 843c2b2303
4 changed files with 51 additions and 19 deletions

View File

@ -945,7 +945,15 @@ template <class ELFT> 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;

View File

@ -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;
}

View File

@ -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

View File

@ -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