Index: lld/trunk/ELF/InputFiles.cpp =================================================================== --- lld/trunk/ELF/InputFiles.cpp +++ lld/trunk/ELF/InputFiles.cpp @@ -999,25 +999,25 @@ for (size_t I = 0; I < Syms.size(); ++I) { const Elf_Sym &Sym = Syms[I]; - StringRef Name = CHECK(Sym.getName(this->StringTable), this); - if (Sym.isUndefined()) { - Symbol *S = Symtab->addUndefined(Name, Sym.getBinding(), - Sym.st_other, Sym.getType(), - /*CanOmitFromDynSym=*/false, this); - S->ExportDynamic = true; - continue; - } - // ELF spec requires that all local symbols precede weak or global // symbols in each symbol table, and the index of first non-local symbol // is stored to sh_info. If a local symbol appears after some non-local // symbol, that's a violation of the spec. + StringRef Name = CHECK(Sym.getName(this->StringTable), this); if (Sym.getBinding() == STB_LOCAL) { warn("found local symbol '" + Name + "' in global part of symbol table in file " + toString(this)); continue; } + if (Sym.isUndefined()) { + Symbol *S = Symtab->addUndefined(Name, Sym.getBinding(), + Sym.st_other, Sym.getType(), + /*CanOmitFromDynSym=*/false, this); + S->ExportDynamic = true; + continue; + } + // MIPS BFD linker puts _gp_disp symbol into DSO files and incorrectly // assigns VER_NDX_LOCAL to this section global symbol. Here is a // workaround for this bug. Index: lld/trunk/test/ELF/invalid/undefined-local-symbol-in-dso.test =================================================================== --- lld/trunk/test/ELF/invalid/undefined-local-symbol-in-dso.test +++ lld/trunk/test/ELF/invalid/undefined-local-symbol-in-dso.test @@ -0,0 +1,66 @@ +# REQUIRES: x86 + +# LLD used to crash when linking against a DSO with an undefined STB_LOCAL +# symbol in the global part of the dynamic symbol table (i.e. an STB_LOCAL +# symbol with an index >= the sh_info of the dynamic symbol table section). Such +# a DSO is very broken, because local symbols should precede all global symbols +# in the symbol table, and because having a symbol that's both undefined and +# STB_LOCAL is a nonsensical combination. Nevertheless, we should warn on such +# input files instead of crashing. + +# We've found actual broken DSOs of this sort in the wild, but for this test, we +# created a reduced broken input file. There are no tools capable of producing a +# broken DSO of this nature, so instead we created a valid DSO with an undefiend +# global symbol in the dynamic symbol table and then manually edited the binary +# to make that symbol local. The valid DSO was created as follows: + +``` +% cat undef.s +.hidden bar +bar: + movq foo@GOT, %rax + +% llvm-mc -triple=x86_64-linux-gnu -filetype=obj -o undef.o undef.s +% ld.lld --no-rosegment -shared -o undefined-local-symbol-in-dso.so undef.o +% strip undef.so +``` + +# (--no-rosegment and stripping are unnecessary; they just produce a smaller +# binary) + +# This DSO should only have a single dynamic symbol table entry for foo, and +# then we can use a small C program to modify that symbol table entry to be +# STB_LOCAL instead of STB_GLOBAL. + +``` +#include +#include + +int main(int argc, char *argv[]) { + FILE *F = fopen(argv[1], "rb+"); + + Elf64_Ehdr Ehdr; + fread(&Ehdr, sizeof(Ehdr), 1, F); + fseek(F, Ehdr.e_shoff, SEEK_SET); + + Elf64_Shdr Shdr; + do { + fread(&Shdr, sizeof(Shdr), 1, F); + } while (Shdr.sh_type != SHT_DYNSYM); + + Elf64_Sym Sym; + fseek(F, Shdr.sh_offset + sizeof(Elf64_Sym), SEEK_SET); + fread(&Sym, sizeof(Sym), 1, F); + Sym.st_info = STB_LOCAL << 4 | ELF64_ST_TYPE(Sym.st_info); + fseek(F, Shdr.sh_offset + sizeof(Elf64_Sym), SEEK_SET); + fwrite(&Sym, sizeof(Sym), 1, F); + fclose(F); +} +``` + +# (the C program just takes its input DSO and modifies the binding of the first +# dynamic symbol table entry to be STB_LOCAL instead of STB_GLOBAL) + +# RUN: ld.lld %p/Inputs/undefined-local-symbol-in-dso.so -o %t 2>&1 | \ +# RUN: FileCheck -check-prefix=WARN %s +# WARN: found local symbol 'foo' in global part of symbol table in file {{.*}}undefined-local-symbol-in-dso.so