diff --git a/llvm/test/tools/llvm-readobj/ELF/gnuhash.test b/llvm/test/tools/llvm-readobj/ELF/gnuhash.test --- a/llvm/test/tools/llvm-readobj/ELF/gnuhash.test +++ b/llvm/test/tools/llvm-readobj/ELF/gnuhash.test @@ -127,7 +127,7 @@ # SYMNDX-NEXT: Shift Count: 0 # SYMNDX-NEXT: Bloom Filter: [0x1] # SYMNDX-NEXT: Buckets: [2] -# SYMNDX-NEXT: warning: '[[FILE]]': the first hashed symbol index (2) is larger than the number of dynamic symbols (2) +# SYMNDX-NEXT: warning: '[[FILE]]': unable to dump 'Values' for the SHT_GNU_HASH section: the first hashed symbol index (2) is larger than the number of dynamic symbols (2) # SYMNDX-NEXT: } --- !ELF diff --git a/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test b/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test --- a/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test +++ b/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test @@ -318,3 +318,112 @@ Sections: - Section: .gnu.hash - Section: .dynamic + +## Linkers might produce an empty no-op SHT_GNU_HASH section when +## there are no dynamic symbols or when all dynamic symbols are undefined. +## Such sections normally have a single zero entry in the bloom +## filter, a single zero entry in the hash bucket and no values. +## +## The index of the first symbol in the dynamic symbol table +## included in the hash table can be set to the number of dynamic symbols, +## which is one larger than the index of the last dynamic symbol. +## For empty tables however, this value is unimportant and can be ignored. + +## Check the case when a 'symndx' index of the first symbol in the dynamic symbol +## table is larger than the number of dynamic symbols. + +## Case A: when the buckets array is not empty and has a non-zero value we report a warning. +# RUN: yaml2obj --docnum=7 -DVAL=0x1 %s -o %t9 +# RUN: llvm-readelf --elf-hash-histogram %t9 2>&1 | \ +# RUN: FileCheck %s -DFILE=%t9 --check-prefix=ERR6 + +# ERR6: warning: '[[FILE]]': unable to print the GNU hash table histogram: the first hashed symbol index (16) is larger than the number of dynamic symbols (1) + +## Case B: we do not report a warning when the buckets array contains only zero values. +# RUN: yaml2obj --docnum=7 -DVAL=0x0 %s -o %t10 +# RUN: llvm-readelf --elf-hash-histogram %t10 2>&1 | \ +# RUN: FileCheck %s --allow-empty --implicit-check-not="Histogram" + +## Case C: we do not report a warning when the buckets array is empty. +# RUN: yaml2obj --docnum=7 -DVAL="" %s -o %t11 +# RUN: llvm-readelf --elf-hash-histogram %t11 2>&1 | \ +# RUN: FileCheck %s --allow-empty --implicit-check-not="Histogram" + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .gnu.hash + Type: SHT_GNU_HASH + Flags: [ SHF_ALLOC ] + Header: + SymNdx: 0x10 + Shift2: 0x0 + BloomFilter: [ 0x0 ] + HashBuckets: [ [[VAL]] ] + HashValues: [ 0x0 ] + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_ALLOC ] + Link: .dynstr + Entries: + - Tag: DT_GNU_HASH + Value: 0x0 + - Tag: DT_NULL + Value: 0x0 +DynamicSymbols: [] +ProgramHeaders: + - Type: PT_LOAD + Sections: + - Section: .gnu.hash + - Section: .dynamic + +## Check we report warnings when the dynamic symbol table is absent or empty. + +## The code locates the dynamic symbol table by the section type. Use SHT_PROGBITS to hide it. +# RUN: yaml2obj --docnum=8 -DTYPE=SHT_PROGBITS %s -o %t12 +# RUN: llvm-readelf --elf-hash-histogram %t12 2>&1 | \ +# RUN: FileCheck %s -DFILE=%t12 --check-prefix=ERR7 + +# ERR7: warning: '[[FILE]]': unable to print the GNU hash table histogram: no dynamic symbol table found + +# RUN: yaml2obj --docnum=8 -DTYPE=SHT_DYNSYM %s -o %t13 +# RUN: llvm-readelf --elf-hash-histogram %t13 2>&1 | \ +# RUN: FileCheck %s -DFILE=%t13 --check-prefix=ERR8 + +# ERR8: warning: '[[FILE]]': unable to print the GNU hash table histogram: the dynamic symbol table is empty + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .gnu.hash + Type: SHT_GNU_HASH + Flags: [ SHF_ALLOC ] + Header: + SymNdx: 0x0 + Shift2: 0x0 + BloomFilter: [ 0x0 ] + HashBuckets: [ 0x0 ] + HashValues: [ 0x0 ] + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_ALLOC ] + Entries: + - Tag: DT_GNU_HASH + Value: 0x0 + - Tag: DT_NULL + Value: 0x0 + - Name: .dynsym + Type: [[TYPE]] + Size: 0 +ProgramHeaders: + - Type: PT_LOAD + Sections: + - Section: .gnu.hash 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 @@ -325,6 +325,8 @@ return Table.slice(0, Size); } + Optional getDynSymRegion() const { return DynSymRegion; } + Elf_Sym_Range dynamic_symbols() const { if (!DynSymRegion) return Elf_Sym_Range(); @@ -2718,12 +2720,16 @@ template static Expected> -getGnuHashTableChains(const typename ELFT::SymRange DynSymTable, +getGnuHashTableChains(Optional DynSymRegion, const typename ELFT::GnuHash *GnuHashTable) { + if (!DynSymRegion) + return createError("no dynamic symbol table found"); + + ArrayRef DynSymTable = + DynSymRegion->getAsArrayRef(); size_t NumSyms = DynSymTable.size(); if (!NumSyms) - return createError("unable to dump 'Values' for the SHT_GNU_HASH " - "section: the dynamic symbol table is empty"); + return createError("the dynamic symbol table is empty"); if (GnuHashTable->symndx < NumSyms) return GnuHashTable->values(NumSyms); @@ -2773,17 +2779,13 @@ ArrayRef Buckets = GnuHashTable->buckets(); W.printList("Buckets", Buckets); - if (!DynSymRegion) { - reportWarning(createError("unable to dump 'Values' for the SHT_GNU_HASH " - "section: no dynamic symbol table found"), - ObjF->getFileName()); - return; - } - Expected> Chains = - getGnuHashTableChains(dynamic_symbols(), GnuHashTable); + getGnuHashTableChains(DynSymRegion, GnuHashTable); if (!Chains) { - reportUniqueWarning(Chains.takeError()); + reportUniqueWarning( + createError("unable to dump 'Values' for the SHT_GNU_HASH " + "section: " + + toString(Chains.takeError()))); return; } @@ -4660,20 +4662,26 @@ template void GNUStyle::printGnuHashHistogram(const Elf_GnuHash &GnuHashTable) { - size_t NBucket = GnuHashTable.nbuckets; - ArrayRef Buckets = GnuHashTable.buckets(); - unsigned NumSyms = this->dumper()->dynamic_symbols().size(); - if (!NumSyms) + Expected> ChainsOrErr = getGnuHashTableChains( + this->dumper()->getDynSymRegion(), &GnuHashTable); + if (!ChainsOrErr) { + this->reportUniqueWarning( + createError("unable to print the GNU hash table histogram: " + + toString(ChainsOrErr.takeError()))); return; - ArrayRef Chains = GnuHashTable.values(NumSyms); + } + + ArrayRef Chains = *ChainsOrErr; size_t Symndx = GnuHashTable.symndx; size_t TotalSyms = 0; size_t MaxChain = 1; size_t CumulativeNonZero = 0; + size_t NBucket = GnuHashTable.nbuckets; if (Chains.empty() || NBucket == 0) return; + ArrayRef Buckets = GnuHashTable.buckets(); std::vector ChainLen(NBucket, 0); for (size_t B = 0; B < NBucket; B++) { if (!Buckets[B])