Skip to content

Commit 66d4430

Browse files
committed
[ELF] Combine foo@v1 and foo with the same versionId if both are defined
Due to an assembler design flaw (IMO), `.symver foo,foo@v1` produces two symbols `foo` and `foo@v1` if `foo` is defined. * `v1 {};` produces both `foo` and `foo@v1`, but GNU ld only produces `foo@v1` * `v1 { foo; };` produces both `foo@@v1` and `foo@v1`, but GNU ld only produces `foo@v1` * `v2 { foo; };` produces both `foo@@v2` and `foo@v1`, matching GNU ld. (Tested by symver.s) This patch implements the GNU ld behavior by reusing the symbol redirection mechanism in D92259. The new test symver-non-default.s checks the first two cases. Without the patch, the second case will produce `foo@v1` and `foo@@v1` which looks weird and makes foo unnecessarily default versioned. Note: `.symver foo,foo@v1,remove` exists but the unfortunate `foo` will not go away anytime soon. Reviewed By: peter.smith Differential Revision: https://reviews.llvm.org/D107235
1 parent bdeb15c commit 66d4430

File tree

4 files changed

+108
-19
lines changed

4 files changed

+108
-19
lines changed

lld/ELF/Driver.cpp

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,23 +2071,37 @@ static void redirectSymbols(ArrayRef<WrappedSymbol> wrapped) {
20712071
if (suffix1[0] != '@' || suffix1[1] == '@')
20722072
continue;
20732073

2074-
// Check whether the default version foo@@v1 exists. If it exists, the
2075-
// symbol can be found by the name "foo" in the symbol table.
2076-
Symbol *maybeDefault = symtab->find(name);
2077-
if (!maybeDefault)
2078-
continue;
2079-
const char *suffix2 = maybeDefault->getVersionSuffix();
2080-
if (suffix2[0] != '@' || suffix2[1] != '@' ||
2081-
strcmp(suffix1 + 1, suffix2 + 2) != 0)
2074+
// Check the existing symbol foo. We have two special cases to handle:
2075+
//
2076+
// * There is a definition of foo@v1 and foo@@v1.
2077+
// * There is a definition of foo@v1 and foo.
2078+
Defined *sym2 = dyn_cast_or_null<Defined>(symtab->find(name));
2079+
if (!sym2)
20822080
continue;
2083-
2084-
// foo@v1 and foo@@v1 should be merged, so redirect foo@v1 to foo@@v1.
2085-
map.try_emplace(sym, maybeDefault);
2086-
// If both foo@v1 and foo@@v1 are defined and non-weak, report a duplicate
2087-
// definition error.
2088-
maybeDefault->resolve(*sym);
2089-
// Eliminate foo@v1 from the symbol table.
2090-
sym->symbolKind = Symbol::PlaceholderKind;
2081+
const char *suffix2 = sym2->getVersionSuffix();
2082+
if (suffix2[0] == '@' && suffix2[1] == '@' &&
2083+
strcmp(suffix1 + 1, suffix2 + 2) == 0) {
2084+
// foo@v1 and foo@@v1 should be merged, so redirect foo@v1 to foo@@v1.
2085+
map.try_emplace(sym, sym2);
2086+
// If both foo@v1 and foo@@v1 are defined and non-weak, report a duplicate
2087+
// definition error.
2088+
sym2->resolve(*sym);
2089+
// Eliminate foo@v1 from the symbol table.
2090+
sym->symbolKind = Symbol::PlaceholderKind;
2091+
} else if (auto *sym1 = dyn_cast<Defined>(sym)) {
2092+
if (sym2->versionId > VER_NDX_GLOBAL
2093+
? config->versionDefinitions[sym2->versionId].name == suffix1 + 1
2094+
: sym1->section == sym2->section && sym1->value == sym2->value) {
2095+
// Due to an assembler design flaw, if foo is defined, .symver foo,
2096+
// foo@v1 defines both foo and foo@v1. Unless foo is bound to a
2097+
// different version, GNU ld makes foo@v1 canonical and elimiates foo.
2098+
// Emulate its behavior, otherwise we would have foo or foo@@v1 beside
2099+
// foo@v1. foo@v1 and foo combining does not apply if they are not
2100+
// defined in the same place.
2101+
map.try_emplace(sym2, sym);
2102+
sym2->symbolKind = Symbol::PlaceholderKind;
2103+
}
2104+
}
20912105
}
20922106

20932107
if (map.empty())

lld/test/ELF/symver-non-default.s

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# REQUIRES: x86
2+
## Test symbol resolution related to .symver produced non-default version symbols.
3+
4+
# RUN: split-file %s %t
5+
# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/ref.s -o %t/ref.o
6+
# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/def1.s -o %t/def1.o
7+
# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/def2.s -o %t/def2.o
8+
# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/def3.s -o %t/def3.o
9+
10+
## foo@v1 & foo defined at the same location are combined.
11+
# RUN: ld.lld -shared --version-script=%t/ver1 %t/def1.o %t/ref.o -o %t1
12+
# RUN: llvm-readelf --dyn-syms %t1 | FileCheck %s --check-prefix=CHECK1
13+
# RUN: ld.lld -shared --version-script=%t/ver2 %t/def1.o %t/ref.o -o %t1
14+
# RUN: llvm-readelf --dyn-syms %t1 | FileCheck %s --check-prefix=CHECK1
15+
16+
# CHECK1: 1: {{.*}} NOTYPE GLOBAL DEFAULT [[#]] foo@v1
17+
# CHECK1-EMPTY:
18+
19+
## def2.o doesn't define foo. foo@v1 & undefined foo are unrelated.
20+
# RUN: ld.lld -shared --version-script=%t/ver1 %t/def2.o %t/ref.o -o %t2
21+
# RUN: llvm-readelf -r --dyn-syms %t2 | FileCheck %s --check-prefix=CHECK2
22+
23+
# CHECK2: R_X86_64_JUMP_SLOT {{.*}} foo + 0
24+
# CHECK2: 1: {{.*}} NOTYPE GLOBAL DEFAULT UND foo{{$}}
25+
# CHECK2-NEXT: 2: {{.*}} NOTYPE GLOBAL DEFAULT [[#]] foo@v1
26+
# CHECK2-EMPTY:
27+
28+
## def2.o doesn't define foo. foo@v1 & defined foo are unrelated.
29+
# RUN: ld.lld -shared --version-script=%t/ver1 %t/def2.o %t/def3.o %t/ref.o -o %t3
30+
# RUN: llvm-readelf -r --dyn-syms %t3 | FileCheck %s --check-prefix=CHECK3
31+
32+
# CHECK3: R_X86_64_JUMP_SLOT {{.*}} foo + 0
33+
# CHECK3: 1: {{.*}} NOTYPE GLOBAL DEFAULT [[#]] foo@v1
34+
# CHECK3-NEXT: 2: {{.*}} NOTYPE GLOBAL DEFAULT [[#]] foo{{$}}
35+
# CHECK3-EMPTY:
36+
37+
## foo@v1 overrides the defined foo which is affected by a version script.
38+
# RUN: ld.lld -shared --version-script=%t/ver2 %t/def2.o %t/def3.o %t/ref.o -o %t4
39+
# RUN: llvm-readelf -r --dyn-syms %t4 | FileCheck %s --check-prefix=CHECK4
40+
41+
# CHECK4: R_X86_64_JUMP_SLOT {{.*}} foo@v1 + 0
42+
# CHECK4: 1: {{.*}} NOTYPE GLOBAL DEFAULT [[#]] foo@v1
43+
# CHECK4-EMPTY:
44+
45+
#--- ver1
46+
v1 {};
47+
48+
#--- ver2
49+
v1 { foo; };
50+
51+
#--- ref.s
52+
call foo
53+
54+
#--- def1.s
55+
.globl foo
56+
.symver foo, foo@v1
57+
foo:
58+
ret
59+
60+
#--- def2.s
61+
.globl foo_v1
62+
.symver foo_v1, foo@v1, remove
63+
foo_v1:
64+
ret
65+
66+
#--- def3.s
67+
.globl foo
68+
foo:
69+
ret

lld/test/ELF/version-script-symver.s

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# REQUIRES: x86
22
## Test how .symver interacts with --version-script.
33
# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o
4+
# RUN: echo 'call foo3; call foo4' > %tref.s
5+
# RUN: llvm-mc -filetype=obj -triple=x86_64 %tref.s -o %tref.o
46

57
# RUN: echo 'v1 { local: foo1; }; v2 { local: foo2; };' > %t1.script
68
# RUN: ld.lld --version-script %t1.script -shared %t.o -o %t1.so
79
# RUN: llvm-readelf --dyn-syms %t1.so | FileCheck --check-prefix=EXACT %s
810
# EXACT: UND
9-
# EXACT-NEXT: [[#]] foo3{{$}}
1011
# EXACT-NEXT: [[#]] foo4@@v2
1112
# EXACT-NEXT: [[#]] _start{{$}}
1213
# EXACT-NEXT: [[#]] foo3@v1
@@ -34,11 +35,16 @@
3435
# MIX2: UND
3536
# MIX2-NEXT: [[#]] foo1@@v1
3637
# MIX2-NEXT: [[#]] foo2@@v1
37-
# MIX2-NEXT: [[#]] foo3@@v1
3838
# MIX2-NEXT: [[#]] foo4@@v2
3939
# MIX2-NEXT: [[#]] foo3@v1
4040
# MIX2-NOT: {{.}}
4141

42+
# RUN: ld.lld --version-script %t4.script -shared %t.o %tref.o -o %t5.so
43+
# RUN: llvm-readelf -r %t5.so | FileCheck --check-prefix=RELOC %s
44+
45+
# RELOC: R_X86_64_JUMP_SLOT {{.*}} foo3@v1 + 0
46+
# RELOC: R_X86_64_JUMP_SLOT {{.*}} foo4@@v2 + 0
47+
4248
.globl foo1; foo1: ret
4349
.globl foo2; foo2: ret
4450
.globl foo3; .symver foo3,foo3@v1; foo3: ret

lld/test/ELF/version-symbol-undef.s

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// RUN: .quad \"basename@FBSD_1.1\" " > %t.s
66
// RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %t.s -o %t.o
77
// RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t2.o
8-
// RUN: echo "FBSD_1.0 { global: basename; local: *; }; FBSD_1.1 { };" > %t2.ver
8+
// RUN: echo "FBSD_1.0 { global: basename; local: *; }; FBSD_1.1 { basename; };" > %t2.ver
99
// RUN: ld.lld --shared --version-script %t2.ver %t2.o -o %t2.so
1010
// RUN: echo "FBSD_1.0 { }; FBSD_1.1 { }; LIBPKG_1.3 { };" > %t.ver
1111
// RUN: ld.lld --shared %t.o --version-script %t.ver %t2.so -o %t.so

0 commit comments

Comments
 (0)