diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1907,17 +1907,43 @@ 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) { 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). Its name was + // truncated at '@' by Symbol::parseSymbolVersion(). + StringRef name = sym->getName(); + const char *end1 = name.data() + name.size(); + if (*end1 != '@' || end1[1] == '@') + continue; + + // Check whether the default version foo@@v1 exists. If exists, the symbol + // can be found by the name "foo" in the symbol table. + Symbol *maybeDefault = symtab->find(name); + if (!maybeDefault) + continue; + const char *end2 = + maybeDefault->getName().data() + maybeDefault->getName().size(); + if (*end2 != '@' || end2[1] != '@' || strcmp(end1 + 1, end2 + 2) != 0) + continue; + + // foo@v1 and foo@@v1 should be considered as one symbol. So we redirect + // foo@v1 references to foo@@v1. + map.try_emplace(sym, maybeDefault); + // If foo@v1 is also definied, report a duplicate definition error. + sym->resolve(*maybeDefault); + // Eliminate foo@v1 from the symbol table. + sym->symbolKind = Symbol::PlaceholderKind; + } // Update pointers in input files. parallelForEach(objectFiles, [&](InputFile *file) { @@ -2146,9 +2172,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/test/ELF/symver.s b/lld/test/ELF/symver.s --- a/lld/test/ELF/symver.s +++ b/lld/test/ELF/symver.s @@ -9,22 +9,24 @@ # 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 an 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 an 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 + +# DUP: error: duplicate symbol: foo@v1 +# DUP-NEXT: >>> defined at {{.*}}/hid1.o:(.text+0x0) +# DUP-NEXT: >>> defined at {{.*}}/def1.o:(.text+0x0) -## 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=t1 --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 {{[1-9]}} foo@@v1 +# CHECK: 1: {{.*}} NOTYPE GLOBAL DEFAULT {{[1-9]}} foo@@v1 # CHECK-EMPTY: ## foo@@v2 does not resolve undefined foo@v1.