[ELF] Support copy relocation on non-default version symbols
authorFangrui Song <i@maskray.me>
Thu, 5 Aug 2021 17:32:14 +0000 (10:32 -0700)
committerFangrui Song <i@maskray.me>
Thu, 5 Aug 2021 17:32:14 +0000 (10:32 -0700)
Copy relocation on a non-default version symbol is unsupported and can crash at
runtime. Fortunately there is a one-line fix which works for most cases:
ensure `getSymbolsAt` unconditionally returns `ss`.

If two non-default version symbols are defined at the same place and both
are copy relocated, our implementation will copy relocated them into different
addresses. The pointer inequality is very unlikely an issue. In GNU ld, copy
relocating version aliases seems to create more pointer inequality problems than
us.

(
In glibc, sys_errlist@GLIBC_2.2.5 sys_errlist@GLIBC_2.3 sys_errlist@GLIBC_2.4
are defined at the same place, but it is unlikely they are all copy relocated in
one executable. Even if so, the variables are read-only and pointer inequality
should not be a problem.
)

Reviewed By: peter.smith

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

lld/ELF/Relocations.cpp
lld/test/ELF/Inputs/copy-rel-version.s
lld/test/ELF/copy-rel-version.s

index e3cc210..537859f 100644 (file)
@@ -527,6 +527,13 @@ static SmallSet<SharedSymbol *, 4> getSymbolsAt(SharedSymbol &ss) {
     if (auto *alias = dyn_cast_or_null<SharedSymbol>(sym))
       ret.insert(alias);
   }
+
+  // The loop does not check SHT_GNU_verneed, so ret does not contain
+  // non-default version symbols. If ss has a non-default version, ret won't
+  // contain ss. Just add ss unconditionally. If a non-default version alias is
+  // separately copy relocated, it and ss will have different addresses.
+  // Fortunately this case is impractical and fails with GNU ld as well.
+  ret.insert(&ss);
   return ret;
 }
 
index 36bb1ba..1477527 100644 (file)
@@ -1,11 +1,22 @@
 .data
-.global foo@v1
-.type foo@v1, @object
-.size foo@v1, 4
-.global foo@@v2
-.type foo@@v2, @object
-.size foo@@v2, 8
-foo@v1:
-foo@@v2:
+.global foo_v1
+.symver foo_v1, foo@v1, remove
+.type foo_v1, @object
+.size foo_v1, 4
+
+.global foo_v2
+.symver foo_v2, foo@v2, remove
+.type foo_v2, @object
+.size foo_v2, 8
+
+.global foo
+.symver foo, foo@@@v3
+.type foo, @object
+.size foo, 12
+
+foo_v1:
+foo_v2:
+foo:
+.int 0
 .int 0
 .int 0
index afa5ebe..feaa59f 100644 (file)
@@ -1,15 +1,27 @@
-// REQUIRES: x86
-// RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
-// RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %p/Inputs/copy-rel-version.s -o %t1.o
-// RUN: echo "v1 {}; v2 {};" > %t.ver
-// RUN: ld.lld %t1.o -shared -soname t1.so --version-script=%t.ver -o %t1.so
-// RUN: ld.lld %t.o %t1.so -o %t
-// RUN: llvm-readobj --symbols %t | FileCheck %s
+# REQUIRES: x86
+## Copy relocate a versioned symbol which has a versioned alias.
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t1.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %p/Inputs/copy-rel-version.s -o %t.o
+# RUN: echo 'v1 {}; v2 {}; v3 {};' > %t.ver
+# RUN: ld.lld %t.o -shared -soname t.so --version-script=%t.ver -o %t.so
+
+## Copy relocate the default version symbol.
+# RUN: ld.lld %t1.o %t.so -o %t1
+# RUN: llvm-readelf --dyn-syms %t1 | FileCheck %s --check-prefix=CHECK1
+
+# CHECK1:       1: {{.+}}            12 OBJECT  GLOBAL DEFAULT [[#]] foo@v3
+# CHECK1-EMPTY:
+
+## Copy relocate the non-default version symbol.
+# RUN: llvm-objcopy --redefine-sym foo=foo@v1 %t1.o %t2.o
+# RUN: ld.lld %t2.o %t.so -o %t2
+# RUN: llvm-readelf --dyn-syms %t2 | FileCheck %s --check-prefix=CHECK2
+
+# CHECK2:       1: [[ADDR:[0-9a-f]+]] 4 OBJECT  GLOBAL DEFAULT [[#]] foo@v1
+# CHECK2-NEXT:  2: [[ADDR]]          12 OBJECT  GLOBAL DEFAULT [[#]] foo@v3
+# CHECK2-EMPTY:
 
 .global _start
 _start:
   leaq foo, %rax
-
-// CHECK:      Name: foo (
-// CHECK-NEXT: Value:
-// CHECK-NEXT: Size: 8