Index: ELF/InputSection.h =================================================================== --- ELF/InputSection.h +++ ELF/InputSection.h @@ -28,6 +28,7 @@ struct SectionPiece; template class DefinedRegular; +template class EhFrameSection; template class MergeSyntheticSection; template class ObjectFile; template class OutputSection; @@ -244,6 +245,7 @@ // Splittable sections are handled as a sequence of data // rather than a single large blob of data. std::vector Pieces; + EhFrameSection *EHSec = nullptr; }; // This corresponds to a non SHF_MERGE section of an input file. Index: ELF/InputSection.cpp =================================================================== --- ELF/InputSection.cpp +++ ELF/InputSection.cpp @@ -121,6 +121,8 @@ OutputSectionBase *InputSectionBase::getOutputSection() const { if (auto *MS = dyn_cast>(this)) return MS->MergeSec ? MS->MergeSec->OutSec : nullptr; + if (auto *EH = dyn_cast>(this)) + return EH->EHSec->OutSec; return OutSec; } @@ -499,7 +501,7 @@ uint8_t *BufLoc = Buf + Offset; uint32_t Type = Rel.Type; - uintX_t AddrLoc = OutSec->Addr + Offset; + uintX_t AddrLoc = getOutputSection()->Addr + Offset; RelExpr Expr = Rel.Expr; uint64_t TargetVA = SignExtend64( getRelocTargetVA(Type, Rel.Addend, AddrLoc, *Rel.Sym, Expr)); Index: ELF/OutputSections.h =================================================================== --- ELF/OutputSections.h +++ ELF/OutputSections.h @@ -129,52 +129,6 @@ uint8_t *Loc = nullptr; }; -struct CieRecord { - EhSectionPiece *Piece = nullptr; - std::vector FdePieces; -}; - -// Output section for .eh_frame. -template class EhOutputSection final : public OutputSectionBase { - typedef typename ELFT::uint uintX_t; - typedef typename ELFT::Shdr Elf_Shdr; - typedef typename ELFT::Rel Elf_Rel; - typedef typename ELFT::Rela Elf_Rela; - -public: - EhOutputSection(); - void writeTo(uint8_t *Buf) override; - void finalize() override; - bool empty() const { return Sections.empty(); } - void forEachInputSection(std::function F) override; - - void addSection(InputSectionBase *S) override; - Kind getKind() const override { return EHFrame; } - static bool classof(const OutputSectionBase *B) { - return B->getKind() == EHFrame; - } - - size_t NumFdes = 0; - -private: - template - void addSectionAux(EhInputSection *S, llvm::ArrayRef Rels); - - template - CieRecord *addCie(EhSectionPiece &Piece, ArrayRef Rels); - - template - bool isFdeLive(EhSectionPiece &Piece, ArrayRef Rels); - - uintX_t getFdePc(uint8_t *Buf, size_t Off, uint8_t Enc); - - std::vector *> Sections; - std::vector Cies; - - // CIE records are uniquified by their contents and personality functions. - llvm::DenseMap, SymbolBody *>, CieRecord> CieMap; -}; - // All output sections that are hadnled by the linker specially are // globally accessible. Writer initializes them, so don't use them // until Writer is initialized. @@ -183,7 +137,6 @@ typedef typename ELFT::Phdr Elf_Phdr; static uint8_t First; - static EhOutputSection *EhFrame; static OutputSection *Bss; static OutputSection *BssRelRo; static OutputSectionBase *Opd; @@ -241,7 +194,6 @@ } template uint8_t Out::First; -template EhOutputSection *Out::EhFrame; template OutputSection *Out::Bss; template OutputSection *Out::BssRelRo; template OutputSectionBase *Out::Opd; Index: ELF/OutputSections.cpp =================================================================== --- ELF/OutputSections.cpp +++ ELF/OutputSections.cpp @@ -9,7 +9,6 @@ #include "OutputSections.h" #include "Config.h" -#include "EhFrame.h" #include "LinkerScript.h" #include "Memory.h" #include "Strings.h" @@ -270,217 +269,6 @@ } template -EhOutputSection::EhOutputSection() - : OutputSectionBase(".eh_frame", SHT_PROGBITS, SHF_ALLOC) {} - -template -void EhOutputSection::forEachInputSection( - std::function F) { - for (EhInputSection *S : Sections) - F(S); -} - -// Search for an existing CIE record or create a new one. -// CIE records from input object files are uniquified by their contents -// and where their relocations point to. -template -template -CieRecord *EhOutputSection::addCie(EhSectionPiece &Piece, - ArrayRef Rels) { - auto *Sec = cast>(Piece.ID); - const endianness E = ELFT::TargetEndianness; - if (read32(Piece.data().data() + 4) != 0) - fatal(toString(Sec) + ": CIE expected at beginning of .eh_frame"); - - SymbolBody *Personality = nullptr; - unsigned FirstRelI = Piece.FirstRelocation; - if (FirstRelI != (unsigned)-1) - Personality = - &Sec->template getFile()->getRelocTargetSym(Rels[FirstRelI]); - - // Search for an existing CIE by CIE contents/relocation target pair. - CieRecord *Cie = &CieMap[{Piece.data(), Personality}]; - - // If not found, create a new one. - if (Cie->Piece == nullptr) { - Cie->Piece = &Piece; - Cies.push_back(Cie); - } - return Cie; -} - -// There is one FDE per function. Returns true if a given FDE -// points to a live function. -template -template -bool EhOutputSection::isFdeLive(EhSectionPiece &Piece, - ArrayRef Rels) { - auto *Sec = cast>(Piece.ID); - unsigned FirstRelI = Piece.FirstRelocation; - if (FirstRelI == (unsigned)-1) - return false; - const RelTy &Rel = Rels[FirstRelI]; - SymbolBody &B = Sec->template getFile()->getRelocTargetSym(Rel); - auto *D = dyn_cast>(&B); - if (!D || !D->Section) - return false; - InputSectionBase *Target = D->Section->Repl; - return Target && Target->Live; -} - -// .eh_frame is a sequence of CIE or FDE records. In general, there -// is one CIE record per input object file which is followed by -// a list of FDEs. This function searches an existing CIE or create a new -// one and associates FDEs to the CIE. -template -template -void EhOutputSection::addSectionAux(EhInputSection *Sec, - ArrayRef Rels) { - const endianness E = ELFT::TargetEndianness; - - DenseMap OffsetToCie; - for (EhSectionPiece &Piece : Sec->Pieces) { - // The empty record is the end marker. - if (Piece.size() == 4) - return; - - size_t Offset = Piece.InputOff; - uint32_t ID = read32(Piece.data().data() + 4); - if (ID == 0) { - OffsetToCie[Offset] = addCie(Piece, Rels); - continue; - } - - uint32_t CieOffset = Offset + 4 - ID; - CieRecord *Cie = OffsetToCie[CieOffset]; - if (!Cie) - fatal(toString(Sec) + ": invalid CIE reference"); - - if (!isFdeLive(Piece, Rels)) - continue; - Cie->FdePieces.push_back(&Piece); - NumFdes++; - } -} - -template -void EhOutputSection::addSection(InputSectionBase *C) { - auto *Sec = cast>(C); - Sec->OutSec = this; - this->updateAlignment(Sec->Alignment); - Sections.push_back(Sec); - - // .eh_frame is a sequence of CIE or FDE records. This function - // splits it into pieces so that we can call - // SplitInputSection::getSectionPiece on the section. - Sec->split(); - if (Sec->Pieces.empty()) - return; - - if (Sec->NumRelocations) { - if (Sec->AreRelocsRela) - addSectionAux(Sec, Sec->template relas()); - else - addSectionAux(Sec, Sec->template rels()); - return; - } - addSectionAux(Sec, makeArrayRef(nullptr, nullptr)); -} - -template -static void writeCieFde(uint8_t *Buf, ArrayRef D) { - memcpy(Buf, D.data(), D.size()); - - // Fix the size field. -4 since size does not include the size field itself. - const endianness E = ELFT::TargetEndianness; - write32(Buf, alignTo(D.size(), sizeof(typename ELFT::uint)) - 4); -} - -template void EhOutputSection::finalize() { - if (this->Size) - return; // Already finalized. - - size_t Off = 0; - for (CieRecord *Cie : Cies) { - Cie->Piece->OutputOff = Off; - Off += alignTo(Cie->Piece->size(), sizeof(uintX_t)); - - for (EhSectionPiece *Fde : Cie->FdePieces) { - Fde->OutputOff = Off; - Off += alignTo(Fde->size(), sizeof(uintX_t)); - } - } - this->Size = Off; -} - -template static uint64_t readFdeAddr(uint8_t *Buf, int Size) { - const endianness E = ELFT::TargetEndianness; - switch (Size) { - case DW_EH_PE_udata2: - return read16(Buf); - case DW_EH_PE_udata4: - return read32(Buf); - case DW_EH_PE_udata8: - return read64(Buf); - case DW_EH_PE_absptr: - if (ELFT::Is64Bits) - return read64(Buf); - return read32(Buf); - } - fatal("unknown FDE size encoding"); -} - -// Returns the VA to which a given FDE (on a mmap'ed buffer) is applied to. -// We need it to create .eh_frame_hdr section. -template -typename ELFT::uint EhOutputSection::getFdePc(uint8_t *Buf, size_t FdeOff, - uint8_t Enc) { - // The starting address to which this FDE applies is - // stored at FDE + 8 byte. - size_t Off = FdeOff + 8; - uint64_t Addr = readFdeAddr(Buf + Off, Enc & 0x7); - if ((Enc & 0x70) == DW_EH_PE_absptr) - return Addr; - if ((Enc & 0x70) == DW_EH_PE_pcrel) - return Addr + this->Addr + Off; - fatal("unknown FDE size relative encoding"); -} - -template void EhOutputSection::writeTo(uint8_t *Buf) { - const endianness E = ELFT::TargetEndianness; - for (CieRecord *Cie : Cies) { - size_t CieOffset = Cie->Piece->OutputOff; - writeCieFde(Buf + CieOffset, Cie->Piece->data()); - - for (EhSectionPiece *Fde : Cie->FdePieces) { - size_t Off = Fde->OutputOff; - writeCieFde(Buf + Off, Fde->data()); - - // FDE's second word should have the offset to an associated CIE. - // Write it. - write32(Buf + Off + 4, Off + 4 - CieOffset); - } - } - - for (EhInputSection *S : Sections) - S->template relocate(Buf, nullptr); - - // Construct .eh_frame_hdr. .eh_frame_hdr is a binary search table - // to get a FDE from an address to which FDE is applied. So here - // we obtain two addresses and pass them to EhFrameHdr object. - if (In::EhFrameHdr) { - for (CieRecord *Cie : Cies) { - uint8_t Enc = getFdeEncoding(Cie->Piece); - for (SectionPiece *Fde : Cie->FdePieces) { - uintX_t Pc = getFdePc(Buf, Fde->OutputOff, Enc); - uintX_t FdeVA = this->Addr + Fde->OutputOff; - In::EhFrameHdr->addFde(Pc, FdeVA); - } - } - } -} - -template static typename ELFT::uint getOutFlags(InputSectionBase *S) { return S->Flags & ~SHF_GROUP & ~SHF_COMPRESSED; } @@ -596,7 +384,7 @@ } else { uint32_t Type = IS->Type; if (IS->kind() == InputSectionBase::EHFrame) { - Out::EhFrame->addSection(IS); + In::EhFrame->addSection(IS); return; } Sec = make>(Key.Name, Type, Flags); @@ -639,11 +427,6 @@ template class OutputSection; template class OutputSection; -template class EhOutputSection; -template class EhOutputSection; -template class EhOutputSection; -template class EhOutputSection; - template class OutputSectionFactory; template class OutputSectionFactory; template class OutputSectionFactory; Index: ELF/Relocations.h =================================================================== --- ELF/Relocations.h +++ ELF/Relocations.h @@ -15,7 +15,6 @@ namespace lld { namespace elf { class SymbolBody; -class InputSectionData; class InputSection; class InputSectionBase; class OutputSectionBase; Index: ELF/SyntheticSections.h =================================================================== --- ELF/SyntheticSections.h +++ ELF/SyntheticSections.h @@ -25,6 +25,7 @@ #ifndef LLD_ELF_SYNTHETIC_SECTION_H #define LLD_ELF_SYNTHETIC_SECTION_H +#include "EhFrame.h" #include "GdbIndex.h" #include "InputSection.h" #include "llvm/ADT/MapVector.h" @@ -59,6 +60,55 @@ } }; +struct CieRecord { + EhSectionPiece *Piece = nullptr; + std::vector FdePieces; +}; + +// Section for .eh_frame. +template +class EhFrameSection final : public SyntheticSection { + typedef typename ELFT::uint uintX_t; + typedef typename ELFT::Shdr Elf_Shdr; + typedef typename ELFT::Rel Elf_Rel; + typedef typename ELFT::Rela Elf_Rela; + + void updateAlignment(uint64_t Val) { + if (Val > this->Alignment) + this->Alignment = Val; + } + +public: + EhFrameSection(); + void writeTo(uint8_t *Buf) override; + void finalize() override; + bool empty() const override { return Sections.empty(); } + size_t getSize() const override { return Size; } + + void addSection(InputSectionBase *S); + + size_t NumFdes = 0; + +private: + uint64_t Size = 0; + template + void addSectionAux(EhInputSection *S, llvm::ArrayRef Rels); + + template + CieRecord *addCie(EhSectionPiece &Piece, ArrayRef Rels); + + template + bool isFdeLive(EhSectionPiece &Piece, ArrayRef Rels); + + uintX_t getFdePc(uint8_t *Buf, size_t Off, uint8_t Enc); + + std::vector *> Sections; + std::vector Cies; + + // CIE records are uniquified by their contents and personality functions. + llvm::DenseMap, SymbolBody *>, CieRecord> CieMap; +}; + template class GotSection final : public SyntheticSection { typedef typename ELFT::uint uintX_t; @@ -762,6 +812,7 @@ static GnuHashTableSection *GnuHashTab; static GdbIndexSection *GdbIndex; static GotSection *Got; + static EhFrameSection *EhFrame; static MipsGotSection *MipsGot; static GotPltSection *GotPlt; static IgotPltSection *IgotPlt; @@ -791,6 +842,7 @@ template GdbIndexSection *In::GdbIndex; template GnuHashTableSection *In::GnuHashTab; template GotSection *In::Got; +template EhFrameSection *In::EhFrame; template MipsGotSection *In::MipsGot; template GotPltSection *In::GotPlt; template IgotPltSection *In::IgotPlt; Index: ELF/SyntheticSections.cpp =================================================================== --- ELF/SyntheticSections.cpp +++ ELF/SyntheticSections.cpp @@ -401,6 +401,210 @@ } template +EhFrameSection::EhFrameSection() + : SyntheticSection(SHF_ALLOC, SHT_PROGBITS, 1, ".eh_frame") {} + +// Search for an existing CIE record or create a new one. +// CIE records from input object files are uniquified by their contents +// and where their relocations point to. +template +template +CieRecord *EhFrameSection::addCie(EhSectionPiece &Piece, + ArrayRef Rels) { + auto *Sec = cast>(Piece.ID); + const endianness E = ELFT::TargetEndianness; + if (read32(Piece.data().data() + 4) != 0) + fatal(toString(Sec) + ": CIE expected at beginning of .eh_frame"); + + SymbolBody *Personality = nullptr; + unsigned FirstRelI = Piece.FirstRelocation; + if (FirstRelI != (unsigned)-1) + Personality = + &Sec->template getFile()->getRelocTargetSym(Rels[FirstRelI]); + + // Search for an existing CIE by CIE contents/relocation target pair. + CieRecord *Cie = &CieMap[{Piece.data(), Personality}]; + + // If not found, create a new one. + if (Cie->Piece == nullptr) { + Cie->Piece = &Piece; + Cies.push_back(Cie); + } + return Cie; +} + +// There is one FDE per function. Returns true if a given FDE +// points to a live function. +template +template +bool EhFrameSection::isFdeLive(EhSectionPiece &Piece, + ArrayRef Rels) { + auto *Sec = cast>(Piece.ID); + unsigned FirstRelI = Piece.FirstRelocation; + if (FirstRelI == (unsigned)-1) + return false; + const RelTy &Rel = Rels[FirstRelI]; + SymbolBody &B = Sec->template getFile()->getRelocTargetSym(Rel); + auto *D = dyn_cast>(&B); + if (!D || !D->Section) + return false; + InputSectionBase *Target = D->Section->Repl; + return Target && Target->Live; +} + +// .eh_frame is a sequence of CIE or FDE records. In general, there +// is one CIE record per input object file which is followed by +// a list of FDEs. This function searches an existing CIE or create a new +// one and associates FDEs to the CIE. +template +template +void EhFrameSection::addSectionAux(EhInputSection *Sec, + ArrayRef Rels) { + const endianness E = ELFT::TargetEndianness; + + DenseMap OffsetToCie; + for (EhSectionPiece &Piece : Sec->Pieces) { + // The empty record is the end marker. + if (Piece.size() == 4) + return; + + size_t Offset = Piece.InputOff; + uint32_t ID = read32(Piece.data().data() + 4); + if (ID == 0) { + OffsetToCie[Offset] = addCie(Piece, Rels); + continue; + } + + uint32_t CieOffset = Offset + 4 - ID; + CieRecord *Cie = OffsetToCie[CieOffset]; + if (!Cie) + fatal(toString(Sec) + ": invalid CIE reference"); + + if (!isFdeLive(Piece, Rels)) + continue; + Cie->FdePieces.push_back(&Piece); + NumFdes++; + } +} + +template +void EhFrameSection::addSection(InputSectionBase *C) { + auto *Sec = cast>(C); + Sec->EHSec = this; + updateAlignment(Sec->Alignment); + Sections.push_back(Sec); + + // .eh_frame is a sequence of CIE or FDE records. This function + // splits it into pieces so that we can call + // SplitInputSection::getSectionPiece on the section. + Sec->split(); + if (Sec->Pieces.empty()) + return; + + if (Sec->NumRelocations) { + if (Sec->AreRelocsRela) + addSectionAux(Sec, Sec->template relas()); + else + addSectionAux(Sec, Sec->template rels()); + return; + } + addSectionAux(Sec, makeArrayRef(nullptr, nullptr)); +} + +template +static void writeCieFde(uint8_t *Buf, ArrayRef D) { + memcpy(Buf, D.data(), D.size()); + + // Fix the size field. -4 since size does not include the size field itself. + const endianness E = ELFT::TargetEndianness; + write32(Buf, alignTo(D.size(), sizeof(typename ELFT::uint)) - 4); +} + +template void EhFrameSection::finalize() { + if (this->Size) + return; // Already finalized. + + size_t Off = 0; + for (CieRecord *Cie : Cies) { + Cie->Piece->OutputOff = Off; + Off += alignTo(Cie->Piece->size(), sizeof(uintX_t)); + + for (EhSectionPiece *Fde : Cie->FdePieces) { + Fde->OutputOff = Off; + Off += alignTo(Fde->size(), sizeof(uintX_t)); + } + } + this->Size = Off; +} + +template static uint64_t readFdeAddr(uint8_t *Buf, int Size) { + const endianness E = ELFT::TargetEndianness; + switch (Size) { + case DW_EH_PE_udata2: + return read16(Buf); + case DW_EH_PE_udata4: + return read32(Buf); + case DW_EH_PE_udata8: + return read64(Buf); + case DW_EH_PE_absptr: + if (ELFT::Is64Bits) + return read64(Buf); + return read32(Buf); + } + fatal("unknown FDE size encoding"); +} + +// Returns the VA to which a given FDE (on a mmap'ed buffer) is applied to. +// We need it to create .eh_frame_hdr section. +template +typename ELFT::uint EhFrameSection::getFdePc(uint8_t *Buf, size_t FdeOff, + uint8_t Enc) { + // The starting address to which this FDE applies is + // stored at FDE + 8 byte. + size_t Off = FdeOff + 8; + uint64_t Addr = readFdeAddr(Buf + Off, Enc & 0x7); + if ((Enc & 0x70) == DW_EH_PE_absptr) + return Addr; + if ((Enc & 0x70) == DW_EH_PE_pcrel) + return Addr + this->OutSec->Addr + Off; + fatal("unknown FDE size relative encoding"); +} + +template void EhFrameSection::writeTo(uint8_t *Buf) { + const endianness E = ELFT::TargetEndianness; + for (CieRecord *Cie : Cies) { + size_t CieOffset = Cie->Piece->OutputOff; + writeCieFde(Buf + CieOffset, Cie->Piece->data()); + + for (EhSectionPiece *Fde : Cie->FdePieces) { + size_t Off = Fde->OutputOff; + writeCieFde(Buf + Off, Fde->data()); + + // FDE's second word should have the offset to an associated CIE. + // Write it. + write32(Buf + Off + 4, Off + 4 - CieOffset); + } + } + + for (EhInputSection *S : Sections) + S->template relocate(Buf, nullptr); + + // Construct .eh_frame_hdr. .eh_frame_hdr is a binary search table + // to get a FDE from an address to which FDE is applied. So here + // we obtain two addresses and pass them to EhFrameHdr object. + if (In::EhFrameHdr) { + for (CieRecord *Cie : Cies) { + uint8_t Enc = getFdeEncoding(Cie->Piece); + for (SectionPiece *Fde : Cie->FdePieces) { + uintX_t Pc = getFdePc(Buf, Fde->OutputOff, Enc); + uintX_t FdeVA = this->OutSec->Addr + Fde->OutputOff; + In::EhFrameHdr->addFde(Pc, FdeVA); + } + } + } +} + +template GotSection::GotSection() : SyntheticSection(SHF_ALLOC | SHF_WRITE, SHT_PROGBITS, Target->GotEntrySize, ".got") {} @@ -1675,7 +1879,7 @@ Buf[1] = DW_EH_PE_pcrel | DW_EH_PE_sdata4; Buf[2] = DW_EH_PE_udata4; Buf[3] = DW_EH_PE_datarel | DW_EH_PE_sdata4; - write32(Buf + 4, Out::EhFrame->Addr - this->getVA() - 4); + write32(Buf + 4, In::EhFrame->OutSec->Addr - this->getVA() - 4); write32(Buf + 8, Fdes.size()); Buf += 12; @@ -1689,7 +1893,7 @@ template size_t EhFrameHeader::getSize() const { // .eh_frame_hdr has a 12 bytes header followed by an array of FDEs. - return 12 + Out::EhFrame->NumFdes * 8; + return 12 + In::EhFrame->NumFdes * 8; } template @@ -1698,7 +1902,7 @@ } template bool EhFrameHeader::empty() const { - return Out::EhFrame->empty(); + return In::EhFrame->empty(); } template @@ -2158,3 +2362,8 @@ template class elf::ThunkSection; template class elf::ThunkSection; template class elf::ThunkSection; + +template class elf::EhFrameSection; +template class elf::EhFrameSection; +template class elf::EhFrameSection; +template class elf::EhFrameSection; Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -318,7 +318,6 @@ SHF_ALLOC | SHF_WRITE); In::DynStrTab = make>(".dynstr", true); In::Dynamic = make>(); - Out::EhFrame = make>(); In::RelaDyn = make>( Config->Rela ? ".rela.dyn" : ".rel.dyn", Config->ZCombreloc); In::ShStrTab = make>(".shstrtab", false); @@ -444,6 +443,11 @@ Add(In::EhFrameHdr); } + if (!Config->Relocatable) { + In::EhFrame = make>(); + Add(In::EhFrame); + } + if (In::SymTab) Add(In::SymTab); Add(In::ShStrTab); @@ -1088,10 +1092,8 @@ // Define __rel[a]_iplt_{start,end} symbols if needed. addRelIpltSymbols(); - if (!Out::EhFrame->empty()) { - OutputSections.push_back(Out::EhFrame); - Out::EhFrame->finalize(); - } + // This has to be before the relocation scan. + finalizeSynthetic({In::EhFrame}); // Scan relocations. This must be done after every symbol is declared so that // we can correctly decide if a dynamic relocation is needed. @@ -1323,7 +1325,7 @@ Ret.push_back(std::move(RelRo)); // PT_GNU_EH_FRAME is a special section pointing on .eh_frame_hdr. - if (!Out::EhFrame->empty() && In::EhFrameHdr) + if (!In::EhFrame->empty() && In::EhFrameHdr) AddHdr(PT_GNU_EH_FRAME, In::EhFrameHdr->OutSec->getPhdrFlags()) ->add(In::EhFrameHdr->OutSec); @@ -1827,7 +1829,7 @@ // The .eh_frame_hdr depends on .eh_frame section contents, therefore // it should be written after .eh_frame is written. - if (!Out::EhFrame->empty() && EhFrameHdr) + if (EhFrameHdr) EhFrameHdr->writeTo(Buf + EhFrameHdr->Offset); } Index: test/ELF/map-file.s =================================================================== --- test/ELF/map-file.s +++ test/ELF/map-file.s @@ -28,6 +28,7 @@ // CHECK: Address Size Align Out In File Symbol // CHECK-NEXT: 0000000000200158 0000000000000030 8 .eh_frame +// CHECK-NEXT: 0000000000200158 0000000000000030 8 .eh_frame // CHECK-NEXT: 0000000000201000 0000000000000015 4 .text // CHECK-NEXT: 0000000000201000 000000000000000e 4 .text // CHECK-NEXT: 0000000000201000 000000000000000e 4 {{.*}}{{/|\\}}map-file.s.tmp1.o