diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1919,17 +1919,46 @@ return v; } -// Do renaming for -wrap by updating pointers to symbols. +// Do renaming for -wrap and foo@v1 by updating pointers to symbols. // // When this function is executed, only InputFiles and symbol table // contain pointers to symbol objects. We visit them to replace pointers, // so that wrapped symbols are swapped as instructed by the command line. -static void wrapSymbols(ArrayRef wrapped) { +static void redirectSymbols(ArrayRef wrapped) { + llvm::TimeTraceScope timeScope("Redirect symbols"); DenseMap map; for (const WrappedSymbol &w : wrapped) { map[w.sym] = w.wrap; map[w.real] = w.sym; } + for (Symbol *sym : symtab->symbols()) { + // Enumerate symbols with a non-default version (foo@v1). + StringRef name = sym->getName(); + const char *suffix1 = sym->getVersionSuffix(); + if (suffix1[0] != '@' || suffix1[1] == '@') + continue; + + // Check whether the default version foo@@v1 exists. If it exists, the + // symbol can be found by the name "foo" in the symbol table. + Symbol *maybeDefault = symtab->find(name); + if (!maybeDefault) + continue; + const char *suffix2 = maybeDefault->getVersionSuffix(); + if (suffix2[0] != '@' || suffix2[1] != '@' || + strcmp(suffix1 + 1, suffix2 + 2) != 0) + continue; + + // foo@v1 and foo@@v1 should be merged, so redirect foo@v1 to foo@@v1. + map.try_emplace(sym, maybeDefault); + // If both foo@v1 and foo@@v1 are defined and non-weak, report a duplicate + // definition error. + maybeDefault->resolve(*sym); + // Eliminate foo@v1 from the symbol table. + sym->symbolKind = Symbol::PlaceholderKind; + } + + if (map.empty()) + return; // Update pointers in input files. parallelForEach(objectFiles, [&](InputFile *file) { @@ -2158,9 +2187,8 @@ !config->thinLTOModulesToCompile.empty()) return; - // Apply symbol renames for -wrap. - if (!wrapped.empty()) - wrapSymbols(wrapped); + // Apply symbol renames for -wrap and combine foo@v1 and foo@@v1. + redirectSymbols(wrapped); { llvm::TimeTraceScope timeScope("Aggregate sections"); diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h --- a/lld/ELF/Symbols.h +++ b/lld/ELF/Symbols.h @@ -178,6 +178,15 @@ void parseSymbolVersion(); + // Get the NUL-terminated version suffix ("", "@...", or "@@..."). + // + // 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); + return nameData + nameSize; + } + bool isInGot() const { return gotIndex != -1U; } bool isInPlt() const { return pltIndex != -1U; } diff --git a/lld/ELF/Symbols.cpp b/lld/ELF/Symbols.cpp --- a/lld/ELF/Symbols.cpp +++ b/lld/ELF/Symbols.cpp @@ -37,11 +37,9 @@ 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(); + const char *suffix = sym.getVersionSuffix(); + if (*suffix == '@') + ret += suffix; return ret; } diff --git a/lld/test/ELF/symver.s b/lld/test/ELF/symver.s --- a/lld/test/ELF/symver.s +++ b/lld/test/ELF/symver.s @@ -15,29 +15,33 @@ # RUN: ld.lld -shared --soname=def1.so --version-script=%t/ver %t/def1.o -o %t/def1.so # RUN: ld.lld -shared --soname=hid1.so --version-script=%t/ver %t/hid1.o -o %t/hid1.so -## TODO Report a duplicate definition error for foo@v1 and foo@@v1. -# RUN: ld.lld -shared --version-script=%t/ver %t/def1.o %t/hid1.o -o /dev/null - +## Report a duplicate definition error for foo@v1 and foo@@v1. +# RUN: not ld.lld -shared --version-script=%t/ver %t/def1.o %t/hid1.o -o /dev/null 2>&1 | \ +# RUN: FileCheck %s --check-prefix=DUP # RUN: ld.lld -shared --version-script=%t/ver %t/def1w.o %t/hid1.o -o /dev/null # RUN: ld.lld -shared --version-script=%t/ver %t/def1.o %t/hid1w.o -o /dev/null # RUN: ld.lld -shared --version-script=%t/ver %t/def1w.o %t/hid1w.o -o /dev/null -## TODO foo@@v1 should resolve undefined foo@v1. -# RUN: not ld.lld -shared --version-script=%t/ver %t/ref1p.o %t/def1.o -o /dev/null +# DUP: error: duplicate symbol: foo@@v1 +# DUP-NEXT: >>> defined at {{.*}}/def1{{w?}}.o:(.text+0x0) +# DUP-NEXT: >>> defined at {{.*}}/hid1.o:(.text+0x0) + +## Protected undefined foo@v1 makes the output symbol protected. +# RUN: ld.lld -shared --version-script=%t/ver %t/ref1p.o %t/def1.o -o %t.protected +# RUN: llvm-readelf --dyn-syms %t.protected | FileCheck %s --check-prefix=PROTECTED + +# PROTECTED: NOTYPE GLOBAL PROTECTED [[#]] foo@@v1 -## TODO ## foo@@v1 resolves both undefined foo and foo@v1. There is one single definition. ## Note: set soname so that the name string referenced by .gnu.version_d is fixed. # RUN: ld.lld -shared --soname=t --version-script=%t/ver %t/ref.o %t/ref1.o %t/def1.o -o %t1 # RUN: llvm-readelf -r --dyn-syms %t1 | FileCheck %s -# CHECK: Relocation section '.rela.plt' at offset {{.*}} contains 2 entries: +# CHECK: Relocation section '.rela.plt' at offset {{.*}} contains 1 entries: # CHECK-NEXT: {{.*}} Type {{.*}} # CHECK-NEXT: {{.*}} R_X86_64_JUMP_SLOT {{.*}} foo@@v1 + 0 -# CHECK-NEXT: {{.*}} R_X86_64_JUMP_SLOT {{.*}} foo + 0 -# CHECK: 1: {{.*}} NOTYPE GLOBAL DEFAULT UND foo{{$}} -# CHECK-NEXT: 2: {{.*}} NOTYPE GLOBAL DEFAULT [[#]] foo@@v1 +# CHECK: 1: {{.*}} NOTYPE GLOBAL DEFAULT [[#]] foo@@v1 # CHECK-EMPTY: ## foo@@v2 does not resolve undefined foo@v1. @@ -96,10 +100,9 @@ # RUN: llvm-readobj -r %t.w1 | FileCheck %s --check-prefix=W1REL # RUN: llvm-objdump -d --no-show-raw-insn %t.w1 | FileCheck %s --check-prefix=W1DIS -## TODO foo should be foo@@v1. # W1REL: .rela.plt { # W1REL-NEXT: R_X86_64_JUMP_SLOT __wrap_foo 0x0 -# W1REL-NEXT: R_X86_64_JUMP_SLOT foo 0x0 +# W1REL-NEXT: R_X86_64_JUMP_SLOT foo@@v1 0x0 # W1REL-NEXT: } # W1DIS-LABEL: <.text>: @@ -150,7 +153,6 @@ # W4DIS-COUNT-3: int3 # W4DIS-NEXT: callq {{.*}} <__wrap_foo@plt> -## TODO The two callq should jump to the same address. ## 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.w5 # RUN: llvm-readobj -r %t.w5 | FileCheck %s --check-prefix=W5REL @@ -158,13 +160,12 @@ # W5REL: .rela.plt { # W5REL-NEXT: R_X86_64_JUMP_SLOT foo@@v1 0x0 -# W5REL-NEXT: R_X86_64_JUMP_SLOT foo 0x0 # W5REL-NEXT: } # W5DIS-LABEL: <.text>: -# W5DIS-NEXT: callq 0x1380 +# W5DIS-NEXT: callq 0x1350 # W5DIS-COUNT-3: int3 -# W5DIS-NEXT: callq 0x1390 +# W5DIS-NEXT: callq 0x1350 #--- ver v1 {};