Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -50,11 +50,13 @@ bool DiscardNone; bool EnableNewDtags; bool ExportDynamic; + bool GnuHash = false; bool Mips64EL = false; bool NoInhibitExec; bool NoUndefined; bool Shared; bool Static = false; + bool SysvHash = true; bool Verbose; bool ZNow = false; ELFKind EKind = ELFNoneKind; Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -155,6 +155,17 @@ Config->SoName = getString(Args, OPT_soname); Config->Sysroot = getString(Args, OPT_sysroot); + if (auto *Arg = Args.getLastArg(OPT_hash_style)) { + StringRef Style = Arg->getValue(); + if (Style == "gnu") { + Config->GnuHash = true; + Config->SysvHash = false; + } else if (Style == "both") { + Config->GnuHash = true; + } else if (Style != "sysv") + error("Unknown hash style: " + Style); + } + for (auto *Arg : Args.filtered(OPT_z)) if (Arg->getValue() == StringRef("now")) Config->ZNow = true; Index: ELF/Options.td =================================================================== --- ELF/Options.td +++ ELF/Options.td @@ -46,6 +46,9 @@ def fini : Separate<["-"], "fini">, MetaVarName<"">, HelpText<"Specify a finalizer function">; +def hash_style : Separate<["--", "-"], "hash-style">, + HelpText<"Specify hash style (sysv, gnu or both)">; + def init : Separate<["-"], "init">, MetaVarName<"">, HelpText<"Specify an initializer function">; @@ -107,6 +110,7 @@ def alias_discard_locals_X: Flag<["-"], "X">, Alias; def alias_entry_e : Separate<["-"], "e">, Alias; def alias_fini_fini : Joined<["-"], "fini=">, Alias; +def alias_hash_style_hash_style : Joined<["--", "-"], "hash-style=">, Alias; def alias_init_init : Joined<["-"], "init=">, Alias; def alias_l__library : Joined<["--"], "library=">, Alias; def alias_o_output : Joined<["--"], "output=">, Alias; @@ -123,7 +127,6 @@ def eh_frame_hdr : Flag<["--"], "eh-frame-hdr">; def end_group : Flag<["--"], "end-group">; def gc_sections : Flag<["--"], "gc-sections">; -def hash_style : Joined<["--"], "hash-style=">; def no_add_needed : Flag<["--"], "no-add-needed">; def no_fatal_warnings : Flag<["--"], "no-fatal-warnings">; def start_group : Flag<["--"], "start-group">; Index: ELF/OutputSections.h =================================================================== --- ELF/OutputSections.h +++ ELF/OutputSections.h @@ -43,6 +43,7 @@ template bool includeInSymtab(const SymbolBody &B); bool includeInDynamicSymtab(const SymbolBody &B); +bool includeInGnuHashTable(const SymbolBody &B); template bool shouldKeepInSymtab( @@ -148,7 +149,7 @@ private: void writeLocalSymbols(uint8_t *&Buf); - void writeGlobalSymbols(uint8_t *&Buf); + void writeGlobalSymbols(uint8_t *Buf); SymbolTable &Table; StringTableSection &StrTabSec; @@ -225,16 +226,46 @@ typedef typename llvm::object::ELFFile::Elf_Word Elf_Word; public: - HashTableSection(); - void addSymbol(SymbolBody *S); + HashTableSection(SymbolTable &Table); void finalize() override; void writeTo(uint8_t *Buf) override; private: + SymbolTable &Table; std::vector Hashes; }; template +class GnuHashTableSection final : public OutputSectionBase { + typedef typename llvm::object::ELFFile::Elf_Word Elf_Word; + typedef typename llvm::object::ELFFile::Elf_Off Elf_Off; + typedef typename llvm::object::ELFFile::uintX_t uintX_t; + +public: + GnuHashTableSection(); + void addSymbol(SymbolBody *S); + void finalize() override; + void writeTo(uint8_t *Buf) override; + +private: + struct HashData { + HashData(SymbolBody *S); + SymbolBody *Body; + uint32_t Hash; + }; + + void writeHeader(uint8_t *Buf); + void writeBloomFilter(uint8_t *Buf); + void writeHashTable(uint8_t *Buf); + + static const unsigned Primes[]; + std::vector HashedSymbols; + unsigned MaskWords; + unsigned NBuckets; + unsigned Shift2; +}; + +template class DynamicSection final : public OutputSectionBase { typedef OutputSectionBase Base; typedef typename llvm::object::ELFFile::Elf_Dyn Elf_Dyn; @@ -263,8 +294,9 @@ // until Writer is initialized. template struct Out { static DynamicSection *Dynamic; + static GnuHashTableSection *GnuHashTab; static GotSection *Got; - static HashTableSection *HashTab; + static HashTableSection *SysvHashTab; static InterpSection *Interp; static OutputSection *Bss; static OutputSectionBase *Opd; @@ -278,8 +310,9 @@ }; template DynamicSection *Out::Dynamic; +template GnuHashTableSection *Out::GnuHashTab; template GotSection *Out::Got; -template HashTableSection *Out::HashTab; +template HashTableSection *Out::SysvHashTab; template InterpSection *Out::Interp; template OutputSection *Out::Bss; template OutputSectionBase *Out::Opd; Index: ELF/OutputSections.cpp =================================================================== --- ELF/OutputSections.cpp +++ ELF/OutputSections.cpp @@ -11,6 +11,7 @@ #include "Config.h" #include "SymbolTable.h" #include "Target.h" +#include "llvm/Support/MathExtras.h" using namespace llvm; using namespace llvm::object; @@ -172,14 +173,15 @@ } template -HashTableSection::HashTableSection() +HashTableSection::HashTableSection(SymbolTable &Table) : OutputSectionBase(".hash", llvm::ELF::SHT_HASH, - llvm::ELF::SHF_ALLOC) { + llvm::ELF::SHF_ALLOC), + Table(Table) { this->Header.sh_entsize = sizeof(Elf_Word); this->Header.sh_addralign = sizeof(Elf_Word); } -static uint32_t hash(StringRef Name) { +static uint32_t hashSysv(StringRef Name) { uint32_t H = 0; for (char C : Name) { H = (H << 4) + C; @@ -191,17 +193,9 @@ return H; } -template void HashTableSection::addSymbol(SymbolBody *S) { - StringRef Name = S->getName(); - Out::DynSymTab->addSymbol(Name); - Hashes.push_back(hash(Name)); - S->setDynamicSymbolTableIndex(Hashes.size()); -} - template void HashTableSection::finalize() { this->Header.sh_link = Out::DynSymTab->SectionIndex; - assert(Out::DynSymTab->getNumSymbols() == Hashes.size() + 1); unsigned NumEntries = 2; // nbucket and nchain. NumEntries += Out::DynSymTab->getNumSymbols(); // The chain entries. @@ -221,13 +215,166 @@ Elf_Word *Buckets = P; Elf_Word *Chains = P + NumSymbols; - for (unsigned I = 1; I < NumSymbols; ++I) { - uint32_t Hash = Hashes[I - 1] % NumSymbols; + for (const std::pair &SymPair : Table.getSymbols()) { + StringRef Name = SymPair.first; + SymbolBody *Body = SymPair.second->Body; + if (!includeInSymtab(*Body) || !includeInDynamicSymtab(*Body)) + continue; + unsigned I = Body->getDynamicSymbolTableIndex(); + uint32_t Hash = hashSysv(Name) % NumSymbols; Chains[I] = Buckets[Hash]; Buckets[Hash] = I; } } +static uint32_t hashGnu(StringRef Name) { + uint32_t H = 5381; + for (char C : Name) + H = (H << 5) + H + C; + return H & 0xffffffff; +} + +template +GnuHashTableSection::HashData::HashData(SymbolBody *S) + : Body(S), Hash(hashGnu(S->getName())) {} + +template +GnuHashTableSection::GnuHashTableSection() + : OutputSectionBase(".gnu.hash", llvm::ELF::SHT_GNU_HASH, + llvm::ELF::SHF_ALLOC) { + this->Header.sh_entsize = + sizeof(Elf_Word) == sizeof(Elf_Off) ? sizeof(Elf_Word) : 0; + this->Header.sh_addralign = sizeof(Elf_Off); +} + +template void GnuHashTableSection::addSymbol(SymbolBody *S) { + HashedSymbols.emplace_back(S); +} + +template +const unsigned GnuHashTableSection::Primes[] = { + 1, // 1 + 1, // 2 + 3, // 3.. 4 + 3, // 5.. 8 + 7, // 9.. 16 + 13, // 17.. 32 + 31, // 33.. 64 + 61, // 65.. 128 + 127, // 129.. 256 + 251, // 257.. 512 + 509, // 513.. 1024 + 1021, // 1025.. 2048 + 2039, // 2049.. 4096 + 4093, // 4097.. 8192 + 8191, // 8193.. 16384 + 16381, // 16385.. 32768 + 32749, // 32769.. 65536 + 65521, // 65537..131072 + 131071 // 131073.. +}; + +template void GnuHashTableSection::finalize() { + this->Header.sh_link = Out::DynSymTab->SectionIndex; + + // Shift2 estimation: just predefined values. + Shift2 = ELFT::Is64Bits ? 6 : 5; + + if (HashedSymbols.empty()) { + MaskWords = 1; + NBuckets = 0; + this->Header.sh_size = sizeof(Elf_Word) * 4 // Header + + sizeof(Elf_Off); // Bloom Filter + return; + } + + const size_t Log2 = Log2_32_Ceil(HashedSymbols.size()); + + // Bloom filter estimation: at least 8 bits for each hashed symbol. + MaskWords = (1u << Log2) / sizeof(Elf_Off); + if (MaskWords == 0) + MaskWords = 1; + + // Hash table buckets count estimation: a prime number, + // less than hashed symbol count. + NBuckets = Primes[std::min(Log2, sizeof(Primes) / sizeof(Primes[0]) - 1)]; + + this->Header.sh_size = + sizeof(Elf_Word) * 4 // Header + + sizeof(Elf_Off) * MaskWords // Bloom Filter + + sizeof(Elf_Word) * NBuckets // Hash Buckets + + sizeof(Elf_Word) * HashedSymbols.size(); // Hash Values; + + // Hashed symbols have to be sorted by their (Hash % NBuckets) values. + std::stable_sort(begin(HashedSymbols), end(HashedSymbols), + [this](const HashData &L, const HashData &R) { + unsigned LBucket = L.Hash % NBuckets; + unsigned RBucket = R.Hash % NBuckets; + return LBucket < RBucket; + }); + unsigned Index = Out::DynSymTab->getNumSymbols() - HashedSymbols.size(); + for (HashData &Item : HashedSymbols) + Item.Body->setDynamicSymbolTableIndex(Index++); +} + +template void GnuHashTableSection::writeTo(uint8_t *Buf) { + writeHeader(Buf); + if (HashedSymbols.empty()) + return; + Buf += sizeof(Elf_Word) * 4; + writeBloomFilter(Buf); + Buf += sizeof(Elf_Off) * MaskWords; + writeHashTable(Buf); +} + +template +void GnuHashTableSection::writeHeader(uint8_t *Buf) { + auto *P = reinterpret_cast(Buf); + *P++ = NBuckets; + *P++ = Out::DynSymTab->getNumSymbols() - HashedSymbols.size(); + *P++ = MaskWords; + *P++ = Shift2; +} + +template +void GnuHashTableSection::writeBloomFilter(uint8_t *Buf) { + static const unsigned C = sizeof(Elf_Off) * CHAR_BIT; + const unsigned MaskWordsBitmask = MaskWords - 1; + + auto *const Masks = reinterpret_cast(Buf); + for (const HashData &Item : HashedSymbols) { + Elf_Off &Mask = Masks[(Item.Hash / C) & MaskWordsBitmask]; + Mask |= static_cast(1u) << (Item.Hash % C); + Mask |= static_cast(1u) << ((Item.Hash >> Shift2) % C); + } +} + +template +void GnuHashTableSection::writeHashTable(uint8_t *Buf) { + static const uint32_t HashMask = 0xfffffffe; + + auto *const Buckets = reinterpret_cast(Buf); + auto *const Values = Buckets + NBuckets; + + signed PrevBucketIndex = -1; + signed PrevValueIndex = -1; + signed ValueIndex = 0; + for (const HashData &Item : HashedSymbols) { + signed BucketIndex = Item.Hash % NBuckets; + assert(PrevBucketIndex <= BucketIndex); + if (BucketIndex != PrevBucketIndex) { + if (PrevValueIndex >= 0) + Values[PrevValueIndex] |= 1u; + Buckets[BucketIndex] = ValueIndex + 1; + PrevBucketIndex = BucketIndex; + } + Values[ValueIndex] = Item.Hash & HashMask; + PrevValueIndex = ValueIndex++; + } + if (PrevValueIndex >= 0) + Values[PrevValueIndex] |= 1u; +} + template DynamicSection::DynamicSection(SymbolTable &SymTab) : OutputSectionBase(".dynamic", llvm::ELF::SHT_DYNAMIC, @@ -255,7 +402,10 @@ ++NumEntries; // DT_SYMENT ++NumEntries; // DT_STRTAB ++NumEntries; // DT_STRSZ - ++NumEntries; // DT_HASH + if (Out::GnuHashTab) + ++NumEntries; // DT_GNU_HASH + if (Out::SysvHashTab) + ++NumEntries; // DT_HASH if (!Config->RPath.empty()) { ++NumEntries; // DT_RUNPATH / DT_RPATH @@ -323,7 +473,10 @@ WritePtr(DT_SYMENT, sizeof(Elf_Sym)); WritePtr(DT_STRTAB, Out::DynStrTab->getVA()); WriteVal(DT_STRSZ, Out::DynStrTab->data().size()); - WritePtr(DT_HASH, Out::HashTab->getVA()); + if (Out::GnuHashTab) + WritePtr(DT_GNU_HASH, Out::GnuHashTab->getVA()); + if (Out::SysvHashTab) + WritePtr(DT_HASH, Out::SysvHashTab->getVA()); if (!Config->RPath.empty()) @@ -526,6 +679,11 @@ return B.isUsedInDynamicReloc(); } +bool lld::elf2::includeInGnuHashTable(const SymbolBody &B) { + // Assume that includeInDynamicSymtab() is already checked. + return !B.isUndefined(); +} + template bool lld::elf2::shouldKeepInSymtab(const ObjectFile &File, StringRef SymName, @@ -616,21 +774,25 @@ } template -void SymbolTableSection::writeGlobalSymbols(uint8_t *&Buf) { +void SymbolTableSection::writeGlobalSymbols(uint8_t *Buf) { // Write the internal symbol table contents to the output symbol table // pointed by Buf. - uint8_t *Start = Buf; + auto *Start = reinterpret_cast(Buf); + auto *Current = Start; for (const std::pair &P : Table.getSymbols()) { StringRef Name = P.first; Symbol *Sym = P.second; SymbolBody *Body = Sym->Body; if (!includeInSymtab(*Body)) continue; - if (StrTabSec.isDynamic() && !includeInDynamicSymtab(*Body)) - continue; - - auto *ESym = reinterpret_cast(Buf); - Buf += sizeof(*ESym); + Elf_Sym *ESym; + if (StrTabSec.isDynamic()) { + if (!includeInDynamicSymtab(*Body)) + continue; + ESym = Start + Body->getDynamicSymbolTableIndex() - 1; + } else { + ESym = Current++; + } ESym->st_name = StrTabSec.getFileOff(Name); @@ -683,8 +845,7 @@ } if (!StrTabSec.isDynamic()) std::stable_sort( - reinterpret_cast(Start), reinterpret_cast(Buf), - [](const Elf_Sym &A, const Elf_Sym &B) -> bool { + Start, Current, [](const Elf_Sym &A, const Elf_Sym &B) -> bool { return A.getBinding() == STB_LOCAL && B.getBinding() != STB_LOCAL; }); } @@ -716,6 +877,11 @@ template class InterpSection; template class InterpSection; +template class GnuHashTableSection; +template class GnuHashTableSection; +template class GnuHashTableSection; +template class GnuHashTableSection; + template class HashTableSection; template class HashTableSection; template class HashTableSection; Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -107,8 +107,12 @@ Out::SymTab = &SymTab; SymbolTableSection DynSymTab(*Symtab, *Out::DynStrTab); Out::DynSymTab = &DynSymTab; - HashTableSection HashTab; - Out::HashTab = &HashTab; + HashTableSection HashTab(*Symtab); + if (Config->SysvHash) + Out::SysvHashTab = &HashTab; + GnuHashTableSection GnuHashTab; + if (Config->GnuHash) + Out::GnuHashTab = &GnuHashTab; RelocationSection RelaDyn(Symtab->shouldUseRela()); Out::RelaDyn = &RelaDyn; DynamicSection Dynamic(*Symtab); @@ -461,6 +465,7 @@ // FIXME: Try to avoid the extra walk over all global symbols. std::vector *> CommonSymbols; + unsigned DynamicSymbolTableIndex = 0; for (auto &P : Symtab.getSymbols()) { StringRef Name = P.first; SymbolBody *Body = P.second->Body; @@ -474,8 +479,14 @@ continue; Out::SymTab->addSymbol(Name); - if (isOutputDynamic() && includeInDynamicSymtab(*Body)) - Out::HashTab->addSymbol(Body); + if (isOutputDynamic() && includeInDynamicSymtab(*Body)) { + if (Out::GnuHashTab && includeInGnuHashTable(*Body)) { + Out::GnuHashTab->addSymbol(Body); + } else { + Body->setDynamicSymbolTableIndex(++DynamicSymbolTableIndex); + } + Out::DynSymTab->addSymbol(Body->getName()); + } } addCommonSymbols(CommonSymbols); @@ -485,7 +496,10 @@ OutputSections.push_back(Out::StrTab); if (isOutputDynamic()) { OutputSections.push_back(Out::DynSymTab); - OutputSections.push_back(Out::HashTab); + if (Out::GnuHashTab) + OutputSections.push_back(Out::GnuHashTab); + if (Out::SysvHashTab) + OutputSections.push_back(Out::SysvHashTab); OutputSections.push_back(Out::Dynamic); OutputSections.push_back(Out::DynStrTab); if (Out::RelaDyn->hasRelocs()) Index: test/elf2/gnu-hash-table.s =================================================================== --- /dev/null +++ test/elf2/gnu-hash-table.s @@ -0,0 +1,193 @@ +# RUN: echo ".globl foo" > %te.s +# RUN: llvm-mc -filetype=obj -triple=i386-pc-linux %te.s -o %te-i386.o +# RUN: llvm-mc -filetype=obj -triple=i386-pc-linux %s -o %t-i386.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t-x86_64.o +# RUN: llvm-mc -filetype=obj -triple=powerpc64-pc-linux %s -o %t-ppc64.o + +# RUN: ld.lld2 -shared --hash-style=gnu -o %te-i386.so %te-i386.o +# RUN: ld.lld2 -shared -hash-style=gnu -o %t-i386.so %t-i386.o +# RUN: ld.lld2 -shared -hash-style=gnu -o %t-x86_64.so %t-x86_64.o +# RUN: ld.lld2 -shared --hash-style both -o %t-ppc64.so %t-ppc64.o + +# RUN: llvm-readobj -dyn-symbols -gnu-hash-table %te-i386.so \ +# RUN: | FileCheck %s -check-prefix=EMPTY +# RUN: llvm-readobj -sections -dyn-symbols -gnu-hash-table %t-i386.so \ +# RUN: | FileCheck %s -check-prefix=I386 +# RUN: llvm-readobj -sections -dyn-symbols -gnu-hash-table %t-x86_64.so \ +# RUN: | FileCheck %s -check-prefix=X86_64 +# RUN: llvm-readobj -sections -dyn-symbols -gnu-hash-table %t-ppc64.so \ +# RUN: | FileCheck %s -check-prefix=PPC64 + +# EMPTY: DynamicSymbols [ +# EMPTY: Symbol { +# EMPTY: Name: foo@ +# EMPTY-NEXT: Value: 0x0 +# EMPTY-NEXT: Size: 0 +# EMPTY-NEXT: Binding: Global (0x1) +# EMPTY-NEXT: Type: None (0x0) +# EMPTY-NEXT: Other: 0 +# EMPTY-NEXT: Section: Undefined (0x0) +# EMPTY-NEXT: } +# EMPTY-NEXT: ] +# EMPTY: GnuHashTable { +# EMPTY-NEXT: Num Buckets: 0 +# EMPTY-NEXT: First Hashed Symbol Index: 2 +# EMPTY-NEXT: Num Mask Words: 1 +# EMPTY-NEXT: Shift Count: 5 +# EMPTY-NEXT: Bloom Filter: [0x0] +# EMPTY-NEXT: Buckets: [] +# EMPTY-NEXT: Values: [] +# EMPTY-NEXT: } + +# I386: Format: ELF32-i386 +# I386: Arch: i386 +# I386: AddressSize: 32bit +# I386: Sections [ +# I386: Name: .gnu.hash +# I386-NEXT: Type: SHT_GNU_HASH (0x6FFFFFF6) +# I386-NEXT: Flags [ (0x2) +# I386-NEXT: SHF_ALLOC (0x2) +# I386-NEXT: ] +# I386-NEXT: Address: +# I386-NEXT: Offset: +# I386-NEXT: Size: 32 +# I386-NEXT: Link: +# I386-NEXT: Info: 0 +# I386-NEXT: AddressAlignment: 4 +# I386-NEXT: EntrySize: 4 +# I386: ] +# I386: DynamicSymbols [ +# I386: Symbol { +# I386: Name: @ +# I386: Binding: Local (0x0) +# I386: Section: Undefined (0x0) +# I386: } +# I386: Symbol { +# I386: Name: baz@ +# I386: Binding: Global (0x1) +# I386: Section: Undefined (0x0) +# I386: } +# I386: Symbol { +# I386: Name: bar@ +# I386: Binding: Global (0x1) +# I386: Section: .text +# I386: } +# I386: Symbol { +# I386: Name: foo@ +# I386: Binding: Global (0x1) +# I386: Section: .text +# I386: } +# I386: ] +# I386: GnuHashTable { +# I386-NEXT: Num Buckets: 1 +# I386-NEXT: First Hashed Symbol Index: 2 +# I386-NEXT: Num Mask Words: 1 +# I386-NEXT: Shift Count: 5 +# I386-NEXT: Bloom Filter: [0x14000220] +# I386-NEXT: Buckets: [1] +# I386-NEXT: Values: [0xB8860BA, 0xB887389] +# I386-NEXT: } + +# X86_64: Format: ELF64-x86-64 +# X86_64: Arch: x86_64 +# X86_64: AddressSize: 64bit +# X86_64: Sections [ +# X86_64: Name: .gnu.hash (40) +# X86_64-NEXT: Type: SHT_GNU_HASH (0x6FFFFFF6) +# X86_64-NEXT: Flags [ (0x2) +# X86_64-NEXT: SHF_ALLOC (0x2) +# X86_64-NEXT: ] +# X86_64-NEXT: Address: +# X86_64-NEXT: Offset: +# X86_64-NEXT: Size: 36 +# X86_64-NEXT: Link: +# X86_64-NEXT: Info: 0 +# X86_64-NEXT: AddressAlignment: 8 +# X86_64-NEXT: EntrySize: 0 +# X86_64-NEXT: } +# X86_64: ] +# X86_64: DynamicSymbols [ +# X86_64: Symbol { +# X86_64: Name: @ +# X86_64: Binding: Local (0x0) +# X86_64: Section: Undefined (0x0) +# X86_64: } +# X86_64: Symbol { +# X86_64: Name: baz@ +# X86_64: Binding: Global (0x1) +# X86_64: Section: Undefined (0x0) +# X86_64: } +# X86_64: Symbol { +# X86_64: Name: bar@ +# X86_64: Binding: Global (0x1) +# X86_64: Section: .text +# X86_64: } +# X86_64: Symbol { +# X86_64: Name: foo@ +# X86_64: Binding: Global (0x1) +# X86_64: Section: .text +# X86_64: } +# X86_64: ] +# X86_64: GnuHashTable { +# X86_64-NEXT: Num Buckets: 1 +# X86_64-NEXT: First Hashed Symbol Index: 2 +# X86_64-NEXT: Num Mask Words: 1 +# X86_64-NEXT: Shift Count: 6 +# X86_64-NEXT: Bloom Filter: [0x400000000004204] +# X86_64-NEXT: Buckets: [1] +# X86_64-NEXT: Values: [0xB8860BA, 0xB887389] +# X86_64-NEXT: } + +# PPC64: Format: ELF64-ppc64 +# PPC64: Arch: powerpc64 +# PPC64: AddressSize: 64bit +# PPC64: Sections [ +# PPC64: Name: .gnu.hash (40) +# PPC64-NEXT: Type: SHT_GNU_HASH (0x6FFFFFF6) +# PPC64-NEXT: Flags [ (0x2) +# PPC64-NEXT: SHF_ALLOC (0x2) +# PPC64-NEXT: ] +# PPC64-NEXT: Address: 0x180 +# PPC64-NEXT: Offset: 0x180 +# PPC64-NEXT: Size: 36 +# PPC64-NEXT: Link: 1 +# PPC64-NEXT: Info: 0 +# PPC64-NEXT: AddressAlignment: 8 +# PPC64-NEXT: EntrySize: 0 +# PPC64-NEXT: } +# PPC64: ] +# PPC64: DynamicSymbols [ +# PPC64: Symbol { +# PPC64: Name: @ +# PPC64: Binding: Local (0x0) +# PPC64: Section: Undefined (0x0) +# PPC64: } +# PPC64: Symbol { +# PPC64: Name: baz@ +# PPC64: Binding: Global (0x1) +# PPC64: Section: Undefined (0x0) +# PPC64: } +# PPC64: Symbol { +# PPC64: Name: bar@ +# PPC64: Binding: Global (0x1) +# PPC64: Section: .text +# PPC64: } +# PPC64: Symbol { +# PPC64: Name: foo@ +# PPC64: Binding: Global (0x1) +# PPC64: Section: .text +# PPC64: } +# PPC64: ] +# PPC64: GnuHashTable { +# PPC64-NEXT: Num Buckets: 1 +# PPC64-NEXT: First Hashed Symbol Index: 2 +# PPC64-NEXT: Num Mask Words: 1 +# PPC64-NEXT: Shift Count: 6 +# PPC64-NEXT: Bloom Filter: [0x400000000004204] +# PPC64-NEXT: Buckets: [1] +# PPC64-NEXT: Values: [0xB8860BA, 0xB887389] +# PPC64-NEXT: } + +.globl foo,bar,baz +foo: +bar: