Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -102,6 +102,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 @@ -234,6 +234,11 @@ if (Config->Pie) error("-r and -pie may not be used together"); } + + // -emit-relocs and -gc-sections don't conflict in theory, but LLD currently + // does not support the combination due to an implementation limitation. + if (Config->EmitRelocs && Config->GcSections) + error("--emit-relocs and --gc-sections may not be used together"); } static StringRef getString(opt::InputArgList &Args, unsigned Key, @@ -519,6 +524,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,13 @@ } 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) + return make>(this, &Sec, Name); return nullptr; } } Index: ELF/InputSection.cpp =================================================================== --- ELF/InputSection.cpp +++ ELF/InputSection.cpp @@ -214,9 +214,9 @@ return Sections[this->Info]; } -// 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) { @@ -235,7 +235,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); } @@ -514,7 +518,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 @@ -288,6 +288,20 @@ return Ret; } +template static void scanRelocationSections() { + for (InputSectionBase *S : Symtab::X->Sections) { + if (S->Type != SHT_REL && S->Type != SHT_RELA) + continue; + InputSection *IS = cast>(S); + if (!IS->getFile()) + continue; + ArrayRef *> Sections = IS->getFile()->getSections(); + InputSectionBase *Target = Sections[IS->Info]; + if (Target == &InputSection::Discarded || !Target->Live) + S->Live = false; + } +} + template void LinkerScript::addSection(OutputSectionFactory &Factory, InputSectionBase *Sec, @@ -366,6 +380,12 @@ addSection(Factory, S, Cmd->Name); } } + + // When --emit-relocs is used, we leave .rel[a]* sections in the output. + // If target section was discarded from script, we should not emit the + // relocation section for it. scanRelocationSections() handles that case. + if (Config->EmitRelocs) + scanRelocationSections(); } // Add sections that didn't match any sections command. 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">; @@ -283,6 +285,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 @@ -449,9 +449,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,5 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t1.o +# RUN: not ld.lld --gc-sections --emit-relocs %t1.o -o %t 2>&1 \ +# RUN: | FileCheck -check-prefix=ERR %s +# ERR: --emit-relocs and --gc-sections may not be used together 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,14 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.o +# RUN: echo "SECTIONS { /DISCARD/ : { *(.bbb) } }" > %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 .aaa,"",@progbits +.Lfoo: + +.section .bbb,"",@progbits +.long .Lfoo