Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -51,11 +51,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 ZNodelete = false; bool ZNow = false; 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_undefined)) Config->Undefined.push_back(Arg->getValue()); 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; @@ -124,7 +128,6 @@ def end_group : Flag<["--"], "end-group">; def fatal_warnings : Flag<["--"], "fatal-warnings">; 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 no_warn_mismatch : Flag<["--"], "no-warn-mismatch">; Index: ELF/OutputSections.h =================================================================== --- ELF/OutputSections.h +++ ELF/OutputSections.h @@ -57,6 +57,7 @@ template bool includeInSymtab(const SymbolBody &B); bool includeInDynamicSymtab(const SymbolBody &B); +bool includeInGnuHashTable(const SymbolBody &B); template bool shouldKeepInSymtab( @@ -156,18 +157,33 @@ void finalize() override; void writeTo(uint8_t *Buf) override; - void addSymbol(StringRef Name, bool isLocal = false); + void addLocalSymbol(StringRef Name); + void addSymbol(SymbolBody *Body); StringTableSection &getStrTabSec() const { return StrTabSec; } unsigned getNumSymbols() const { return NumVisible + 1; } + unsigned getNumGnuHashSymbols() const { return NumGnuHashed; } + + struct SymbolData { + SymbolData(SymbolBody *Body, bool GnuHashed); + SymbolBody *Body; + bool GnuHashed; + uint32_t GnuHash; + }; + ArrayRef getSymbols() const; + ArrayRef getGnuHashSymbols() const; private: void writeLocalSymbols(uint8_t *&Buf); void writeGlobalSymbols(uint8_t *Buf); + static unsigned char getSymbolBinding(SymbolBody *Body); + SymbolTable &Table; StringTableSection &StrTabSec; + std::vector Symbols; unsigned NumVisible = 0; unsigned NumLocals = 0; + unsigned NumGnuHashed = 0; }; template @@ -257,12 +273,35 @@ public: HashTableSection(); - void addSymbol(SymbolBody *S); + void finalize() override; + void writeTo(uint8_t *Buf) override; +}; + +// Outputs Gnu Hash section. For detailed explanation see: +// https://blogs.oracle.com/ali/entry/gnu_hash_elf_sections +template +class GnuHashTableSection final : public OutputSectionBase { + typedef typename llvm::object::ELFFile::Elf_Off Elf_Off; + typedef typename llvm::object::ELFFile::Elf_Word Elf_Word; + typedef typename llvm::object::ELFFile::uintX_t uintX_t; + +public: + GnuHashTableSection(); void finalize() override; void writeTo(uint8_t *Buf) override; + static unsigned calcNBuckets(unsigned NumHashed); + private: - std::vector Hashes; + static unsigned calcMaskWords(unsigned NumHashed); + + void writeHeader(uint8_t *Buf); + void writeBloomFilter(uint8_t *Buf); + void writeHashTable(uint8_t *Buf); + + unsigned MaskWords; + unsigned NBuckets; + unsigned Shift2; }; template @@ -294,6 +333,7 @@ // until Writer is initialized. template struct Out { static DynamicSection *Dynamic; + static GnuHashTableSection *GnuHashTab; static GotSection *Got; static HashTableSection *HashTab; static InterpSection *Interp; @@ -309,6 +349,7 @@ }; template DynamicSection *Out::Dynamic; +template GnuHashTableSection *Out::GnuHashTab; template GotSection *Out::Got; template HashTableSection *Out::HashTab; template InterpSection *Out::Interp; 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; @@ -184,7 +185,7 @@ 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; @@ -196,17 +197,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. @@ -226,13 +219,129 @@ 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 typename SymbolTableSection::SymbolData &Item : + Out::DynSymTab->getSymbols()) { + SymbolBody *Body = Item.Body; + StringRef Name = Body->getName(); + 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 (uint8_t C : Name) + H = (H << 5) + H + C; + return H & 0xffffffff; +} + +template +GnuHashTableSection::GnuHashTableSection() + : OutputSectionBase(".gnu.hash", llvm::ELF::SHT_GNU_HASH, + llvm::ELF::SHF_ALLOC) { + this->Header.sh_entsize = ELFT::Is64Bits ? 0 : 4; + this->Header.sh_addralign = ELFT::Is64Bits ? 8 : 4; +} + +template +unsigned GnuHashTableSection::calcNBuckets(unsigned NumHashed) { + if (!NumHashed) + return 0; + + // These values are prime numbers which are not greater than 2^(N-1) + 1. + // In result, for any particular NumHashed we return a prime number + // which is not greater than NumHashed. + // No deep investigation was done, it just looks good for the first try. + static const unsigned Primes[] = { + 1, 1, 3, 3, 7, 13, 31, 61, 127, 251, + 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071}; + + return Primes[std::min(Log2_32_Ceil(NumHashed), + array_lengthof(Primes) - 1)]; +} + +template +unsigned GnuHashTableSection::calcMaskWords(unsigned NumHashed) { + if (!NumHashed) + return 1; + // Bloom filter estimation: at least 8 bits for each hashed symbol. + return std::max((1u << Log2_32_Ceil(NumHashed)) / sizeof(Elf_Off), + 1); +} + +template void GnuHashTableSection::finalize() { + const unsigned NumHashed = Out::DynSymTab->getNumGnuHashSymbols(); + NBuckets = calcNBuckets(NumHashed); + MaskWords = calcMaskWords(NumHashed); + // Second hash shift estimation: just predefined values. + Shift2 = ELFT::Is64Bits ? 6 : 5; + + this->Header.sh_link = Out::DynSymTab->SectionIndex; + this->Header.sh_size = sizeof(Elf_Word) * 4 // Header + + sizeof(Elf_Off) * MaskWords // Bloom Filter + + sizeof(Elf_Word) * NBuckets // Hash Buckets + + sizeof(Elf_Word) * NumHashed; // Hash Values +} + +template void GnuHashTableSection::writeTo(uint8_t *Buf) { + writeHeader(Buf); + if (!NBuckets) // There are no hashed symbols + 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() - + Out::DynSymTab->getNumGnuHashSymbols(); + *P++ = MaskWords; + *P++ = Shift2; +} + +template +void GnuHashTableSection::writeBloomFilter(uint8_t *Buf) { + unsigned C = sizeof(Elf_Off) * 8; + + auto *Masks = reinterpret_cast(Buf); + for (const typename SymbolTableSection::SymbolData &Item : + Out::DynSymTab->getGnuHashSymbols()) + Masks[(Item.GnuHash / C) & (MaskWords - 1)] |= + (static_cast(1) << (Item.GnuHash % C)) | + (static_cast(1) << ((Item.GnuHash >> Shift2) % C)); +} + +template +void GnuHashTableSection::writeHashTable(uint8_t *Buf) { + Elf_Word *Buckets = reinterpret_cast(Buf); + Elf_Word *Values = Buckets + NBuckets; + + int PrevBucketIndex = -1; + int PrevValueIndex = -1; + int ValueIndex = 0; + for (const typename SymbolTableSection::SymbolData &Item : + Out::DynSymTab->getGnuHashSymbols()) { + int BucketIndex = Item.GnuHash % NBuckets; + assert(PrevBucketIndex <= BucketIndex); + if (BucketIndex != PrevBucketIndex) { + if (PrevValueIndex >= 0) + Values[PrevValueIndex] |= 1; + Buckets[BucketIndex] = ValueIndex + 1; + PrevBucketIndex = BucketIndex; + } + Values[ValueIndex] = Item.GnuHash & ~1; + PrevValueIndex = ValueIndex++; + } + if (PrevValueIndex >= 0) + Values[PrevValueIndex] |= 1; +} + template DynamicSection::DynamicSection(SymbolTable &SymTab) : OutputSectionBase(".dynamic", llvm::ELF::SHT_DYNAMIC, @@ -260,7 +369,10 @@ ++NumEntries; // DT_SYMENT ++NumEntries; // DT_STRTAB ++NumEntries; // DT_STRSZ - ++NumEntries; // DT_HASH + if (Out::GnuHashTab) + ++NumEntries; // DT_GNU_HASH + if (Out::HashTab) + ++NumEntries; // DT_HASH if (!Config->RPath.empty()) { ++NumEntries; // DT_RUNPATH / DT_RPATH @@ -330,7 +442,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::HashTab) + WritePtr(DT_HASH, Out::HashTab->getVA()); if (!Config->RPath.empty()) @@ -591,6 +706,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, @@ -610,6 +730,12 @@ } template +SymbolTableSection::SymbolData::SymbolData(SymbolBody *Body, + bool GnuHashed) + : Body(Body), GnuHashed(GnuHashed), + GnuHash(GnuHashed ? hashGnu(Body->getName()) : 0) {} + +template SymbolTableSection::SymbolTableSection( SymbolTable &Table, StringTableSection &StrTabSec) : OutputSectionBase( @@ -628,14 +754,50 @@ this->Header.sh_size = getNumSymbols() * sizeof(Elf_Sym); this->Header.sh_link = StrTabSec.SectionIndex; this->Header.sh_info = NumLocals + 1; + + const unsigned NBuckets = + NumGnuHashed ? GnuHashTableSection::calcNBuckets(NumGnuHashed) : 0; + std::stable_sort( + Symbols.begin(), Symbols.end(), + [NBuckets](const SymbolData &L, const SymbolData &R) -> bool { + const bool LLocal = getSymbolBinding(L.Body) == STB_LOCAL; + const bool RLocal = getSymbolBinding(R.Body) == STB_LOCAL; + if (LLocal || RLocal) + return !RLocal; + if (!L.GnuHashed || !R.GnuHashed) + return R.GnuHashed; + return L.GnuHash % NBuckets < R.GnuHash % NBuckets; + }); + if (StrTabSec.isDynamic()) { + // All symbols which belong to Gnu Hash section + // should be placed after not hashed symbols. + assert(std::is_sorted(Symbols.begin(), Symbols.end(), + [](const SymbolData &L, const SymbolData &R) { + return !L.GnuHashed && R.GnuHashed; + })); + unsigned Index = 0; + for (const SymbolData &Item : Symbols) { + Item.Body->setDynamicSymbolTableIndex(++Index); + } + } } template -void SymbolTableSection::addSymbol(StringRef Name, bool isLocal) { +void SymbolTableSection::addLocalSymbol(StringRef Name) { StrTabSec.add(Name); ++NumVisible; - if (isLocal) - ++NumLocals; + ++NumLocals; +} + +template +void SymbolTableSection::addSymbol(SymbolBody *Body) { + StrTabSec.add(Body->getName()); + const bool GnuHashed = StrTabSec.isDynamic() && Out::GnuHashTab && + includeInGnuHashTable(*Body); + Symbols.emplace_back(Body, GnuHashed); + ++NumVisible; + if (GnuHashed) + ++NumGnuHashed; } template void SymbolTableSection::writeTo(uint8_t *Buf) { @@ -686,18 +848,10 @@ void SymbolTableSection::writeGlobalSymbols(uint8_t *Buf) { // Write the internal symbol table contents to the output symbol table // pointed by Buf. - uint8_t *Start = Buf; - 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); + auto *ESym = reinterpret_cast(Buf); + for (const SymbolData &Item : Symbols) { + SymbolBody *Body = Item.Body; + StringRef Name = Body->getName(); ESym->st_name = StrTabSec.getFileOff(Name); @@ -721,19 +875,16 @@ break; } - unsigned char Binding = Body->isWeak() ? STB_WEAK : STB_GLOBAL; + unsigned char Binding = getSymbolBinding(Body); unsigned char Type = STT_NOTYPE; uintX_t Size = 0; if (const auto *EBody = dyn_cast>(Body)) { const Elf_Sym &InputSym = EBody->Sym; - Binding = InputSym.getBinding(); Type = InputSym.getType(); Size = InputSym.st_size; } unsigned char Visibility = Body->getMostConstrainingVisibility(); - if (Visibility != STV_DEFAULT && Visibility != STV_PROTECTED) - Binding = STB_LOCAL; ESym->setBindingAndType(Binding, Type); ESym->st_size = Size; @@ -747,13 +898,31 @@ ESym->st_shndx = SHN_ABS; else if (OutSec) ESym->st_shndx = OutSec->SectionIndex; + + ++ESym; } - if (!StrTabSec.isDynamic()) - std::stable_sort( - reinterpret_cast(Start), reinterpret_cast(Buf), - [](const Elf_Sym &A, const Elf_Sym &B) -> bool { - return A.getBinding() == STB_LOCAL && B.getBinding() != STB_LOCAL; - }); +} + +template +unsigned char SymbolTableSection::getSymbolBinding(SymbolBody *Body) { + unsigned char Visibility = Body->getMostConstrainingVisibility(); + if (Visibility != STV_DEFAULT && Visibility != STV_PROTECTED) + return STB_LOCAL; + if (const auto *EBody = dyn_cast>(Body)) + return EBody->Sym.getBinding(); + return Body->isWeak() ? STB_WEAK : STB_GLOBAL; +} + +template +ArrayRef::SymbolData> +SymbolTableSection::getSymbols() const { + return Symbols; +} + +template +ArrayRef::SymbolData> +SymbolTableSection::getGnuHashSymbols() const { + return getSymbols().slice(Symbols.size() - NumGnuHashed); } namespace lld { @@ -783,6 +952,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 @@ -109,7 +109,11 @@ SymbolTableSection DynSymTab(*Symtab, *Out::DynStrTab); Out::DynSymTab = &DynSymTab; HashTableSection HashTab; - Out::HashTab = &HashTab; + if (Config->SysvHash) + Out::HashTab = &HashTab; + GnuHashTableSection GnuHashTab; + if (Config->GnuHash) + Out::GnuHashTab = &GnuHashTab; RelocationSection RelaDyn(Symtab->shouldUseRela()); Out::RelaDyn = &RelaDyn; DynamicSection Dynamic(*Symtab); @@ -264,7 +268,7 @@ StringRef SymName = *SymNameOrErr; if (!shouldKeepInSymtab(*F, SymName, Sym)) continue; - Out::SymTab->addSymbol(SymName, true); + Out::SymTab->addLocalSymbol(SymName); } } } @@ -480,7 +484,6 @@ // FIXME: Try to avoid the extra walk over all global symbols. std::vector *> CommonSymbols; for (auto &P : Symtab.getSymbols()) { - StringRef Name = P.first; SymbolBody *Body = P.second->Body; if (auto *U = dyn_cast>(Body)) if (!U->isWeak() && !U->canKeepUndefined()) @@ -490,10 +493,11 @@ CommonSymbols.push_back(C); if (!includeInSymtab(*Body)) continue; - Out::SymTab->addSymbol(Name); + Out::SymTab->addSymbol(Body); - if (isOutputDynamic() && includeInDynamicSymtab(*Body)) - Out::HashTab->addSymbol(Body); + if (isOutputDynamic() && includeInDynamicSymtab(*Body)) { + Out::DynSymTab->addSymbol(Body); + } } addCommonSymbols(CommonSymbols); @@ -503,7 +507,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::HashTab) + OutputSections.push_back(Out::HashTab); 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: