[ELF] Add -z dead-reloc-in-nonalloc=<section_glob>=<value>
authorFangrui Song <maskray@google.com>
Wed, 8 Jul 2020 17:10:43 +0000 (10:10 -0700)
committerFangrui Song <maskray@google.com>
Wed, 8 Jul 2020 17:15:16 +0000 (10:15 -0700)
... 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

lld/ELF/Config.h
lld/ELF/Driver.cpp
lld/ELF/InputSection.cpp
lld/docs/ld.lld.1
lld/test/ELF/dead-reloc-in-nonalloc.s [new file with mode: 0644]
lld/test/ELF/debug-dead-reloc.s

index 9486ef2..e74a4a0 100644 (file)
@@ -145,6 +145,7 @@ struct Configuration {
   bool checkSections;
   bool compressDebugSections;
   bool cref;
+  std::vector<std::pair<llvm::GlobPattern, uint64_t>> deadRelocInNonAlloc;
   bool defineCommon;
   bool demangle = true;
   bool dependentLibraries;
index 4e02507..301f113 100644 (file)
@@ -444,6 +444,7 @@ static bool isKnownZFlag(StringRef s) {
          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 @@ static void readConfigs(opt::InputArgList &args) {
   config->zText = getZFlag(args, "text", "notext", true);
   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.
   if (auto *arg = args.getLastArg(OPT_plugin_opt_mcpu_eq))
     parseClangOption(saver.save("-mcpu=" + StringRef(arg->getValue())),
index fa7c0fb..7a7ebd9 100644 (file)
@@ -857,6 +857,12 @@ void InputSection::relocateNonAlloc(uint8_t *buf, ArrayRef<RelTy> rels) {
   const bool isDebugLocOrRanges =
       isDebug && (name == ".debug_loc" || name == ".debug_ranges");
   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) {
     RelType type = rel.getType(config->isMips64EL);
@@ -907,7 +913,8 @@ void InputSection::relocateNonAlloc(uint8_t *buf, ArrayRef<RelTy> rels) {
       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 @@ void InputSection::relocateNonAlloc(uint8_t *buf, ArrayRef<RelTy> rels) {
       auto *ds = dyn_cast<Defined>(&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<bits>(*tombstone)
+                      : (isDebugLocOrRanges ? UINT64_MAX - 1 : UINT64_MAX);
+        target->relocateNoSym(bufLoc, type, value);
         continue;
       }
     }
index 3acc818..5edeaf8 100644 (file)
@@ -625,6 +625,13 @@ Use wrapper functions for symbol.
 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 (file)
index 0000000..00d3d2c
--- /dev/null
@@ -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
index 7e6dc8d..d784519 100644 (file)
 # 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"