diff --git a/llvm/test/tools/llvm-readobj/ELF/all.test b/llvm/test/tools/llvm-readobj/ELF/all.test --- a/llvm/test/tools/llvm-readobj/ELF/all.test +++ b/llvm/test/tools/llvm-readobj/ELF/all.test @@ -112,4 +112,5 @@ Sections: - Section: .note.gnu.build-id Symbols: [] -DynamicSymbols: [] +DynamicSymbols: + - Name: foo diff --git a/llvm/test/tools/llvm-readobj/ELF/dyn-symbols.test b/llvm/test/tools/llvm-readobj/ELF/dyn-symbols.test --- a/llvm/test/tools/llvm-readobj/ELF/dyn-symbols.test +++ b/llvm/test/tools/llvm-readobj/ELF/dyn-symbols.test @@ -429,3 +429,349 @@ Value: 0x123 - Tag: DT_NULL Value: 0 + +## Case 10: There is no size tag in the dynamic table for a dynamic symbol +## table. However, the size of the dynsym can be derived from the .hash section, +## if it exists, allowing dumping of the dynamic symbol table in the event of an +## object without section headers. The following cases illustrate this +## behaviour. + +## a1) Table size is derived from hash table, with DT_SYMTAB before DT_HASH. +# RUN: yaml2obj --docnum=13 %s -o %t10a1-64 -DBITS=64 \ +# RUN: -DTAG1=DT_SYMTAB -DTAG2=DT_HASH -DADDR1=0x400 -DADDR2=0x600 +# RUN: llvm-strip --strip-sections %t10a1-64 +# RUN: llvm-readobj --dyn-symbols %t10a1-64 2>&1 | \ +# RUN: FileCheck %s --check-prefix=LLVM-HASHA --implicit-check-not=warning +# RUN: llvm-readelf --dyn-symbols %t10a1-64 2>&1 | \ +# RUN: FileCheck %s --check-prefix=GNU-HASHA --implicit-check-not=warning +# RUN: yaml2obj --docnum=13 %s -o %t10a1-32 -DBITS=32 \ +# RUN: -DTAG1=DT_SYMTAB -DTAG2=DT_HASH -DADDR1=0x400 -DADDR2=0x600 +# RUN: llvm-strip --strip-sections %t10a1-32 +# RUN: llvm-readobj --dyn-symbols %t10a1-32 2>&1 | \ +# RUN: FileCheck %s --check-prefix=LLVM-HASHA --implicit-check-not=warning +# RUN: llvm-readelf --dyn-symbols %t10a1-32 2>&1 | \ +# RUN: FileCheck %s --check-prefix=GNU-HASHA --implicit-check-not=warning + +## a2) Table size is dervied from hash table, with DT_HASH before DT_SYMTAB. +# RUN: yaml2obj --docnum=13 %s -o %t10a2-64 -DBITS=64 \ +# RUN: -DTAG1=DT_HASH -DTAG2=DT_SYMTAB -DADDR1=0x600 -DADDR2=0x400 +# RUN: llvm-strip --strip-sections %t10a2-64 +# RUN: llvm-readobj --dyn-symbols %t10a2-64 2>&1 | \ +# RUN: FileCheck %s --check-prefix=LLVM-HASHA --implicit-check-not=warning +# RUN: llvm-readelf --dyn-symbols %t10a2-64 2>&1 | \ +# RUN: FileCheck %s --check-prefix=GNU-HASHA --implicit-check-not=warning + +# LLVM-HASHA: DynamicSymbols [ +# LLVM-HASHA-NEXT: Symbol { +# LLVM-HASHA-NEXT: Name: (0) +# LLVM-HASHA-NEXT: Value: 0x0 +# LLVM-HASHA-NEXT: Size: 0 +# LLVM-HASHA-NEXT: Binding: Local (0x0) +# LLVM-HASHA-NEXT: Type: None (0x0) +# LLVM-HASHA-NEXT: Other: 0 +# LLVM-HASHA-NEXT: Section: Undefined (0x0) +# LLVM-HASHA-NEXT: } +# LLVM-HASHA-NEXT: Symbol { +# LLVM-HASHA-NEXT: Name: foo (5) +# LLVM-HASHA-NEXT: Value: 0x100 +# LLVM-HASHA-NEXT: Size: 0 +# LLVM-HASHA-NEXT: Binding: Local (0x0) +# LLVM-HASHA-NEXT: Type: Function (0x2) +# LLVM-HASHA-NEXT: Other: 0 +# LLVM-HASHA-NEXT: Section: (0x1) +# LLVM-HASHA-NEXT: } +# LLVM-HASHA-NEXT: Symbol { +# LLVM-HASHA-NEXT: Name: bar (1) +# LLVM-HASHA-NEXT: Value: 0x200 +# LLVM-HASHA-NEXT: Size: 0 +# LLVM-HASHA-NEXT: Binding: Local (0x0) +# LLVM-HASHA-NEXT: Type: Object (0x1) +# LLVM-HASHA-NEXT: Other: 0 +# LLVM-HASHA-NEXT: Section: (0x2) +# LLVM-HASHA-NEXT: } +# LLVM-HASHA-NEXT: ] + +# GNU-HASHA: Symbol table for image contains 3 entries: +# GNU-HASHA-NEXT: Num: Value Size Type Bind Vis Ndx Name +# GNU-HASHA-NEXT: 0: {{0*}}00000000 0 NOTYPE LOCAL DEFAULT UND +# GNU-HASHA-NEXT: 1: {{0*}}00000100 0 FUNC LOCAL DEFAULT 1 foo +# GNU-HASHA-NEXT: 2: {{0*}}00000200 0 OBJECT LOCAL DEFAULT 2 bar +# GNU-HASHA-EMPTY: + +--- !ELF +FileHeader: + Class: ELFCLASS[[BITS]] + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + - Name: .data + Type: SHT_PROGBITS + - Name: .dynsym + Type: SHT_DYNSYM + Flags: [ SHF_ALLOC ] + Address: 0x400 + AddressAlign: 0x400 + - Name: .hash + Type: SHT_HASH + Flags: [ SHF_ALLOC ] + Address: 0x600 + AddressAlign: 0x200 + Bucket: [ 1 ] + Chain: [ 1, 2, 3 ] + - Name: .dynstr + Type: SHT_STRTAB + Flags: [ SHF_ALLOC ] + Address: 0x800 + AddressAlign: 0x200 + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_ALLOC ] + Address: 0xA00 + AddressAlign: 0x200 + Entries: + - Tag: [[TAG1]] + Value: [[ADDR1]] + - Tag: [[TAG2]] + Value: [[ADDR2]] + - Tag: DT_STRTAB + Value: 0x800 + - Tag: DT_STRSZ + Value: 9 + - Tag: DT_NULL + Value: 0 +DynamicSymbols: + - Name: foo + Type: STT_FUNC + Section: .text + Value: 0x100 + - Name: bar + Type: STT_OBJECT + Section: .data + Value: 0x200 +ProgramHeaders: + - Type: PT_LOAD + VAddr: 0 + Sections: + - Section: .text + - Section: .data + - Type: PT_LOAD + VAddr: 0x400 + Sections: + - Section: .dynsym + - Section: .hash + - Section: .dynstr + - Section: .dynamic + - Type: PT_DYNAMIC + VAddr: 0xA00 + Sections: + - Section: .dynamic + +## b) Table size from DT_HASH does not match size from section header. +# RUN: yaml2obj --docnum=14 %s -o %t10b-smaller -DCHAIN="[1, 2]" +# RUN: llvm-readobj --dyn-symbols %t10b-smaller 2>&1 | \ +# RUN: FileCheck %s --check-prefixes=LLVM-HASHB,HASHB-WARN \ +# RUN: --implicit-check-not=warning -DNCHAIN=2 +# RUN: llvm-readelf --dyn-symbols %t10b-smaller 2>&1 | \ +# RUN: FileCheck %s --check-prefixes=GNU-HASHB,HASHB-WARN \ +# RUN: --implicit-check-not=warning -DNCHAIN=2 + +# RUN: yaml2obj --docnum=14 %s -o %t10b-larger -DCHAIN="[1, 2, 3, 4]" +# RUN: llvm-readobj --dyn-symbols %t10b-larger 2>&1 | \ +# RUN: FileCheck %s --check-prefixes=LLVM-HASHB,LLVM-HASHB4,HASHB-WARN \ +# RUN: --implicit-check-not=warning -DNCHAIN=4 +# RUN: llvm-readelf --dyn-symbols %t10b-larger 2>&1 | \ +# RUN: FileCheck %s --check-prefixes=GNU-HASHB,GNU-HASHB4,HASHB-WARN \ +# RUN: --implicit-check-not=warning -DNCHAIN=4 + +# HASHB-WARN: warning: '{{.*}}10b{{.*}}': hash table nchain ([[NCHAIN]]) differs from symbol count derived from SHT_DYNSYM section header (3) + +# LLVM-HASHB: DynamicSymbols [ +# LLVM-HASHB-NEXT: Symbol { +# LLVM-HASHB-NEXT: Name: (0) +# LLVM-HASHB-NEXT: Value: 0x0 +# LLVM-HASHB-NEXT: Size: 0 +# LLVM-HASHB-NEXT: Binding: Local (0x0) +# LLVM-HASHB-NEXT: Type: None (0x0) +# LLVM-HASHB-NEXT: Other: 0 +# LLVM-HASHB-NEXT: Section: Undefined (0x0) +# LLVM-HASHB-NEXT: } +# LLVM-HASHB-NEXT: Symbol { +# LLVM-HASHB-NEXT: Name: foo (9) +# LLVM-HASHB-NEXT: Value: 0x100 +# LLVM-HASHB-NEXT: Size: 0 +# LLVM-HASHB-NEXT: Binding: Local (0x0) +# LLVM-HASHB-NEXT: Type: Function (0x2) +# LLVM-HASHB-NEXT: Other: 0 +# LLVM-HASHB-NEXT: Section: .text (0x1) +# LLVM-HASHB-NEXT: } +# LLVM-HASHB4-NEXT: Symbol { +# LLVM-HASHB4-NEXT: Name: bar (5) +# LLVM-HASHB4-NEXT: Value: 0x200 +# LLVM-HASHB4-NEXT: Size: 0 +# LLVM-HASHB4-NEXT: Binding: Local (0x0) +# LLVM-HASHB4-NEXT: Type: Object (0x1) +# LLVM-HASHB4-NEXT: Other: 0 +# LLVM-HASHB4-NEXT: Section: .data (0x2) +# LLVM-HASHB4-NEXT: } +# LLVM-HASHB4-NEXT: Symbol { +# LLVM-HASHB4-NEXT: Name: baz (1) +# LLVM-HASHB4-NEXT: Value: 0x300 +# LLVM-HASHB4-NEXT: Size: 0 +# LLVM-HASHB4-NEXT: Binding: Local (0x0) +# LLVM-HASHB4-NEXT: Type: Object (0x1) +# LLVM-HASHB4-NEXT: Other: 0 +# LLVM-HASHB4-NEXT: Section: .data (0x2) +# LLVM-HASHB4-NEXT: } +# LLVM-HASHB-NEXT: ] + +# GNU-HASHB: Symbol table '.dynsym' contains [[NCHAIN]] entries: +# GNU-HASHB-NEXT: Num: Value Size Type Bind Vis Ndx Name +# GNU-HASHB-NEXT: 0: {{0*}}00000000 0 NOTYPE LOCAL DEFAULT UND +# GNU-HASHB-NEXT: 1: {{0*}}00000100 0 FUNC LOCAL DEFAULT 1 foo +# GNU-HASHB4-NEXT: 2: {{0*}}00000200 0 OBJECT LOCAL DEFAULT 2 bar +# GNU-HASHB4-NEXT: 3: {{0*}}00000300 0 OBJECT LOCAL DEFAULT 2 baz +# GNU-HASHB-EMPTY: + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + - Name: .data + Type: SHT_PROGBITS + - Name: .dynsym + Type: SHT_DYNSYM + Flags: [ SHF_ALLOC ] + ShSize: 0x48 + Address: 0x400 + AddressAlign: 0x400 + - Name: .hash + Type: SHT_HASH + Flags: [ SHF_ALLOC ] + Address: 0x600 + AddressAlign: 0x200 + Bucket: [ 1 ] + Chain: [[CHAIN]] + - Name: .dynstr + Type: SHT_STRTAB + Flags: [ SHF_ALLOC ] + Address: 0x800 + AddressAlign: 0x200 + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_ALLOC ] + Address: 0xA00 + AddressAlign: 0x200 + Entries: + - Tag: DT_SYMTAB + Value: 0x400 + - Tag: DT_HASH + Value: 0x600 + - Tag: DT_STRTAB + Value: 0x800 + - Tag: DT_STRSZ + Value: 13 + - Tag: DT_NULL + Value: 0 +DynamicSymbols: + - Name: foo + Type: STT_FUNC + Section: .text + Value: 0x100 + - Name: bar + Type: STT_OBJECT + Section: .data + Value: 0x200 + - Name: baz + Type: STT_OBJECT + Section: .data + Value: 0x300 +ProgramHeaders: + - Type: PT_LOAD + VAddr: 0 + Sections: + - Section: .text + - Section: .data + - Type: PT_LOAD + VAddr: 0x400 + Sections: + - Section: .dynsym + - Section: .hash + - Section: .dynstr + - Section: .dynamic + - Type: PT_DYNAMIC + VAddr: 0xA00 + Sections: + - Section: .dynamic + +## c) DT_HASH is missing (table size treated as 0). +# RUN: yaml2obj --docnum=15 %s -o %t10c +# RUN: llvm-strip --strip-sections %t10c +# RUN: llvm-readobj --dyn-symbols %t10c 2>&1 | \ +# RUN: FileCheck %s --check-prefix=LLVM-HASH2 --implicit-check-not=warning +# RUN: llvm-readelf --dyn-symbols %t10c 2>&1 | \ +# RUN: FileCheck %s --implicit-check-not={{.}} --allow-empty + +# LLVM-HASH2: DynamicSymbols [ +# LLVM-HASH2-NEXT: ] + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + - Name: .dynsym + Type: SHT_DYNSYM + Flags: [ SHF_ALLOC ] + Address: 0x400 + AddressAlign: 0x400 + - Name: .dynstr + Type: SHT_STRTAB + Flags: [ SHF_ALLOC ] + Address: 0x600 + AddressAlign: 0x200 + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_ALLOC ] + Address: 0x800 + AddressAlign: 0x200 + Entries: + - Tag: DT_SYMTAB + Value: 0x400 + - Tag: DT_STRTAB + Value: 0x600 + - Tag: DT_STRSZ + Value: 9 + - Tag: DT_NULL + Value: 0 +DynamicSymbols: + - Name: foo + Type: STT_FUNC + Section: .text + Value: 0x100 +ProgramHeaders: + - Type: PT_LOAD + VAddr: 0 + Sections: + - Section: .text + - Type: PT_LOAD + VAddr: 0x400 + Sections: + - Section: .dynsym + - Section: .dynstr + - Section: .dynamic + - Type: PT_DYNAMIC + VAddr: 0x800 + Sections: + - Section: .dynamic diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp --- a/llvm/tools/llvm-readobj/ELFDumper.cpp +++ b/llvm/tools/llvm-readobj/ELFDumper.cpp @@ -283,7 +283,7 @@ DynRegionInfo DynRelaRegion; DynRegionInfo DynRelrRegion; DynRegionInfo DynPLTRelRegion; - DynRegionInfo DynSymRegion; + Optional DynSymRegion; DynRegionInfo DynamicTable; StringRef DynamicStringTable; std::string SOName = ""; @@ -322,7 +322,9 @@ } Elf_Sym_Range dynamic_symbols() const { - return DynSymRegion.getAsArrayRef(); + if (!DynSymRegion) + return Elf_Sym_Range(); + return DynSymRegion->getAsArrayRef(); } Elf_Rel_Range dyn_rels() const; @@ -667,8 +669,8 @@ StrTable = DynamicStringTable; Syms = dynamic_symbols(); SymtabName = DynSymtabName; - if (DynSymRegion.Addr) - Entries = DynSymRegion.Size / DynSymRegion.EntSize; + if (DynSymRegion) + Entries = DynSymRegion->Size / DynSymRegion->EntSize; } else { if (!DotSymtabSec) return; @@ -993,7 +995,7 @@ template Error ELFDumper::LoadVersionMap() const { // If there is no dynamic symtab or version table, there is nothing to do. - if (!DynSymRegion.Addr || !SymbolVersionSection) + if (!DynSymRegion || !SymbolVersionSection) return Error::success(); // Has the VersionMap already been loaded? @@ -1043,10 +1045,11 @@ return ""; } + assert(DynSymRegion && "DynSymRegion has not been initialised"); // Determine the position in the symbol table of this entry. size_t EntryIndex = (reinterpret_cast(Sym) - - reinterpret_cast(DynSymRegion.Addr)) / - sizeof(Elf_Sym); + reinterpret_cast(DynSymRegion->Addr)) / + sizeof(Elf_Sym); // Get the corresponding version index entry. const Elf_Versym *Versym = unwrapOrError( @@ -1980,8 +1983,12 @@ ScopedPrinter &Writer) : ObjDumper(Writer), ObjF(ObjF), DynRelRegion(ObjF->getFileName()), DynRelaRegion(ObjF->getFileName()), DynRelrRegion(ObjF->getFileName()), - DynPLTRelRegion(ObjF->getFileName()), DynSymRegion(ObjF->getFileName()), - DynamicTable(ObjF->getFileName()) { + DynPLTRelRegion(ObjF->getFileName()), DynamicTable(ObjF->getFileName()) { + if (opts::Output == opts::GNU) + ELFDumperStyle.reset(new GNUStyle(Writer, this)); + else + ELFDumperStyle.reset(new LLVMStyle(Writer, this)); + const ELFFile *Obj = ObjF->getELFFile(); typename ELFT::ShdrRange Sections = unwrapOrError(ObjF->getFileName(), Obj->sections()); @@ -1992,9 +1999,9 @@ DotSymtabSec = &Sec; break; case ELF::SHT_DYNSYM: - if (!DynSymRegion.Size) { + if (!DynSymRegion) { DynSymRegion = createDRIFrom(&Sec); - DynSymRegion.Context = + DynSymRegion->Context = ("section with index " + Twine(&Sec - &Sections.front())).str(); // This is only used (if Elf_Shdr present)for naming section in GNU // style @@ -2034,11 +2041,6 @@ } loadDynamicTable(Obj); - - if (opts::Output == opts::GNU) - ELFDumperStyle.reset(new GNUStyle(Writer, this)); - else - ELFDumperStyle.reset(new LLVMStyle(Writer, this)); } template @@ -2059,6 +2061,7 @@ uint64_t SONameOffset = 0; const char *StringTableBegin = nullptr; uint64_t StringTableSize = 0; + Optional TempDynSymRegion; for (const Elf_Dyn &Dyn : dynamic_table()) { switch (Dyn.d_tag) { case ELF::DT_HASH: @@ -2077,26 +2080,13 @@ StringTableSize = Dyn.getVal(); break; case ELF::DT_SYMTAB: { - // Often we find the information about the dynamic symbol table - // location in the SHT_DYNSYM section header. However, the value in - // DT_SYMTAB has priority, because it is used by dynamic loaders to - // locate .dynsym at runtime. The location we find in the section header - // and the location we find here should match. If we can't map the - // DT_SYMTAB value to an address (e.g. when there are no program headers), we - // ignore its value. + // If we can't map the DT_SYMTAB value to an address (e.g. when there are + // no program headers), we ignore its value. if (const uint8_t *VA = toMappedAddr(Dyn.getTag(), Dyn.getPtr())) { - // EntSize is non-zero if the dynamic symbol table has been found via a - // section header. - if (DynSymRegion.EntSize && VA != DynSymRegion.Addr) - reportWarning( - createError( - "SHT_DYNSYM section header and DT_SYMTAB disagree about " - "the location of the dynamic symbol table"), - ObjF->getFileName()); - - DynSymRegion.Addr = VA; - DynSymRegion.EntSize = sizeof(Elf_Sym); - DynSymRegion.EntSizePrintName = ""; + TempDynSymRegion.emplace(ObjF->getFileName()); + TempDynSymRegion->Addr = VA; + TempDynSymRegion->EntSize = sizeof(Elf_Sym); + TempDynSymRegion->EntSizePrintName = ""; } break; } @@ -2176,6 +2166,46 @@ if (StringTableBegin) DynamicStringTable = StringRef(StringTableBegin, StringTableSize); SOName = getDynamicString(SONameOffset); + + // Often we find the information about the dynamic symbol table + // location in the SHT_DYNSYM section header. However, the value in + // DT_SYMTAB has priority, because it is used by dynamic loaders to + // locate .dynsym at runtime. The location we find in the section header + // and the location we find here should match. + if (DynSymRegion && TempDynSymRegion && + TempDynSymRegion->Addr != DynSymRegion->Addr) + reportWarning( + createError("SHT_DYNSYM section header and DT_SYMTAB disagree about " + "the location of the dynamic symbol table"), + ObjF->getFileName()); + + // Check to see if the hash table nchain value conflicts with the size of + // the dynamic symbol table according to the section header. + if (HashTable && DynSymRegion && + HashTable->nchain != DynSymRegion->Size / DynSymRegion->EntSize) + ELFDumperStyle->reportUniqueWarning(createError( + "hash table nchain (" + Twine(HashTable->nchain) + + ") differs from symbol count derived from SHT_DYNSYM section " + "header (" + + Twine(DynSymRegion->Size / DynSymRegion->EntSize) + ")")); + + // Delay the creation of the actual dynamic symbol table until now, so that + // checks can always be made against the section header-based properties, + // without worrying about tag order. + if (TempDynSymRegion) { + if (!DynSymRegion) { + DynSymRegion = TempDynSymRegion; + } else { + DynSymRegion->Addr = TempDynSymRegion->Addr; + DynSymRegion->EntSize = TempDynSymRegion->EntSize; + DynSymRegion->EntSizePrintName = TempDynSymRegion->EntSizePrintName; + } + } + + // Derive the dynamic symbol table size from the hash table, if present. + if (HashTable && DynSymRegion) { + DynSymRegion->Size = HashTable->nchain * DynSymRegion->EntSize; + } } template @@ -2591,7 +2621,7 @@ ArrayRef Buckets = GnuHashTable->buckets(); W.printList("Buckets", Buckets); - if (!DynSymRegion.Addr) { + if (!DynSymRegion) { reportWarning(createError("unable to dump 'Values' for the SHT_GNU_HASH " "section: no dynamic symbol table found"), ObjF->getFileName()); @@ -3689,10 +3719,10 @@ size_t Entries, bool NonVisibilityBitsUsed) { if (!Name.empty()) - OS << "\nSymbol table '" << Name << "' contains " << Entries - << " entries:\n"; + OS << "\nSymbol table '" << Name << "'"; else - OS << "\n Symbol table for image:\n"; + OS << "\nSymbol table for image"; + OS << " contains " << Entries << " entries:\n"; if (ELFT::Is64Bits) OS << " Num: Value Size Type Bind Vis"; @@ -5881,7 +5911,10 @@ Expected SectionName = this->dumper()->getSymbolSectionName(Symbol, *SectionIndex); if (!SectionName) { - this->reportUniqueWarning(SectionName.takeError()); + // Don't report an invalid section name if the section headers are missing. + // In such situations, all sections will be "invalid". + if (!this->dumper()->getElfObject()->sections().empty()) + this->reportUniqueWarning(SectionName.takeError()); W.printHex("Section", "", *SectionIndex); } else { W.printHex("Section", *SectionName, *SectionIndex);