Index: ELF/LinkerScript.h =================================================================== --- ELF/LinkerScript.h +++ ELF/LinkerScript.h @@ -10,6 +10,7 @@ #ifndef LLD_ELF_LINKER_SCRIPT_H #define LLD_ELF_LINKER_SCRIPT_H +#include "Writer.h" #include "lld/Core/LLVM.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/MapVector.h" @@ -46,6 +47,14 @@ SectionsCommandKind Kind; std::vector Expr; StringRef Name; + std::vector Headers; +}; + +struct PhdrsCommand { + StringRef HeaderName; + unsigned HeaderType; + bool HasFilehdr; + bool HasPhdrs; }; // ScriptConfiguration holds linker script parse results. @@ -59,6 +68,9 @@ // Used to assign addresses to sections. std::vector Commands; + // Used to assign sections to headers. + std::vector PhdrsCommands; + bool DoLayout = false; llvm::BumpPtrAllocator Alloc; @@ -75,6 +87,8 @@ typedef typename ELFT::uint uintX_t; public: + typedef Phdr Phdr; + StringRef getOutputSection(InputSectionBase *S); ArrayRef getFiller(StringRef Name); bool isDiscarded(InputSectionBase *S); @@ -82,12 +96,15 @@ void assignAddresses(ArrayRef *> S); int compareSections(StringRef A, StringRef B); void addScriptedSymbols(); + std::vector createPhdrs(ArrayRef *> S); + bool hasPhdrsCommands(); private: // "ScriptConfig" is a bit too long, so define a short name for it. ScriptConfiguration &Opt = *ScriptConfig; int getSectionIndex(StringRef Name); + std::vector getPhdrIndicesForSection(StringRef Name); uintX_t Dot; }; Index: ELF/LinkerScript.cpp =================================================================== --- ELF/LinkerScript.cpp +++ ELF/LinkerScript.cpp @@ -23,6 +23,7 @@ #include "Symbols.h" #include "SymbolTable.h" #include "Target.h" +#include "Writer.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ELF.h" #include "llvm/Support/FileSystem.h" @@ -218,7 +219,7 @@ for (OutputSectionBase *Sec : Sections) { StringRef Name = Sec->getName(); if (getSectionIndex(Name) == INT_MAX) - Opt.Commands.push_back({SectionKind, {}, Name}); + Opt.Commands.push_back({SectionKind, {}, Name, {}}); } // Assign addresses as instructed by linker script SECTIONS sub-commands. @@ -277,6 +278,92 @@ } template +std::vector> +LinkerScript::createPhdrs(ArrayRef *> Sections) { + int TlsNum = -1; + int NoteNum = -1; + int RelroNum = -1; + Phdr *Load = nullptr; + uintX_t Flags = PF_R; + std::vector Phdrs; + + for (const PhdrsCommand &Hdr : Opt.PhdrsCommands) { + Phdrs.emplace_back(Hdr.HeaderType, PF_R); + Phdr &Added = Phdrs.back(); + + if (Hdr.HasFilehdr) + Added.AddSec(Out::ElfHeader); + if (Hdr.HasPhdrs) + Added.AddSec(Out::ProgramHeaders); + + switch (Hdr.HeaderType) { + case PT_INTERP: + if (needsInterpSection()) + Added.AddSec(Out::Interp); + break; + case PT_DYNAMIC: + if (!isOutputDynamic()) + continue; + Added.H.p_flags = toPhdrFlags(Out::Dynamic->getFlags()); + Added.AddSec(Out::Dynamic); + break; + case PT_TLS: + TlsNum = Phdrs.size() - 1; + break; + case PT_NOTE: + NoteNum = Phdrs.size() - 1; + break; + case PT_GNU_RELRO: + RelroNum = Phdrs.size() - 1; + break; + case PT_GNU_EH_FRAME: + if (Out::EhFrame->empty() || !Out::EhFrameHdr) + continue; + Added.H.p_flags = toPhdrFlags(Out::EhFrameHdr->getFlags()); + Added.AddSec(Out::EhFrameHdr); + break; + } + } + + for (OutputSectionBase *Sec : Sections) { + if (!(Sec->getFlags() & SHF_ALLOC)) + break; + + if (TlsNum != -1 && (Sec->getFlags() & SHF_TLS)) + Phdrs[TlsNum].AddSec(Sec); + + if (!needsPtLoad(Sec)) + continue; + + std::vector HeaderIds = + Script::X->getPhdrIndicesForSection(Sec->getName()); + + if (!HeaderIds.empty()) { + // Assign headers specified by linker script + for (size_t HdrId : HeaderIds) { + Phdrs[HdrId].AddSec(Sec); + Phdrs[HdrId].H.p_flags |= toPhdrFlags(Sec->getFlags()); + } + } else { + // If we have no load segment or flags've changed then we want new load + // segment. + uintX_t NewFlags = toPhdrFlags(Sec->getFlags()); + if (Load == nullptr || Flags != NewFlags) { + Load = &*Phdrs.emplace(Phdrs.end(), PT_LOAD, NewFlags); + Flags = NewFlags; + } + Load->AddSec(Sec); + } + + if (RelroNum != -1 && isRelroSection(Sec)) + Phdrs[RelroNum].AddSec(Sec); + if (NoteNum != -1 && Sec->getType() == SHT_NOTE) + Phdrs[NoteNum].AddSec(Sec); + } + return Phdrs; +} + +template ArrayRef LinkerScript::getFiller(StringRef Name) { auto I = Opt.Filler.find(Name); if (I == Opt.Filler.end()) @@ -316,6 +403,35 @@ Symtab::X->addAbsolute(Cmd.Name, STV_DEFAULT); } +template bool LinkerScript::hasPhdrsCommands() { + return !Opt.PhdrsCommands.empty(); +} + +// Returns indices ELF headers containing specific section, identified +// by Name. Each index is a zero based number of ELF header listed within +// PHDRS {} script block. +template +std::vector +LinkerScript::getPhdrIndicesForSection(StringRef Name) { + std::vector Indices; + auto ItSect = std::find_if( + Opt.Commands.begin(), Opt.Commands.end(), + [Name](const SectionsCommand &Cmd) { return Cmd.Name == Name; }); + if (ItSect != Opt.Commands.end()) { + SectionsCommand &SecCmd = (*ItSect); + for (StringRef HdrName : SecCmd.Headers) { + auto ItHdr = std::find_if( + Opt.PhdrsCommands.rbegin(), Opt.PhdrsCommands.rend(), + [HdrName](PhdrsCommand &Cmd) { return Cmd.HeaderName == HdrName; }); + if (ItHdr == Opt.PhdrsCommands.rend()) + error("section header '" + HdrName + "' is not listed in PHDRS"); + else + Indices.push_back(std::distance(ItHdr, Opt.PhdrsCommands.rend()) - 1); + } + } + return Indices; +} + class elf::ScriptParser : public ScriptParserBase { typedef void (ScriptParser::*Handler)(); @@ -336,11 +452,14 @@ void readOutput(); void readOutputArch(); void readOutputFormat(); + void readPhdrs(); void readSearchDir(); void readSections(); void readLocationCounterValue(); void readOutputSectionDescription(StringRef OutSec); + void readOutputSectionHeaders(); + void readPhdrsType(); void readSymbolAssignment(StringRef Name); std::vector readSectionsCommandExpr(); @@ -359,6 +478,7 @@ {"OUTPUT", &ScriptParser::readOutput}, {"OUTPUT_ARCH", &ScriptParser::readOutputArch}, {"OUTPUT_FORMAT", &ScriptParser::readOutputFormat}, + {"PHDRS", &ScriptParser::readPhdrs}, {"SEARCH_DIR", &ScriptParser::readSearchDir}, {"SECTIONS", &ScriptParser::readSections}, {";", &ScriptParser::readNothing}}; @@ -495,6 +615,28 @@ expect(")"); } +void ScriptParser::readPhdrs() { + expect("{"); + while (!Error && !skip("}")) { + StringRef Tok = next(); + Opt.PhdrsCommands.push_back({Tok, PT_NULL, false, false}); + PhdrsCommand &PhdrCmd = Opt.PhdrsCommands.back(); + + readPhdrsType(); + do { + Tok = next(); + if (Tok == ";") + break; + if (Tok == "FILEHDR") + PhdrCmd.HasFilehdr = true; + else if (Tok == "PHDRS") + PhdrCmd.HasPhdrs = true; + else + setError("unexpected header attribute: " + Tok); + } while (!Error); + } +} + void ScriptParser::readSearchDir() { expect("("); Config->SearchPaths.push_back(next()); @@ -525,11 +667,11 @@ if (Expr.empty()) error("error in location counter expression"); else - Opt.Commands.push_back({ExprKind, std::move(Expr), ""}); + Opt.Commands.push_back({ExprKind, std::move(Expr), "", {}}); } void ScriptParser::readOutputSectionDescription(StringRef OutSec) { - Opt.Commands.push_back({SectionKind, {}, OutSec}); + Opt.Commands.push_back({SectionKind, {}, OutSec, {}}); expect(":"); expect("{"); @@ -553,6 +695,7 @@ setError("unknown command " + Tok); } } + readOutputSectionHeaders(); StringRef Tok = peek(); if (Tok.startswith("=")) { @@ -572,7 +715,7 @@ if (Expr.empty()) error("error in symbol assignment expression"); else - Opt.Commands.push_back({SymbolAssignmentKind, std::move(Expr), Name}); + Opt.Commands.push_back({SymbolAssignmentKind, std::move(Expr), Name, {}}); } std::vector ScriptParser::readSectionsCommandExpr() { @@ -586,6 +729,35 @@ return Expr; } +void ScriptParser::readOutputSectionHeaders() { + while (!Error && peek().startswith(":")) { + StringRef Tok = next(); + Tok = (Tok.size() == 1) ? next() : Tok.substr(1); + if (Tok.empty()) + setError("section header name is empty"); + else + Opt.Commands.back().Headers.push_back(Tok); + } +} + +void ScriptParser::readPhdrsType() { + static const char *typeNames[] = { + "PT_NULL", "PT_LOAD", "PT_DYNAMIC", "PT_INTERP", + "PT_NOTE", "PT_SHLIB", "PT_PHDR", "PT_TLS", + "PT_GNU_EH_FRAME", "PT_GNU_STACK", "PT_GNU_RELRO"}; + static unsigned typeCodes[] = { + PT_NULL, PT_LOAD, PT_DYNAMIC, PT_INTERP, PT_NOTE, PT_SHLIB, + PT_PHDR, PT_TLS, PT_GNU_EH_FRAME, PT_GNU_STACK, PT_GNU_RELRO}; + + PhdrsCommand &Cmd = Opt.PhdrsCommands.back(); + StringRef Tok = next(); + auto It = std::find(std::begin(typeNames), std::end(typeNames), Tok); + if (It != std::end(typeNames)) + Cmd.HeaderType = typeCodes[std::distance(std::begin(typeNames), It)]; + else + setError("invalid header type"); +} + static bool isUnderSysroot(StringRef Path) { if (Config->Sysroot == "") return false; Index: ELF/Writer.h =================================================================== --- ELF/Writer.h +++ ELF/Writer.h @@ -10,6 +10,7 @@ #ifndef LLD_ELF_WRITER_H #define LLD_ELF_WRITER_H +#include #include namespace llvm { @@ -18,13 +19,30 @@ namespace lld { namespace elf { +template class OutputSectionBase; template class InputSectionBase; template class ObjectFile; template class SymbolTable; - template void writeResult(SymbolTable *Symtab); - template void markLive(); +template bool needsInterpSection(); +template bool isOutputDynamic(); +template bool isRelroSection(OutputSectionBase *Sec); +template bool needsPtLoad(OutputSectionBase *Sec); +uint32_t toPhdrFlags(uint64_t Flags); + +// This describes a program header entry. +// Each contains type, access flags and range of output sections that will be +// placed in it. +template +struct Phdr { + Phdr(unsigned Type, unsigned Flags); + void AddSec(OutputSectionBase *Sec); + + typename ELFT::Phdr H = {}; + OutputSectionBase *First = nullptr; + OutputSectionBase *Last = nullptr; +}; template llvm::StringRef getOutputSectionName(InputSectionBase *S); Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -44,18 +44,7 @@ void run(); private: - // This describes a program header entry. - // Each contains type, access flags and range of output sections that will be - // placed in it. - struct Phdr { - Phdr(unsigned Type, unsigned Flags) { - H.p_type = Type; - H.p_flags = Flags; - } - Elf_Phdr H = {}; - OutputSectionBase *First = nullptr; - OutputSectionBase *Last = nullptr; - }; + typedef Phdr Phdr; void copyLocalSymbols(); void addReservedSymbols(); @@ -74,12 +63,6 @@ void writeHeader(); void writeSections(); void writeBuildId(); - bool needsInterpSection() const { - return !Symtab.getSharedFiles().empty() && !Config->DynamicLinker.empty(); - } - bool isOutputDynamic() const { - return !Symtab.getSharedFiles().empty() || Config->Pic; - } void addCommonSymbols(std::vector &Syms); @@ -240,7 +223,10 @@ if (Config->Relocatable) { assignFileOffsets(); } else { - createPhdrs(); + if (Script::X->hasPhdrsCommands()) + Phdrs = Script::X->createPhdrs(OutputSections); + else + createPhdrs(); fixHeaders(); if (ScriptConfig->DoLayout) { Script::X->assignAddresses(OutputSections); @@ -375,7 +361,7 @@ .Default(1); } -template static bool isRelroSection(OutputSectionBase *Sec) { +template bool elf::isRelroSection(OutputSectionBase *Sec) { if (!Config->ZRelro) return false; typename ELFT::uint Flags = Sec->getFlags(); @@ -471,6 +457,40 @@ return false; } +uint32_t elf::toPhdrFlags(uint64_t Flags) { + uint32_t Ret = PF_R; + if (Flags & SHF_WRITE) + Ret |= PF_W; + if (Flags & SHF_EXECINSTR) + Ret |= PF_X; + return Ret; +} + +// Various helper functions +template bool elf::needsInterpSection() { + return !Symtab::X->getSharedFiles().empty() && + !Config->DynamicLinker.empty(); +} + +template bool elf::isOutputDynamic() { + return !Symtab::X->getSharedFiles().empty() || Config->Pic; +} + +// Program header entry +template +Phdr::Phdr(unsigned Type, unsigned Flags) { + H.p_type = Type; + H.p_flags = Flags; +} + +template +void Phdr::AddSec(OutputSectionBase *Sec) { + Last = Sec; + if (!First) + First = Sec; + H.p_align = std::max(H.p_align, Sec->getAlignment()); +} + // Until this function is called, common symbols do not belong to any section. // This function adds them to end of BSS section. template @@ -514,7 +534,7 @@ // need these symbols, since IRELATIVE relocs are resolved through GOT // and PLT. For details, see http://www.airs.com/blog/archives/403. template void Writer::addRelIpltSymbols() { - if (isOutputDynamic() || !Out::RelaPlt) + if (isOutputDynamic() || !Out::RelaPlt) return; StringRef S = Config->Rela ? "__rela_iplt_start" : "__rel_iplt_start"; addOptionalSynthetic(Symtab, S, Out::RelaPlt, 0); @@ -568,7 +588,7 @@ // static linking the linker is required to optimize away any references to // __tls_get_addr, so it's not defined anywhere. Create a hidden definition // to avoid the undefined symbol error. - if (!isOutputDynamic()) + if (!isOutputDynamic()) Symtab.addIgnored("__tls_get_addr"); auto Define = [this](StringRef S, DefinedRegular *&Sym1, @@ -606,7 +626,7 @@ template void Writer::createSections() { // Add .interp first because some loaders want to see that section // on the first page of the executable file when loaded into memory. - if (needsInterpSection()) + if (needsInterpSection()) OutputSections.push_back(Out::Interp); // A core file does not usually contain unmodified segments except @@ -668,7 +688,7 @@ // It should be okay as no one seems to care about the type. // Even the author of gold doesn't remember why gold behaves that way. // https://sourceware.org/ml/binutils/2002-03/msg00360.html - if (isOutputDynamic()) + if (isOutputDynamic()) Symtab.addSynthetic("_DYNAMIC", Out::Dynamic, 0); // Define __rel[a]_iplt_{start,end} symbols if needed. @@ -722,7 +742,7 @@ if (Out::SymTab) Out::SymTab->addSymbol(Body); - if (isOutputDynamic() && S->includeInDynsym()) { + if (isOutputDynamic() && S->includeInDynsym()) { Out::DynSymTab->addSymbol(Body); if (auto *SS = dyn_cast>(Body)) if (SS->File->isNeeded()) @@ -751,7 +771,7 @@ // Finalizers fix each section's size. // .dynsym is finalized early since that may fill up .gnu.hash. - if (isOutputDynamic()) + if (isOutputDynamic()) Out::DynSymTab->finalize(); // Fill other section headers. The dynamic table is finalized @@ -763,7 +783,7 @@ if (Sec != Out::DynStrTab && Sec != Out::Dynamic) Sec->finalize(); - if (isOutputDynamic()) + if (isOutputDynamic()) Out::Dynamic->finalize(); // Now that all output offsets are fixed. Finalize mergeable sections @@ -798,7 +818,7 @@ Add(Out::SymTab); Add(Out::ShStrTab); Add(Out::StrTab); - if (isOutputDynamic()) { + if (isOutputDynamic()) { Add(Out::DynSymTab); bool HasVerNeed = Out::VerNeed->getNeedNum() != 0; @@ -821,7 +841,7 @@ // Even during static linking it can contain R_[*]_IRELATIVE relocations. if (Out::RelaPlt && Out::RelaPlt->hasRelocs()) { Add(Out::RelaPlt); - Out::RelaPlt->Static = !isOutputDynamic(); + Out::RelaPlt->Static = !isOutputDynamic(); } if (needsGot()) @@ -881,7 +901,7 @@ Symtab.addSynthetic(Stop, Sec, DefinedSynthetic::SectionEnd); } -template static bool needsPtLoad(OutputSectionBase *Sec) { +template bool elf::needsPtLoad(OutputSectionBase *Sec) { if (!(Sec->getFlags() & SHF_ALLOC)) return false; @@ -893,15 +913,6 @@ return true; } -static uint32_t toPhdrFlags(uint64_t Flags) { - uint32_t Ret = PF_R; - if (Flags & SHF_WRITE) - Ret |= PF_W; - if (Flags & SHF_EXECINSTR) - Ret |= PF_X; - return Ret; -} - // Decide which program headers to create and which sections to include in each // one. template void Writer::createPhdrs() { @@ -909,28 +920,21 @@ return &*Phdrs.emplace(Phdrs.end(), Type, Flags); }; - auto AddSec = [](Phdr &Hdr, OutputSectionBase *Sec) { - Hdr.Last = Sec; - if (!Hdr.First) - Hdr.First = Sec; - Hdr.H.p_align = std::max(Hdr.H.p_align, Sec->getAlignment()); - }; - // The first phdr entry is PT_PHDR which describes the program header itself. Phdr &Hdr = *AddHdr(PT_PHDR, PF_R); - AddSec(Hdr, Out::ProgramHeaders); + Hdr.AddSec(Out::ProgramHeaders); // PT_INTERP must be the second entry if exists. - if (needsInterpSection()) { + if (needsInterpSection()) { Phdr &Hdr = *AddHdr(PT_INTERP, toPhdrFlags(Out::Interp->getFlags())); - AddSec(Hdr, Out::Interp); + Hdr.AddSec(Out::Interp); } // Add the first PT_LOAD segment for regular output sections. uintX_t Flags = PF_R; Phdr *Load = AddHdr(PT_LOAD, Flags); - AddSec(*Load, Out::ElfHeader); - AddSec(*Load, Out::ProgramHeaders); + Load->AddSec(Out::ElfHeader); + Load->AddSec(Out::ProgramHeaders); Phdr TlsHdr(PT_TLS, PF_R); Phdr RelRo(PT_GNU_RELRO, PF_R); @@ -943,7 +947,7 @@ // and put all TLS sections inside for futher use when // assign addresses. if (Sec->getFlags() & SHF_TLS) - AddSec(TlsHdr, Sec); + TlsHdr.AddSec(Sec); if (!needsPtLoad(Sec)) continue; @@ -955,12 +959,12 @@ Flags = NewFlags; } - AddSec(*Load, Sec); + Load->AddSec(Sec); if (isRelroSection(Sec)) - AddSec(RelRo, Sec); + RelRo.AddSec(Sec); if (Sec->getType() == SHT_NOTE) - AddSec(Note, Sec); + Note.AddSec(Sec); } // Add the TLS segment unless it's empty. @@ -968,9 +972,9 @@ Phdrs.push_back(std::move(TlsHdr)); // Add an entry for .dynamic. - if (isOutputDynamic()) { + if (isOutputDynamic()) { Phdr &H = *AddHdr(PT_DYNAMIC, toPhdrFlags(Out::Dynamic->getFlags())); - AddSec(H, Out::Dynamic); + H.AddSec(Out::Dynamic); } // PT_GNU_RELRO includes all sections that should be marked as @@ -982,7 +986,7 @@ if (!Out::EhFrame->empty() && Out::EhFrameHdr) { Phdr &Hdr = *AddHdr(PT_GNU_EH_FRAME, toPhdrFlags(Out::EhFrameHdr->getFlags())); - AddSec(Hdr, Out::EhFrameHdr); + Hdr.AddSec(Out::EhFrameHdr); } // PT_GNU_STACK is a special section to tell the loader to make the @@ -992,8 +996,6 @@ if (Note.First) Phdrs.push_back(std::move(Note)); - - Out::ProgramHeaders->setSize(sizeof(Elf_Phdr) * Phdrs.size()); } // The first section of each PT_LOAD and the first section after PT_GNU_RELRO @@ -1026,6 +1028,7 @@ Out::ElfHeader->setVA(BaseVA); uintX_t Off = Out::ElfHeader->getSize(); Out::ProgramHeaders->setVA(Off + BaseVA); + Out::ProgramHeaders->setSize(sizeof(Elf_Phdr) * Phdrs.size()); } // Assign VAs (addresses at run-time) to output sections. @@ -1281,3 +1284,28 @@ template void elf::writeResult(SymbolTable *Symtab); template void elf::writeResult(SymbolTable *Symtab); template void elf::writeResult(SymbolTable *Symtab); + +template struct elf::Phdr; +template struct elf::Phdr; +template struct elf::Phdr; +template struct elf::Phdr; + +template bool elf::needsInterpSection(); +template bool elf::needsInterpSection(); +template bool elf::needsInterpSection(); +template bool elf::needsInterpSection(); + +template bool elf::isOutputDynamic(); +template bool elf::isOutputDynamic(); +template bool elf::isOutputDynamic(); +template bool elf::isOutputDynamic(); + +template bool elf::isRelroSection(OutputSectionBase *); +template bool elf::isRelroSection(OutputSectionBase *); +template bool elf::isRelroSection(OutputSectionBase *); +template bool elf::isRelroSection(OutputSectionBase *); + +template bool elf::needsPtLoad(OutputSectionBase *); +template bool elf::needsPtLoad(OutputSectionBase *); +template bool elf::needsPtLoad(OutputSectionBase *); +template bool elf::needsPtLoad(OutputSectionBase *); Index: test/ELF/linkerscript-phdrs.s =================================================================== --- test/ELF/linkerscript-phdrs.s +++ test/ELF/linkerscript-phdrs.s @@ -0,0 +1,36 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t +# RUN: echo "PHDRS {all PT_LOAD FILEHDR PHDRS ;} \ +# RUN: SECTIONS { \ +# RUN: . = 0x10000200; \ +# RUN: .text : {*(.text.*)} :all \ +# RUN: .foo : {*(.foo.*)} :all \ +# RUN: .data : {*(.data.*)} :all}" > %t.script + +# RUN: ld.lld -o %t1 --script %t.script %t +# RUN: llvm-readobj -program-headers %t1 | FileCheck %s +# CHECK: ProgramHeaders [ +# CHECK-NEXT: ProgramHeader { +# CHECK-NEXT: Type: PT_LOAD (0x1) +# CHECK-NEXT: Offset: 0x0 +# CHECK-NEXT: VirtualAddress: 0x10000000 +# CHECK-NEXT: PhysicalAddress: 0x10000000 +# CHECK-NEXT: FileSize: 521 +# CHECK-NEXT: MemSize: 521 +# CHECK-NEXT: Flags [ (0x7) +# CHECK-NEXT: PF_R (0x4) +# CHECK-NEXT: PF_W (0x2) +# CHECK-NEXT: PF_X (0x1) +# CHECK-NEXT: ] + +.global _start +_start: + nop + +.section .foo.1,"a" +foo1: + .long 0 + +.section .foo.2,"aw" +foo2: + .long 0