[ELF] Add -z dead-reloc-in-nonalloc=<section_glob>=<value>

... to customize the tombstone value we use for an absolute relocation
referencing a discarded symbol. This can be used as a workaround when
some debug processing tool has trouble with current -1 tombstone value
(https://bugs.chromium.org/p/chromium/issues/detail?id=1102223#c11 )

For example, to get the current built-in rules (not considering the .debug_line special case for ICF):

```
-z dead-reloc-in-nonalloc='.debug_*=0xffffffffffffffff'
-z dead-reloc-in-nonalloc=.debug_loc=0xfffffffffffffffe
-z dead-reloc-in-nonalloc=.debug_ranges=0xfffffffffffffffe
```

To get GNU ld (as of binutils 2.35)'s behavior:

```
-z dead-reloc-in-nonalloc='*=0'
-z dead-reloc-in-nonalloc=.debug_ranges=1
```

This option has other use cases. For example, if we want to check
whether a non-SHF_ALLOC section has dead relocations.
With this patch, we can run a regular LLD and run another with a special
-z dead-reloc-in-nonalloc=, then compare their output.

Reviewed By: thakis

Differential Revision: https://reviews.llvm.org/D83264
This commit is contained in:
Fangrui Song 2020-07-08 10:10:43 -07:00
parent 9520b6c8ab
commit 4ce56b8122
6 changed files with 119 additions and 3 deletions

View File

@ -145,6 +145,7 @@ struct Configuration {
bool checkSections; bool checkSections;
bool compressDebugSections; bool compressDebugSections;
bool cref; bool cref;
std::vector<std::pair<llvm::GlobPattern, uint64_t>> deadRelocInNonAlloc;
bool defineCommon; bool defineCommon;
bool demangle = true; bool demangle = true;
bool dependentLibraries; bool dependentLibraries;

View File

@ -444,6 +444,7 @@ static bool isKnownZFlag(StringRef s) {
s == "rela" || s == "relro" || s == "retpolineplt" || s == "rela" || s == "relro" || s == "retpolineplt" ||
s == "rodynamic" || s == "shstk" || s == "text" || s == "undefs" || s == "rodynamic" || s == "shstk" || s == "text" || s == "undefs" ||
s == "wxneeded" || s.startswith("common-page-size=") || s == "wxneeded" || s.startswith("common-page-size=") ||
s.startswith("dead-reloc-in-nonalloc=") ||
s.startswith("max-page-size=") || s.startswith("stack-size=") || s.startswith("max-page-size=") || s.startswith("stack-size=") ||
s.startswith("start-stop-visibility="); s.startswith("start-stop-visibility=");
} }
@ -1069,6 +1070,27 @@ static void readConfigs(opt::InputArgList &args) {
config->zText = getZFlag(args, "text", "notext", true); config->zText = getZFlag(args, "text", "notext", true);
config->zWxneeded = hasZOption(args, "wxneeded"); config->zWxneeded = hasZOption(args, "wxneeded");
for (opt::Arg *arg : args.filtered(OPT_z)) {
std::pair<StringRef, StringRef> option =
StringRef(arg->getValue()).split('=');
if (option.first != "dead-reloc-in-nonalloc")
continue;
constexpr StringRef errPrefix = "-z dead-reloc-in-nonalloc=: ";
std::pair<StringRef, StringRef> kv = option.second.split('=');
if (kv.first.empty() || kv.second.empty()) {
error(errPrefix + "expected <section_glob>=<value>");
continue;
}
uint64_t v;
if (!to_integer(kv.second, v))
error(errPrefix + "expected a non-negative integer, but got '" +
kv.second + "'");
else if (Expected<GlobPattern> pat = GlobPattern::create(kv.first))
config->deadRelocInNonAlloc.emplace_back(std::move(*pat), v);
else
error(errPrefix + toString(pat.takeError()));
}
// Parse LTO options. // Parse LTO options.
if (auto *arg = args.getLastArg(OPT_plugin_opt_mcpu_eq)) if (auto *arg = args.getLastArg(OPT_plugin_opt_mcpu_eq))
parseClangOption(saver.save("-mcpu=" + StringRef(arg->getValue())), parseClangOption(saver.save("-mcpu=" + StringRef(arg->getValue())),

View File

@ -857,6 +857,12 @@ void InputSection::relocateNonAlloc(uint8_t *buf, ArrayRef<RelTy> rels) {
const bool isDebugLocOrRanges = const bool isDebugLocOrRanges =
isDebug && (name == ".debug_loc" || name == ".debug_ranges"); isDebug && (name == ".debug_loc" || name == ".debug_ranges");
const bool isDebugLine = isDebug && name == ".debug_line"; const bool isDebugLine = isDebug && name == ".debug_line";
Optional<uint64_t> tombstone;
for (const auto &patAndValue : llvm::reverse(config->deadRelocInNonAlloc))
if (patAndValue.first.match(this->name)) {
tombstone = patAndValue.second;
break;
}
for (const RelTy &rel : rels) { for (const RelTy &rel : rels) {
RelType type = rel.getType(config->isMips64EL); RelType type = rel.getType(config->isMips64EL);
@ -907,7 +913,8 @@ void InputSection::relocateNonAlloc(uint8_t *buf, ArrayRef<RelTy> rels) {
continue; 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 // Resolve relocations in .debug_* referencing (discarded symbols or ICF
// folded section symbols) to a tombstone value. Resolving to addend is // folded section symbols) to a tombstone value. Resolving to addend is
// unsatisfactory because the result address range may collide with a // unsatisfactory because the result address range may collide with a
@ -935,8 +942,11 @@ void InputSection::relocateNonAlloc(uint8_t *buf, ArrayRef<RelTy> rels) {
auto *ds = dyn_cast<Defined>(&sym); auto *ds = dyn_cast<Defined>(&sym);
if (!sym.getOutputSection() || if (!sym.getOutputSection() ||
(ds && ds->section->repl != ds->section && !isDebugLine)) { (ds && ds->section->repl != ds->section && !isDebugLine)) {
target->relocateNoSym(bufLoc, type, // If -z dead-reloc-in-nonalloc= is specified, respect it.
isDebugLocOrRanges ? UINT64_MAX - 1 : UINT64_MAX); const uint64_t value =
tombstone ? SignExtend64<bits>(*tombstone)
: (isDebugLocOrRanges ? UINT64_MAX - 1 : UINT64_MAX);
target->relocateNoSym(bufLoc, type, value);
continue; continue;
} }
} }

View File

@ -625,6 +625,13 @@ Use wrapper functions for symbol.
Linker option extensions. Linker option extensions.
.Bl -tag -width indent -compact .Bl -tag -width indent -compact
.Pp .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 .It Cm execstack
Make the main stack executable. Make the main stack executable.
Stack permissions are recorded in the Stack permissions are recorded in the

View File

@ -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 <section_glob>=<value>
# 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

View File

@ -19,6 +19,13 @@
# CHECK-NEXT: 0000 ffffffff ffffffff 08000000 00000000 # CHECK-NEXT: 0000 ffffffff ffffffff 08000000 00000000
# CHECK-NEXT: 0010 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" .section .text.1,"ax"
.byte 0 .byte 0
.section .text.2,"axe" .section .text.2,"axe"