diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -145,6 +145,7 @@ bool checkSections; bool compressDebugSections; bool cref; + std::vector> deadRelocInNonAlloc; bool defineCommon; bool demangle = true; bool dependentLibraries; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -444,6 +444,7 @@ s == "rela" || s == "relro" || s == "retpolineplt" || s == "rodynamic" || s == "shstk" || s == "text" || s == "undefs" || s == "wxneeded" || s.startswith("common-page-size=") || + s.startswith("dead-reloc-in-nonalloc=") || s.startswith("max-page-size=") || s.startswith("stack-size=") || s.startswith("start-stop-visibility="); } @@ -1069,6 +1070,27 @@ config->zText = getZFlag(args, "text", "notext", true); config->zWxneeded = hasZOption(args, "wxneeded"); + for (opt::Arg *arg : args.filtered(OPT_z)) { + std::pair option = + StringRef(arg->getValue()).split('='); + if (option.first != "dead-reloc-in-nonalloc") + continue; + constexpr StringRef errPrefix = "-z dead-reloc-in-nonalloc=: "; + std::pair kv = option.second.split('='); + if (kv.first.empty() || kv.second.empty()) { + error(errPrefix + "expected ="); + continue; + } + uint64_t v; + if (!to_integer(kv.second, v)) + error(errPrefix + "expected a non-negative integer, but got '" + + kv.second + "'"); + else if (Expected pat = GlobPattern::create(kv.first)) + config->deadRelocInNonAlloc.emplace_back(std::move(*pat), v); + else + error(errPrefix + toString(pat.takeError())); + } + // Parse LTO options. if (auto *arg = args.getLastArg(OPT_plugin_opt_mcpu_eq)) parseClangOption(saver.save("-mcpu=" + StringRef(arg->getValue())), diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp --- a/lld/ELF/InputSection.cpp +++ b/lld/ELF/InputSection.cpp @@ -857,6 +857,12 @@ const bool isDebugLocOrRanges = isDebug && (name == ".debug_loc" || name == ".debug_ranges"); const bool isDebugLine = isDebug && name == ".debug_line"; + Optional tombstone; + for (const auto &patAndValue : llvm::reverse(config->deadRelocInNonAlloc)) + if (patAndValue.first.match(this->name)) { + tombstone = patAndValue.second; + break; + } for (const RelTy &rel : rels) { RelType type = rel.getType(config->isMips64EL); @@ -907,7 +913,8 @@ continue; } - if (isDebug && (type == target->symbolicRel || expr == R_DTPREL)) { + if (tombstone || + (isDebug && (type == target->symbolicRel || expr == R_DTPREL))) { // Resolve relocations in .debug_* referencing (discarded symbols or ICF // folded section symbols) to a tombstone value. Resolving to addend is // unsatisfactory because the result address range may collide with a @@ -935,8 +942,11 @@ auto *ds = dyn_cast(&sym); if (!sym.getOutputSection() || (ds && ds->section->repl != ds->section && !isDebugLine)) { - target->relocateNoSym(bufLoc, type, - isDebugLocOrRanges ? UINT64_MAX - 1 : UINT64_MAX); + // If -z dead-reloc-in-nonalloc= is specified, respect it. + const uint64_t value = + tombstone ? SignExtend64(*tombstone) + : (isDebugLocOrRanges ? UINT64_MAX - 1 : UINT64_MAX); + target->relocateNoSym(bufLoc, type, value); continue; } } diff --git a/lld/docs/ld.lld.1 b/lld/docs/ld.lld.1 --- a/lld/docs/ld.lld.1 +++ b/lld/docs/ld.lld.1 @@ -625,6 +625,13 @@ Linker option extensions. .Bl -tag -width indent -compact .Pp +.It Cm dead-reloc-in-nonalloc Ns = Ns Ar section_glob=value +Resolve a relocation in a matched non-SHF_ALLOC section referencing a discarded symbol to +.Ar value +Accepts globs, in the event of a section matching more than one option, the last +option takes precedence. An order of least specific to most specific match is +recommended. +.Pp .It Cm execstack Make the main stack executable. Stack permissions are recorded in the diff --git a/lld/test/ELF/dead-reloc-in-nonalloc.s b/lld/test/ELF/dead-reloc-in-nonalloc.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/dead-reloc-in-nonalloc.s @@ -0,0 +1,69 @@ +# REQUIRES: x86 +## Test that -z dead-reloc-in-nonalloc= can customize the tombstone value we +## use for an absolute relocation referencing a discarded symbol. + +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o +# RUN: ld.lld --icf=all -z dead-reloc-in-nonalloc=.debug_info=0xaaaaaaaa \ +# RUN: -z dead-reloc-in-nonalloc=.not_debug=0xbbbbbbbb %t.o -o %t +# RUN: llvm-objdump -s %t | FileCheck %s --check-prefixes=COMMON,AA +## 0xaaaaaaaa == 2863311530 +# RUN: ld.lld --icf=all -z dead-reloc-in-nonalloc=.debug_info=2863311530 \ +# RUN: -z dead-reloc-in-nonalloc=.not_debug=0xbbbbbbbb %t.o -o - | cmp %t - + +# COMMON: Contents of section .debug_addr: +# COMMON-NEXT: 0000 [[ADDR:[0-9a-f]+]] 00000000 ffffffff ffffffff + +# AA: Contents of section .debug_info: +# AA-NEXT: 0000 [[ADDR]] 00000000 aaaaaaaa 00000000 +# AA: Contents of section .not_debug: +# AA-NEXT: 0000 bbbbbbbb + +## Specifying zero can get a behavior similar to GNU ld. +# RUN: ld.lld --icf=all -z dead-reloc-in-nonalloc=.debug_info=0 %t.o -o %tzero +# RUN: llvm-objdump -s %tzero | FileCheck %s --check-prefixes=COMMON,ZERO + +# ZERO: Contents of section .debug_info: +# ZERO-NEXT: 0000 {{[0-9a-f]+}}000 00000000 00000000 00000000 + +## Glob works. +# RUN: ld.lld --icf=all -z dead-reloc-in-nonalloc='.debug_i*=0xaaaaaaaa' \ +# RUN: -z dead-reloc-in-nonalloc='[.]not_debug=0xbbbbbbbb' %t.o -o - | cmp %t - + +## If a section matches multiple option. The last option wins. +# RUN: ld.lld --icf=all -z dead-reloc-in-nonalloc='.debug_info=1' \ +# RUN: -z dead-reloc-in-nonalloc='.debug_i*=0' %t.o -o - | cmp %tzero - + +## Test all possible invalid cases. +# RUN: not ld.lld -z dead-reloc-in-nonalloc= 2>&1 | FileCheck %s --check-prefix=USAGE +# RUN: not ld.lld -z dead-reloc-in-nonalloc=a= 2>&1 | FileCheck %s --check-prefix=USAGE +# RUN: not ld.lld -z dead-reloc-in-nonalloc==0 2>&1 | FileCheck %s --check-prefix=USAGE + +# USAGE: error: -z dead-reloc-in-nonalloc=: expected = + +# RUN: not ld.lld -z dead-reloc-in-nonalloc=a=-1 2>&1 | FileCheck %s --check-prefix=NON-INTEGER + +# NON-INTEGER: error: -z dead-reloc-in-nonalloc=: expected a non-negative integer, but got '-1' + +# RUN: not ld.lld -z dead-reloc-in-nonalloc='['=0 2>&1 | FileCheck %s --check-prefix=INVALID + +# INVALID: error: -z dead-reloc-in-nonalloc=: invalid glob pattern: [ + +.globl _start +_start: + ret + +## .text.1 will be folded by ICF. +.section .text.1,"ax" + ret + +.section .debug_addr + .quad .text+8 + .quad .text.1+8 + +.section .debug_info + .quad .text+8 + .quad .text.1+8 + +## Test a non-.debug_ section. +.section .not_debug + .long .text.1+8 diff --git a/lld/test/ELF/debug-dead-reloc.s b/lld/test/ELF/debug-dead-reloc.s --- a/lld/test/ELF/debug-dead-reloc.s +++ b/lld/test/ELF/debug-dead-reloc.s @@ -19,6 +19,13 @@ # CHECK-NEXT: 0000 ffffffff ffffffff 08000000 00000000 # CHECK-NEXT: 0010 ffffffff ffffffff 08000000 00000000 +## -z dead-reloc-in-nonalloc= can override the tombstone value. +# RUN: ld.lld --gc-sections -z dead-reloc-in-nonalloc=.debug_loc=42 %t.o %t1.o %t1.o -o %t42 +# RUN: llvm-objdump -s %t42 | FileCheck %s --check-prefix=OVERRIDE + +# OVERRIDE: Contents of section .debug_loc: +# OVERRIDE-NEXT: 0000 2a000000 00000000 2a000000 00000000 + .section .text.1,"ax" .byte 0 .section .text.2,"axe"