Index: lld/trunk/ELF/Config.h =================================================================== --- lld/trunk/ELF/Config.h +++ lld/trunk/ELF/Config.h @@ -57,6 +57,7 @@ bool DiscardAll; bool DiscardLocals; bool DiscardNone; + bool EhFrameHdr; bool EnableNewDtags; bool ExportDynamic; bool GcSections; Index: lld/trunk/ELF/Driver.cpp =================================================================== --- lld/trunk/ELF/Driver.cpp +++ lld/trunk/ELF/Driver.cpp @@ -189,6 +189,7 @@ Config->DiscardAll = Args.hasArg(OPT_discard_all); Config->DiscardLocals = Args.hasArg(OPT_discard_locals); Config->DiscardNone = Args.hasArg(OPT_discard_none); + Config->EhFrameHdr = Args.hasArg(OPT_eh_frame_hdr); Config->EnableNewDtags = !Args.hasArg(OPT_disable_new_dtags); Config->ExportDynamic = Args.hasArg(OPT_export_dynamic); Config->GcSections = Args.hasArg(OPT_gc_sections); Index: lld/trunk/ELF/Options.td =================================================================== --- lld/trunk/ELF/Options.td +++ lld/trunk/ELF/Options.td @@ -36,6 +36,9 @@ def dynamic_linker : Separate<["--", "-"], "dynamic-linker">, HelpText<"Which dynamic linker to use">; +def eh_frame_hdr : Flag<["--"], "eh-frame-hdr">, + HelpText<"Request creation of .eh_frame_hdr section and PT_GNU_EH_FRAME segment header">; + def enable_new_dtags : Flag<["--"], "enable-new-dtags">, HelpText<"Enable new dynamic tags">; @@ -154,7 +157,6 @@ // Options listed below are silently ignored for now for compatibility. def build_id : Flag<["--"], "build-id">; -def eh_frame_hdr : Flag<["--"], "eh-frame-hdr">; def fatal_warnings : Flag<["--"], "fatal-warnings">; def no_add_needed : Flag<["--"], "no-add-needed">; def no_fatal_warnings : Flag<["--"], "no-fatal-warnings">; Index: lld/trunk/ELF/OutputSections.h =================================================================== --- lld/trunk/ELF/OutputSections.h +++ lld/trunk/ELF/OutputSections.h @@ -287,6 +287,7 @@ template struct Cie : public EHRegion { Cie(EHInputSection *S, unsigned Index); std::vector> Fdes; + uint8_t FdeEncoding; }; template @@ -308,6 +309,7 @@ void addSection(InputSectionBase *S) override; private: + uint8_t getFdeEncoding(ArrayRef D); uintX_t readEntryLength(ArrayRef D); std::vector *> Sections; @@ -429,6 +431,42 @@ uint32_t GprMask = 0; }; +// --eh-frame-hdr option tells linker to construct a header for all the +// .eh_frame sections. This header is placed to a section named .eh_frame_hdr +// and also to a PT_GNU_EH_FRAME segment. +// At runtime the unwinder then can find all the PT_GNU_EH_FRAME segments by +// calling dl_iterate_phdr. +// This section contains a lookup table for quick binary search of FDEs. +// Detailed info about internals can be found in Ian Lance Taylor's blog: +// http://www.airs.com/blog/archives/460 (".eh_frame") +// http://www.airs.com/blog/archives/462 (".eh_frame_hdr") +template +class EhFrameHeader final : public OutputSectionBase { + typedef typename llvm::object::ELFFile::uintX_t uintX_t; + +public: + EhFrameHeader(); + void writeTo(uint8_t *Buf) override; + + void addFde(uint8_t Enc, size_t Off, uint8_t *PCRel); + void assignEhFrame(EHOutputSection *Sec); + void reserveFde(); + + bool Live = false; + +private: + struct FdeData { + uint8_t Enc; + size_t Off; + uint8_t *PCRel; + }; + + uintX_t getFdePc(uintX_t EhVA, const FdeData &F); + + EHOutputSection *Sec = nullptr; + std::vector FdeList; +}; + inline uint64_t align(uint64_t Value, uint64_t Align) { return llvm::RoundUpToAlignment(Value, Align); } @@ -440,6 +478,7 @@ typedef typename llvm::object::ELFFile::uintX_t uintX_t; typedef typename llvm::object::ELFFile::Elf_Phdr Elf_Phdr; static DynamicSection *Dynamic; + static EhFrameHeader *EhFrameHdr; static GnuHashTableSection *GnuHashTab; static GotPltSection *GotPlt; static GotSection *Got; @@ -461,6 +500,7 @@ }; template DynamicSection *Out::Dynamic; +template EhFrameHeader *Out::EhFrameHdr; template GnuHashTableSection *Out::GnuHashTab; template GotPltSection *Out::GotPlt; template GotSection *Out::Got; Index: lld/trunk/ELF/OutputSections.cpp =================================================================== --- lld/trunk/ELF/OutputSections.cpp +++ lld/trunk/ELF/OutputSections.cpp @@ -11,7 +11,9 @@ #include "Config.h" #include "SymbolTable.h" #include "Target.h" +#include "llvm/Support/Dwarf.h" #include "llvm/Support/MathExtras.h" +#include using namespace llvm; using namespace llvm::object; @@ -756,6 +758,99 @@ } template +EhFrameHeader::EhFrameHeader() + : OutputSectionBase(".eh_frame_hdr", llvm::ELF::SHT_PROGBITS, + SHF_ALLOC) { + // It's a 4 bytes of header + pointer to the contents of the .eh_frame section + // + the number of FDE pointers in the table. + this->Header.sh_size = 12; +} + +// We have to get PC values of FDEs. They depend on relocations +// which are target specific, so we run this code after performing +// all relocations. We read the values from ouput buffer according to the +// encoding given for FDEs. Return value is an offset to the initial PC value +// for the FDE. +template +typename EhFrameHeader::uintX_t +EhFrameHeader::getFdePc(uintX_t EhVA, const FdeData &F) { + const endianness E = ELFT::TargetEndianness; + assert((F.Enc & 0xF0) != dwarf::DW_EH_PE_datarel); + + uintX_t FdeOff = EhVA + F.Off + 8; + switch (F.Enc & 0xF) { + case dwarf::DW_EH_PE_udata2: + case dwarf::DW_EH_PE_sdata2: + return FdeOff + read16(F.PCRel); + case dwarf::DW_EH_PE_udata4: + case dwarf::DW_EH_PE_sdata4: + return FdeOff + read32(F.PCRel); + case dwarf::DW_EH_PE_udata8: + case dwarf::DW_EH_PE_sdata8: + return FdeOff + read64(F.PCRel); + case dwarf::DW_EH_PE_absptr: + if (sizeof(uintX_t) == 8) + return FdeOff + read64(F.PCRel); + return FdeOff + read32(F.PCRel); + } + error("unknown FDE size encoding"); +} + +template void EhFrameHeader::writeTo(uint8_t *Buf) { + const endianness E = ELFT::TargetEndianness; + + const uint8_t Header[] = {1, dwarf::DW_EH_PE_pcrel | dwarf::DW_EH_PE_sdata4, + dwarf::DW_EH_PE_udata4, + dwarf::DW_EH_PE_datarel | dwarf::DW_EH_PE_sdata4}; + memcpy(Buf, Header, sizeof(Header)); + + uintX_t EhVA = Sec->getVA(); + uintX_t VA = this->getVA(); + uintX_t EhOff = EhVA - VA - 4; + write32(Buf + 4, EhOff); + write32(Buf + 8, this->FdeList.size()); + Buf += 12; + + // InitialPC -> Offset in .eh_frame, sorted by InitialPC. + std::map PcToOffset; + for (const FdeData &F : FdeList) + PcToOffset[getFdePc(EhVA, F)] = F.Off; + + for (auto &I : PcToOffset) { + // The first four bytes are an offset to the initial PC value for the FDE. + write32(Buf, I.first - VA); + // The last four bytes are an offset to the FDE data itself. + write32(Buf + 4, EhVA + I.second - VA); + Buf += 8; + } +} + +template +void EhFrameHeader::assignEhFrame(EHOutputSection *Sec) { + if (this->Sec && this->Sec != Sec) { + warning("multiple .eh_frame sections not supported for .eh_frame_hdr"); + Live = false; + return; + } + Live = Config->EhFrameHdr; + this->Sec = Sec; +} + +template +void EhFrameHeader::addFde(uint8_t Enc, size_t Off, uint8_t *PCRel) { + if (Live && (Enc & 0xF0) == dwarf::DW_EH_PE_datarel) + error("DW_EH_PE_datarel encoding unsupported for FDEs by .eh_frame_hdr"); + FdeList.push_back(FdeData{Enc, Off, PCRel}); +} + +template void EhFrameHeader::reserveFde() { + // Each FDE entry is 8 bytes long: + // The first four bytes are an offset to the initial PC value for the FDE. The + // last four byte are an offset to the FDE data itself. + this->Header.sh_size += 8; +} + +template OutputSection::OutputSection(StringRef Name, uint32_t Type, uintX_t Flags) : OutputSectionBase(Name, Type, Flags) {} @@ -908,7 +1003,9 @@ template EHOutputSection::EHOutputSection(StringRef Name, uint32_t Type, uintX_t Flags) - : OutputSectionBase(Name, Type, Flags) {} + : OutputSectionBase(Name, Type, Flags) { + Out::EhFrameHdr->assignEhFrame(this); +} template EHRegion::EHRegion(EHInputSection *S, unsigned Index) @@ -927,6 +1024,107 @@ Cie::Cie(EHInputSection *S, unsigned Index) : EHRegion(S, Index) {} +// Read a byte and advance D by one byte. +static uint8_t readByte(ArrayRef &D) { + if (D.empty()) + error("corrupted or unsupported CIE information"); + uint8_t B = D.front(); + D = D.slice(1); + return B; +} + +static void skipLeb128(ArrayRef &D) { + while (!D.empty()) { + uint8_t Val = D.front(); + D = D.slice(1); + if ((Val & 0x80) == 0) + return; + } + error("corrupted or unsupported CIE information"); +} + +template static unsigned getSizeForEncoding(unsigned Enc) { + typedef typename ELFFile::uintX_t uintX_t; + switch (Enc & 0x0f) { + default: + error("unknown FDE encoding"); + case dwarf::DW_EH_PE_absptr: + case dwarf::DW_EH_PE_signed: + return sizeof(uintX_t); + case dwarf::DW_EH_PE_udata2: + case dwarf::DW_EH_PE_sdata2: + return 2; + case dwarf::DW_EH_PE_udata4: + case dwarf::DW_EH_PE_sdata4: + return 4; + case dwarf::DW_EH_PE_udata8: + case dwarf::DW_EH_PE_sdata8: + return 8; + } +} + +template +uint8_t EHOutputSection::getFdeEncoding(ArrayRef D) { + auto Check = [](bool C) { + if (!C) + error("corrupted or unsupported CIE information"); + }; + + Check(D.size() >= 8); + D = D.slice(8); + + uint8_t Version = readByte(D); + if (Version != 1 && Version != 3) + error("FDE version 1 or 3 expected, but got " + Twine((unsigned)Version)); + + auto AugEnd = std::find(D.begin() + 1, D.end(), '\0'); + Check(AugEnd != D.end()); + ArrayRef AugString(D.begin(), AugEnd - D.begin()); + D = D.slice(AugString.size() + 1); + + // Code alignment factor should always be 1 for .eh_frame. + if (readByte(D) != 1) + error("CIE code alignment must be 1"); + // Skip data alignment factor + skipLeb128(D); + + // Skip the return address register. In CIE version 1 this is a single + // byte. In CIE version 3 this is an unsigned LEB128. + if (Version == 1) + readByte(D); + else + skipLeb128(D); + + while (!AugString.empty()) { + switch (readByte(AugString)) { + case 'z': + skipLeb128(D); + break; + case 'R': + return readByte(D); + case 'P': { + uint8_t Enc = readByte(D); + if ((Enc & 0xf0) == dwarf::DW_EH_PE_aligned) + error("DW_EH_PE_aligned encoding for address of a personality routine " + "handler not supported"); + unsigned EncSize = getSizeForEncoding(Enc); + Check(D.size() >= EncSize); + D = D.slice(EncSize); + break; + } + case 'S': + case 'L': + // L: Language Specific Data Area (LSDA) encoding + // S: This CIE represents a stack frame for the invocation of a signal + // handler + break; + default: + error("unknown .eh_frame augmentation string value"); + } + } + return dwarf::DW_EH_PE_absptr; +} + template template void EHOutputSection::addSectionAux( @@ -964,6 +1162,8 @@ if (ID == 0) { // CIE Cie C(S, Index); + if (Config->EhFrameHdr) + C.FdeEncoding = getFdeEncoding(D); StringRef Personality; if (HasReloc) { @@ -989,6 +1189,7 @@ if (I == OffsetToIndex.end()) error("Invalid CIE reference"); Cies[I->second].Fdes.push_back(EHRegion(S, Index)); + Out::EhFrameHdr->reserveFde(); this->Header.sh_size += align(Length, sizeof(uintX_t)); } } @@ -1062,6 +1263,7 @@ uintX_t Len = writeAlignedCieOrFde(F.data(), Buf + Offset); write32(Buf + Offset + 4, Offset + 4 - CieOffset); // Pointer F.S->Offsets[F.Index].second = Offset; + Out::EhFrameHdr->addFde(C.FdeEncoding, Offset, Buf + Offset + 8); Offset += Len; } } @@ -1455,6 +1657,11 @@ template class OutputSectionBase; template class OutputSectionBase; +template class EhFrameHeader; +template class EhFrameHeader; +template class EhFrameHeader; +template class EhFrameHeader; + template class GotPltSection; template class GotPltSection; template class GotPltSection; Index: lld/trunk/ELF/Writer.cpp =================================================================== --- lld/trunk/ELF/Writer.cpp +++ lld/trunk/ELF/Writer.cpp @@ -140,6 +140,8 @@ Out::RelaPlt = &RelaPlt; DynamicSection Dynamic(*Symtab); Out::Dynamic = &Dynamic; + EhFrameHeader EhFrameHdr; + Out::EhFrameHdr = &EhFrameHdr; Writer(*Symtab).run(); } @@ -910,6 +912,9 @@ Add(Out::GotPlt); if (!Out::Plt->empty()) Add(Out::Plt); + + if (Out::EhFrameHdr->Live) + Add(Out::EhFrameHdr); } // The linker is expected to define SECNAME_start and SECNAME_end @@ -1109,6 +1114,12 @@ *PH = GnuRelroPhdr; } + if (Out::EhFrameHdr->Live) { + Elf_Phdr *PH = &Phdrs[++PhdrIdx]; + PH->p_type = PT_GNU_EH_FRAME; + copyPhdr(PH, Out::EhFrameHdr); + } + // PT_GNU_STACK is a special section to tell the loader to make the // pages for the stack non-executable. if (!Config->ZExecStack) { @@ -1158,6 +1169,8 @@ ++I; if (HasRelro) ++I; + if (Out::EhFrameHdr->Live) + ++I; return I; } Index: lld/trunk/test/ELF/eh-frame-hdr-no-out.s =================================================================== --- lld/trunk/test/ELF/eh-frame-hdr-no-out.s +++ lld/trunk/test/ELF/eh-frame-hdr-no-out.s @@ -0,0 +1,6 @@ +// REQUIRES: x86 +// RUN: not ld.lld --eh-frame-hdr %p/Inputs/invalid-cie-version2.elf -o %t >& %t.log +// RUN: FileCheck %s < %t.log + +// invalid-cie-version2.elf contains unsupported version of CIE = 2. +// CHECK: FDE version 1 or 3 expected, but got 2 Index: lld/trunk/test/ELF/eh-frame-hdr-no-out2.s =================================================================== --- lld/trunk/test/ELF/eh-frame-hdr-no-out2.s +++ lld/trunk/test/ELF/eh-frame-hdr-no-out2.s @@ -0,0 +1,19 @@ +// REQUIRES: x86 +// RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o +// RUN: ld.lld --eh-frame-hdr %t.o -o %t +// RUN: llvm-readobj -s -program-headers %t | FileCheck %s --check-prefix=NOHDR + +.section foo,"ax",@progbits + nop + +.text +.globl _start; +_start: + +// There is no .eh_frame section, +// therefore .eh_frame_hdr also not created. +// NOHDR: Sections [ +// NOHDR-NOT: Name: .eh_frame +// NOHDR-NOT: Name: .eh_frame_hdr +// NOHDR: ProgramHeaders [ +// NOHDR-NOT: PT_GNU_EH_FRAME Index: lld/trunk/test/ELF/eh-frame-hdr.s =================================================================== --- lld/trunk/test/ELF/eh-frame-hdr.s +++ lld/trunk/test/ELF/eh-frame-hdr.s @@ -0,0 +1,127 @@ +// REQUIRES: x86 +// RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o +// RUN: ld.lld %t.o -o %t +// RUN: llvm-readobj -file-headers -s -section-data -program-headers -symbols %t | FileCheck %s --check-prefix=NOHDR +// RUN: ld.lld --eh-frame-hdr %t.o -o %t +// RUN: llvm-readobj -file-headers -s -section-data -program-headers -symbols %t | FileCheck %s --check-prefix=HDR +// RUN: llvm-objdump -d %t | FileCheck %s --check-prefix=HDRDISASM + +.section foo,"ax",@progbits +.cfi_startproc + nop +.cfi_endproc + +.section bar,"ax",@progbits +.cfi_startproc + nop +.cfi_endproc + +.section dah,"ax",@progbits +.cfi_startproc + nop +.cfi_endproc + +.text +.globl _start; +_start: + +// NOHDR: Sections [ +// NOHDR-NOT: Name: .eh_frame_hdr +// NOHDR: ProgramHeaders [ +// NOHDR-NOT: PT_GNU_EH_FRAME + +//HDRDISASM: Disassembly of section foo: +//HDRDISASM-NEXT: foo: +//HDRDISASM-NEXT: 11000: 90 nop +//HDRDISASM-NEXT: Disassembly of section bar: +//HDRDISASM-NEXT: bar: +//HDRDISASM-NEXT: 11001: 90 nop +//HDRDISASM-NEXT: Disassembly of section dah: +//HDRDISASM-NEXT: dah: +//HDRDISASM-NEXT: 11002: 90 nop + +// HDR: Sections [ +// HDR: Section { +// HDR: Index: 1 +// HDR-NEXT: Name: .eh_frame +// HDR-NEXT: Type: SHT_X86_64_UNWIND +// HDR-NEXT: Flags [ +// HDR-NEXT: SHF_ALLOC +// HDR-NEXT: ] +// HDR-NEXT: Address: 0x10158 +// HDR-NEXT: Offset: 0x158 +// HDR-NEXT: Size: 96 +// HDR-NEXT: Link: 0 +// HDR-NEXT: Info: 0 +// HDR-NEXT: AddressAlignment: 8 +// HDR-NEXT: EntrySize: 0 +// HDR-NEXT: SectionData ( +// HDR-NEXT: 0000: 14000000 00000000 017A5200 01781001 | +// HDR-NEXT: 0010: 1B0C0708 90010000 14000000 1C000000 | +// HDR-NEXT: 0020: 880E0000 01000000 00000000 00000000 | +// HDR-NEXT: 0030: 14000000 34000000 710E0000 01000000 | +// HDR-NEXT: 0040: 00000000 00000000 14000000 4C000000 | +// HDR-NEXT: 0050: 5A0E0000 01000000 00000000 00000000 | +// CIE: 14000000 00000000 017A5200 01781001 1B0C0708 90010000 +// FDE(1): 14000000 1C000000 880E0000 01000000 00000000 00000000 +// address of data (starts with 0x880E0000) = 0x10158 + 0x0020 = 0x10178 +// The starting address to which this FDE applies = 0xE88 + 0x10178 = 0x11000 +// The number of bytes after the start address to which this FDE applies = 0x01000000 = 1 +// FDE(2): 14000000 34000000 710E0000 01000000 00000000 00000000 +// address of data (starts with 0x710E0000) = 0x10158 + 0x0038 = 0x10190 +// The starting address to which this FDE applies = 0xE71 + 0x10190 = 0x11001 +// The number of bytes after the start address to which this FDE applies = 0x01000000 = 1 +// FDE(3): 14000000 4C000000 5A0E0000 01000000 00000000 00000000 +// address of data (starts with 0x5A0E0000) = 0x10158 + 0x0050 = 0x101A8 +// The starting address to which this FDE applies = 0xE5A + 0x101A8 = 0x11002 +// The number of bytes after the start address to which this FDE applies = 0x01000000 = 1 +// HDR-NEXT: ) +// HDR-NEXT: } +// HDR-NEXT: Section { +// HDR-NEXT: Index: 2 +// HDR-NEXT: Name: .eh_frame_hdr +// HDR-NEXT: Type: SHT_PROGBITS +// HDR-NEXT: Flags [ +// HDR-NEXT: SHF_ALLOC +// HDR-NEXT: ] +// HDR-NEXT: Address: 0x101B8 +// HDR-NEXT: Offset: 0x1B8 +// HDR-NEXT: Size: 36 +// HDR-NEXT: Link: 0 +// HDR-NEXT: Info: 0 +// HDR-NEXT: AddressAlignment: 0 +// HDR-NEXT: EntrySize: 0 +// HDR-NEXT: SectionData ( +// HDR-NEXT: 0000: 011B033B 9CFFFFFF 03000000 480E0000 | +// HDR-NEXT: 0010: B8FFFFFF 490E0000 D0FFFFFF 4A0E0000 | +// HDR-NEXT: 0020: E8FFFFFF | +// Header (always 4 bytes): 0x011B033B +// 9CFFFFFF = .eh_frame(0x10158) - .eh_frame_hdr(0x101B8) - 4 +// 03000000 = 3 = the number of FDE pointers in the table. +// Entry(1): 480E0000 B8FFFFFF +// 480E0000 = 0x11000 - .eh_frame_hdr(0x101B8) = 0xE48 +// B8FFFFFF = address of FDE(1) - .eh_frame_hdr(0x101B8) = +// = .eh_frame(0x10158) + 24 - 0x101B8 = 0xFFFFFFB8 +// Entry(2): 490E0000 D0FFFFFF +// 490E0000 = 0x11001 - .eh_frame_hdr(0x101B8) = 0xE49 +// D0FFFFFF = address of FDE(2) - .eh_frame_hdr(0x101B8) = +// = .eh_frame(0x10158) + 24 + 24 - 0x101B8 = 0xFFFFFFD0 +// Entry(3): 4A0E0000 E8FFFFFF +// 4A0E0000 = 0x11002 - .eh_frame_hdr(0x101B8) = 0xE4A +// E8FFFFFF = address of FDE(2) - .eh_frame_hdr(0x101B8) = +// = .eh_frame(0x10158) + 24 + 24 - 0x101B8 = 0xFFFFFFE8 +// HDR-NEXT: ) +// HDR-NEXT: } +// HDR: ProgramHeaders [ +// HDR: ProgramHeader { +// HDR: Type: PT_GNU_EH_FRAME +// HDR-NEXT: Offset: 0x1B8 +// HDR-NEXT: VirtualAddress: 0x101B8 +// HDR-NEXT: PhysicalAddress: 0x101B8 +// HDR-NEXT: FileSize: 36 +// HDR-NEXT: MemSize: 36 +// HDR-NEXT: Flags [ +// HDR-NEXT: PF_R +// HDR-NEXT: ] +// HDR-NEXT: Alignment: 1 +// HDR-NEXT: }