Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -56,6 +56,7 @@ bool DiscardAll; bool DiscardLocals; bool DiscardNone; + bool EhFrameHdr; bool EnableNewDtags; bool ExportDynamic; bool GcSections; Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -188,6 +188,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: ELF/Options.td =================================================================== --- ELF/Options.td +++ 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">; @@ -151,7 +154,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: ELF/OutputSections.h =================================================================== --- ELF/OutputSections.h +++ 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: + bool readCieInfo(Cie &Cie, ArrayRef D); uintX_t readEntryLength(ArrayRef D); std::vector *> Sections; @@ -433,6 +435,41 @@ return llvm::RoundUpToAlignment(Value, Align); } +// --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 EhFrameHdr final : public OutputSectionBase { + typedef typename llvm::object::ELFFile::uintX_t uintX_t; + +public: + EhFrameHdr(); + 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; +}; + // 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. @@ -440,6 +477,7 @@ typedef typename llvm::object::ELFFile::uintX_t uintX_t; typedef typename llvm::object::ELFFile::Elf_Phdr Elf_Phdr; static DynamicSection *Dynamic; + static EhFrameHdr *EhFrameHdr; static GnuHashTableSection *GnuHashTab; static GotPltSection *GotPlt; static GotSection *Got; @@ -461,6 +499,7 @@ }; template DynamicSection *Out::Dynamic; +template EhFrameHdr *Out::EhFrameHdr; template GnuHashTableSection *Out::GnuHashTab; template GotPltSection *Out::GotPlt; template GotSection *Out::Got; Index: ELF/OutputSections.cpp =================================================================== --- ELF/OutputSections.cpp +++ 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,103 @@ } template +EhFrameHdr::EhFrameHdr() + : 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 EhFrameHdr::uintX_t +EhFrameHdr::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 EhFrameHdr::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. + uintX_t InitialPcOff = I.first - VA; + write32(Buf, InitialPcOff); + // The last four bytes are an offset to the FDE data itself. + uintX_t FdeDataOff = EhVA + I.second - VA; + write32(Buf + 4, FdeDataOff); + Buf += 8; + } +} + +template +void EhFrameHdr::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 EhFrameHdr::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 EhFrameHdr::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) {} @@ -906,9 +1005,11 @@ } template -EHOutputSection::EHOutputSection(StringRef Name, uint32_t Type, - uintX_t Flags) - : OutputSectionBase(Name, Type, Flags) {} +EHOutputSection::EHOutputSection(StringRef Name, uint32_t sh_type, + uintX_t sh_flags) + : OutputSectionBase(Name, sh_type, sh_flags) { + Out::EhFrameHdr->assignEhFrame(this); +} template EHRegion::EHRegion(EHInputSection *S, unsigned Index) @@ -927,6 +1028,111 @@ Cie::Cie(EHInputSection *S, unsigned Index) : EHRegion(S, Index) {} +// Read a byte and advance D by one byte. +static bool readByte(ArrayRef &D, uint8_t *R = nullptr) { + if (D.empty()) + return false; + if (R) + *R = D.front(); + D = D.slice(1); + return true; +} + +static bool skipLeb128(ArrayRef &D) { + while (!D.empty()) { + uint8_t Val = D.front(); + D = D.slice(1); + if ((Val & 0x80) == 0) + return true; + } + return false; +} + +template static unsigned getSizeForEncoding(unsigned Enc) { + typedef typename ELFFile::uintX_t uintX_t; + unsigned format = Enc & 0x0f; + switch (format) { + default: + llvm_unreachable("Unknown 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 +bool EHOutputSection::readCieInfo(Cie &Cie, ArrayRef D) { + uint8_t Version; + if (!readByte(D, &Version)) + return false; + if (Version != 1 && Version != 3) + return false; + + auto AugEnd = std::find(D.begin() + 1, D.end(), '\0'); + if (AugEnd == D.end()) + return false; + + ArrayRef AugString(D.begin(), AugEnd - D.begin()); + D = D.slice(AugString.size() + 1); + + // Code alignment factor should always be 1 for .eh_frame. + uint8_t CodeAlign; + if (!readByte(D, &CodeAlign) || CodeAlign != 1) + return false; + // Skip data alignment factor + if (!skipLeb128(D)) + return false; + + // 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)) || (Version == 3 && !skipLeb128(D))) + return false; + + Cie.FdeEncoding = dwarf::DW_EH_PE_absptr; + uint8_t AugSym; + while (readByte(AugString, &AugSym)) { + switch (AugSym) { + case 'z': + if (!skipLeb128(D)) + return false; + break; + case 'R': + return readByte(D, &Cie.FdeEncoding); + case 'P': { + uint8_t Enc; + if (!readByte(D, &Enc)) + return false; + unsigned EncSize = getSizeForEncoding(Enc); + // DW_EH_PE_aligned not yet supported. + if ((Enc & 0xf0) == dwarf::DW_EH_PE_aligned) + return false; + if (D.size() < EncSize) + return false; + 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: + return false; + } + } + return true; +} + template template void EHOutputSection::addSectionAux( @@ -965,6 +1171,11 @@ // CIE Cie C(S, Index); + if (!readCieInfo(C, ArrayRef(D.data() + 8, Length - 8))) { + Out::EhFrameHdr->Live = false; + warning("corrupted or unsupported CIE information"); + } + StringRef Personality; if (HasReloc) { uint32_t SymIndex = RelI->getSymbol(Config->Mips64EL); @@ -989,6 +1200,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 +1274,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 +1668,11 @@ template class OutputSectionBase; template class OutputSectionBase; +template class EhFrameHdr; +template class EhFrameHdr; +template class EhFrameHdr; +template class EhFrameHdr; + template class GotPltSection; template class GotPltSection; template class GotPltSection; Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -140,6 +140,8 @@ Out::RelaPlt = &RelaPlt; DynamicSection Dynamic(*Symtab); Out::Dynamic = &Dynamic; + EhFrameHdr EhFrameHdr; + Out::EhFrameHdr = &EhFrameHdr; Writer(*Symtab).run(); } @@ -903,6 +905,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 @@ -1102,6 +1107,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) { @@ -1151,6 +1162,8 @@ ++I; if (HasRelro) ++I; + if (Out::EhFrameHdr->Live) + ++I; return I; } Index: test/ELF/eh-frame-hdr-no-out.s =================================================================== --- test/ELF/eh-frame-hdr-no-out.s +++ test/ELF/eh-frame-hdr-no-out.s @@ -0,0 +1,12 @@ +// REQUIRES: x86 +// RUN: ld.lld --eh-frame-hdr %p/Inputs/invalid-cie-version2.elf -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 %p/Inputs/invalid-cie-version2.elf -o %t 2>&1 | FileCheck %s +// CHECK: corrupted or unsupported CIE information + +// invalid-cie-version2.elf contains unsupported +// version of CIE = 2. No .eh_frame_hdr is produced. +// NOHDR: Sections [ +// NOHDR-NOT: Name: .eh_frame_hdr +// NOHDR: ProgramHeaders [ +// NOHDR-NOT: PT_GNU_EH_FRAME Index: test/ELF/eh-frame-hdr-no-out2.s =================================================================== --- test/ELF/eh-frame-hdr-no-out2.s +++ 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: test/ELF/eh-frame-hdr.s =================================================================== --- test/ELF/eh-frame-hdr.s +++ 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: }