Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -103,6 +103,7 @@ bool Demangle = true; bool DisableVerify; bool EhFrameHdr; + bool EmitRelocs; bool EnableNewDtags; bool ExportDynamic; bool FatalWarnings; Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -482,6 +482,7 @@ Config->BsymbolicFunctions = Args.hasArg(OPT_Bsymbolic_functions); Config->Demangle = getArg(Args, OPT_demangle, OPT_no_demangle, true); Config->DisableVerify = Args.hasArg(OPT_disable_verify); + Config->EmitRelocs = Args.hasArg(OPT_emit_relocs); Config->EhFrameHdr = Args.hasArg(OPT_eh_frame_hdr); Config->EnableNewDtags = !Args.hasArg(OPT_disable_new_dtags); Config->ExportDynamic = Index: ELF/InputFiles.cpp =================================================================== --- ELF/InputFiles.cpp +++ ELF/InputFiles.cpp @@ -399,6 +399,19 @@ } assert(isUInt<31>(NumRelocations)); Target->NumRelocations = NumRelocations; + + // Relocation sections processed by the linker are usually removed + // from the output, so returning `nullptr` for the normal case. + // However, if -emit-relocs is given, we need to leave them in the output. + // (Some post link analysis tools need this information.) + if (Config->EmitRelocs) { + if (!isa>(Target)) + fatal(toString(this) + ": --emit-relocs for relocations sections " + "pointing to .eh_frame is not supported"); + InputSection *RelocSec = make>(this, &Sec, Name); + cast>(Target)->DependentSection = RelocSec; + return RelocSec; + } return nullptr; } } Index: ELF/InputSection.h =================================================================== --- ELF/InputSection.h +++ ELF/InputSection.h @@ -311,6 +311,8 @@ } // namespace elf template std::string toString(const elf::InputSectionBase *); +bool shouldGC(uint64_t Flags, uint32_t Type); + } // namespace lld #endif Index: ELF/InputSection.cpp =================================================================== --- ELF/InputSection.cpp +++ ELF/InputSection.cpp @@ -42,6 +42,19 @@ return (FileName + ":(" + Sec->Name + ")").str(); } +// Returns if GC should be enabled for section. We do not run GC for +// non-allocatable sections, which typically contain debugging information. +// REL[A] sections are not allocatable, but we do GC for them, because them are +// dependent on section they relocate, we do not want them be live +// unconditionally. That is used for --emit-relocs. +bool lld::shouldGC(uint64_t Flags, uint32_t Type) { + if (!Config->GcSections) + return false; + if (Type == SHT_RELA || Type == SHT_REL) + return true; + return Flags & SHF_ALLOC; +} + template static ArrayRef getSectionContents(elf::ObjectFile *File, const typename ELFT::Shdr *Hdr) { @@ -57,8 +70,7 @@ uint32_t Info, uintX_t Addralign, ArrayRef Data, StringRef Name, Kind SectionKind) - : InputSectionData(SectionKind, Name, Data, - !Config->GcSections || !(Flags & SHF_ALLOC)), + : InputSectionData(SectionKind, Name, Data, !shouldGC(Flags, Type)), File(File), Flags(Flags), Entsize(Entsize), Type(Type), Link(Link), Info(Info), Repl(this) { NumRelocations = 0; @@ -229,9 +241,9 @@ return Total; } -// This is used for -r. We can't use memcpy to copy relocations because we need -// to update symbol table offset and section index for each relocation. So we -// copy relocations one by one. +// This is used for -r and --emit-relocs. We can't use memcpy to copy +// relocations because we need to update symbol table offset and section index +// for each relocation. So we copy relocations one by one. template template void InputSection::copyRelocations(uint8_t *Buf, ArrayRef Rels) { @@ -250,7 +262,11 @@ if (Config->Rela) P->r_addend = getAddend(Rel); - P->r_offset = RelocatedSection->getOffset(Rel.r_offset); + + // Output section VA is zero for -r, so r_offset is an offset within the + // section, but for --emit-relocs it is an absolute address. + P->r_offset = RelocatedSection->OutSec->Addr + + RelocatedSection->getOffset(Rel.r_offset); P->setSymbolAndType(In::SymTab->getSymbolIndex(&Body), Type, Config->Mips64EL); } @@ -534,7 +550,8 @@ return; } - // If -r is given, then an InputSection may be a relocation section. + // If -r or --emit-relocs is given, then an InputSection + // may be a relocation section. if (this->Type == SHT_RELA) { copyRelocations(Buf + OutSecOff, this->template getDataAs()); return; Index: ELF/LinkerScript.cpp =================================================================== --- ELF/LinkerScript.cpp +++ ELF/LinkerScript.cpp @@ -262,6 +262,12 @@ for (InputSectionBase *S : V) { S->Live = false; reportDiscarded(S); + + // If we discard a section, we also should discard a dependent section. + InputSection *IS = dyn_cast>(S); + if (!IS || !IS->DependentSection || !IS->DependentSection->Live) + continue; + discard({IS->DependentSection}); } } Index: ELF/MarkLive.cpp =================================================================== --- ELF/MarkLive.cpp +++ ELF/MarkLive.cpp @@ -158,7 +158,7 @@ // We do not garbage-collect two types of sections: // 1) Sections used by the loader (.init, .fini, .ctors, .dtors or .jcr) -// 2) Non-allocatable sections which typically contain debugging information +// 2) Sections that are not supposed to be GC, see comment for shouldGC(). template static bool isReserved(InputSectionBase *Sec) { switch (Sec->Type) { case SHT_FINI_ARRAY: @@ -167,7 +167,7 @@ case SHT_PREINIT_ARRAY: return true; default: - if (!(Sec->Flags & SHF_ALLOC)) + if (!shouldGC(Sec->Flags, Sec->Type)) return true; // We do not want to reclaim sections if they can be referred @@ -196,8 +196,8 @@ if (!R.Sec || R.Sec == &InputSection::Discarded) return; - // We don't gc non alloc sections. - if (!(R.Sec->Flags & SHF_ALLOC)) + // We don't GC some sections, see comment for shouldGC(). + if (!shouldGC(R.Sec->Flags, R.Sec->Type)) return; // Usually, a whole section is marked as live or dead, but in mergeable Index: ELF/Options.td =================================================================== --- ELF/Options.td +++ ELF/Options.td @@ -70,6 +70,8 @@ def eh_frame_hdr: F<"eh-frame-hdr">, HelpText<"Request creation of .eh_frame_hdr section and PT_GNU_EH_FRAME segment header">; +def emit_relocs: F<"emit-relocs">, HelpText<"Generate relocations in output">; + def enable_new_dtags: F<"enable-new-dtags">, HelpText<"Enable new dynamic tags">; @@ -277,6 +279,7 @@ def alias_discard_all_x: Flag<["-"], "x">, Alias; def alias_discard_locals_X: Flag<["-"], "X">, Alias; def alias_dynamic_list: J<"dynamic-list=">, Alias; +def alias_emit_relocs: Flag<["-"], "q">, Alias; def alias_entry_e: JoinedOrSeparate<["-"], "e">, Alias; def alias_entry_entry: J<"entry=">, Alias; def alias_error_limit: J<"error-limit=">, Alias; Index: ELF/OutputSections.cpp =================================================================== --- ELF/OutputSections.cpp +++ ELF/OutputSections.cpp @@ -116,7 +116,8 @@ } uint32_t Type = this->Type; - if (!Config->Relocatable || (Type != SHT_RELA && Type != SHT_REL)) + if ((!Config->Relocatable && !Config->EmitRelocs) || + (Type != SHT_RELA && Type != SHT_REL)) return; this->Link = In::SymTab->OutSec->SectionIndex; Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -396,9 +396,9 @@ if (B.isFile()) return false; - // We keep sections in symtab for relocatable output. + // We keep sections in symtab for relocatable output and --emit-reloc. if (B.isSection()) - return Config->Relocatable; + return Config->Relocatable || Config->EmitRelocs; // If sym references a section in a discarded group, don't keep it. if (Sec == &InputSection::Discarded) Index: test/ELF/Inputs/emit-relocs.s =================================================================== --- test/ELF/Inputs/emit-relocs.s +++ test/ELF/Inputs/emit-relocs.s @@ -0,0 +1,10 @@ +.section .text +.globl fn2 +.type fn2,@function +fn2: + nop + +foo: + movl $foo, %edx + callq fn2@PLT + nop Index: test/ELF/emit-relocs-gc.s =================================================================== --- test/ELF/emit-relocs-gc.s +++ test/ELF/emit-relocs-gc.s @@ -0,0 +1,30 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t1.o + +## Check what we emit for non-gc case. +# RUN: ld.lld --emit-relocs %t1.o -o %t +# RUN: llvm-objdump %t -section-headers | FileCheck %s --check-prefix=NOGC +# NOGC: Sections: +# NOGC-NEXT: Idx Name +# NOGC-NEXT: 0 +# NOGC-NEXT: 1 .bar +# NOGC-NEXT: 2 .text +# NOGC-NEXT: 3 .rela.text +# NOGC-NEXT: 4 .rela.bar + +## GC collects .bar section and we do not emit .rela.bar then. +# RUN: ld.lld --gc-sections --emit-relocs %t1.o -o %t +# RUN: llvm-objdump %t -section-headers | FileCheck %s --check-prefix=GC +# GC: Sections: +# GC-NEXT: Idx Name +# GC-NEXT: 0 +# GC-NEXT: 1 .text +# GC-NEXT: 2 .rela.text +# GC-NOT: rela.bar + +.globl _start +_start: + .quad .text + +.section .bar,"a" +.quad .bar Index: test/ELF/emit-relocs.s =================================================================== --- test/ELF/emit-relocs.s +++ test/ELF/emit-relocs.s @@ -0,0 +1,98 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t1.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %p/Inputs/emit-relocs.s -o %t2.o +# RUN: ld.lld --emit-relocs %t1.o %t2.o -o %t +# RUN: llvm-readobj -t -r %t | FileCheck %s + +## Check single dash form. +# RUN: ld.lld -emit-relocs %t1.o %t2.o -o %t1 +# RUN: llvm-readobj -t -r %t1 | FileCheck %s + +## Check alias. +# RUN: ld.lld -q %t1.o %t2.o -o %t2 +# RUN: llvm-readobj -t -r %t2 | FileCheck %s + +# CHECK: Relocations [ +# CHECK-NEXT: Section ({{.*}}) .rela.text { +# CHECK-NEXT: 0x201002 R_X86_64_32 .text 0x1 +# CHECK-NEXT: 0x201007 R_X86_64_PLT32 fn 0xFFFFFFFFFFFFFFFC +# CHECK-NEXT: 0x20100E R_X86_64_32 .text 0x1 +# CHECK-NEXT: 0x201013 R_X86_64_PLT32 fn2 0xFFFFFFFFFFFFFFFC +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: Symbols [ +# CHECK-NEXT: Symbol { +# CHECK-NEXT: Name: +# CHECK-NEXT: Value: 0x0 +# CHECK-NEXT: Size: 0 +# CHECK-NEXT: Binding: Local +# CHECK-NEXT: Type: None +# CHECK-NEXT: Other: 0 +# CHECK-NEXT: Section: Undefined +# CHECK-NEXT: } +# CHECK-NEXT: Symbol { +# CHECK-NEXT: Name: bar +# CHECK-NEXT: Value: 0x201001 +# CHECK-NEXT: Size: 0 +# CHECK-NEXT: Binding: Local +# CHECK-NEXT: Type: None +# CHECK-NEXT: Other: 0 +# CHECK-NEXT: Section: .text +# CHECK-NEXT: } +# CHECK-NEXT: Symbol { +# CHECK-NEXT: Name: +# CHECK-NEXT: Value: 0x201000 +# CHECK-NEXT: Size: 0 +# CHECK-NEXT: Binding: Local +# CHECK-NEXT: Type: Section +# CHECK-NEXT: Other: 0 +# CHECK-NEXT: Section: .text +# CHECK-NEXT: } +# CHECK-NEXT: Symbol { +# CHECK-NEXT: Name: foo +# CHECK-NEXT: Value: 0x20100D +# CHECK-NEXT: Size: 0 +# CHECK-NEXT: Binding: Local +# CHECK-NEXT: Type: None +# CHECK-NEXT: Other: 0 +# CHECK-NEXT: Section: .text +# CHECK-NEXT: } +# CHECK-NEXT: Symbol { +# CHECK-NEXT: Name: +# CHECK-NEXT: Value: 0x20100C +# CHECK-NEXT: Size: 0 +# CHECK-NEXT: Binding: Local +# CHECK-NEXT: Type: Section +# CHECK-NEXT: Other: 0 +# CHECK-NEXT: Section: .text +# CHECK-NEXT: } +# CHECK-NEXT: Symbol { +# CHECK-NEXT: Name: fn +# CHECK-NEXT: Value: 0x201000 +# CHECK-NEXT: Size: 0 +# CHECK-NEXT: Binding: Global +# CHECK-NEXT: Type: Function +# CHECK-NEXT: Other: 0 +# CHECK-NEXT: Section: .text +# CHECK-NEXT: } +# CHECK-NEXT: Symbol { +# CHECK-NEXT: Name: fn2 +# CHECK-NEXT: Value: 0x20100C +# CHECK-NEXT: Size: 0 +# CHECK-NEXT: Binding: Global +# CHECK-NEXT: Type: Function +# CHECK-NEXT: Other: 0 +# CHECK-NEXT: Section: .text +# CHECK-NEXT: } +# CHECK-NEXT: ] + +.section .text +.globl fn +.type fn,@function +fn: + nop + +bar: + movl $bar, %edx + callq fn@PLT + nop Index: test/ELF/linkerscript/emit-reloc-discard.s =================================================================== --- test/ELF/linkerscript/emit-reloc-discard.s +++ test/ELF/linkerscript/emit-reloc-discard.s @@ -0,0 +1,15 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.o +# RUN: echo "SECTIONS { /DISCARD/ : { *(.debug*) } }" > %t.script +# RUN: ld.lld --emit-relocs --script %t.script %t.o -o %t1 +# RUN: llvm-readobj -r %t1 | FileCheck %s + +# CHECK: Relocations [ +# CHECK-NEXT: ] + +.section .debug_str,"MS",@progbits,1 +.Linfo_string0: + .asciz "AAA" + +.section .debug_info,"",@progbits + .long .Linfo_string0