diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -257,6 +257,8 @@ bool relocatable; bool relrGlibc = false; bool relrPackDynRelocs = false; + llvm::SmallVector, 0> + remapInputs; llvm::DenseSet saveTempsArgs; llvm::SmallVector, 0> shuffleSections; bool singleRoRx; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1077,6 +1077,31 @@ return arg == "none" || arg == "warning" || arg == "error"; } +// A remapping file contains a tab-separated pair: from, to. +static void readRemapInputsFile(StringRef filename) { + std::optional buffer = readFile(filename); + if (!buffer) + return; + SmallVector fields; + size_t lineno = 0; + for (StringRef line : args::getLines(*buffer)) { + fields.clear(); + line.split(fields, '\t'); + ++lineno; + if (fields.size() != 2) { + error(filename + ":" + Twine(lineno) + ": parse error"); + return; + } + // Try to parse pattern. + if (Expected pat = GlobPattern::create(fields[0])) + config->remapInputs.emplace_back(std::move(*pat), fields[1]); + else { + error(filename + ":" + Twine(lineno) + ": " + toString(pat.takeError())); + return; + } + } +} + // Initializes Config members by the command line options. static void readConfigs(opt::InputArgList &args) { errorHandler().verbose = args.hasArg(OPT_verbose); @@ -1563,6 +1588,19 @@ } else { error(Twine("cannot find version script ") + arg->getValue()); } + + for (opt::Arg *arg : args.filtered(OPT_remap_inputs)) { + StringRef value(arg->getValue()); + auto [pattern, toFile] = value.split(','); + if (toFile.empty()) + error(arg->getSpelling() + ": invalid argument '" + value + "'"); + else if (Expected pat = GlobPattern::create(pattern)) + config->remapInputs.emplace_back(std::move(*pat), toFile); + else + error(arg->getSpelling() + ": " + toString(pat.takeError())); + } + for (opt::Arg *arg : args.filtered(OPT_remap_inputs_file)) + readRemapInputsFile(arg->getValue()); } // Some Config members do not directly correspond to any particular diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -190,6 +190,16 @@ std::optional elf::readFile(StringRef path) { llvm::TimeTraceScope timeScope("Load input files", path); + for (const auto &[pat, toFile] : config->remapInputs) + if (pat.match(path)) { + log("mapping " + Twine(path) + " to " + toFile); + path = toFile; +#ifdef _WIN32 + if (path == "/dev/null") + path = "NUL"; +#endif + } + // The --chroot option changes our virtual root directory. // This is useful when you are dealing with files created by --reproduce. if (!config->chroot.empty() && path.startswith("/")) diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -359,6 +359,11 @@ "Enable global pointer relaxation", "Disable global pointer relaxation (default)">; +defm remap_inputs: EEq<"remap-inputs", "Remap inputs matching given pattern to another input">; + +defm remap_inputs_file: EEq<"remap-inputs-file", "Remap inputs based on patterns in file">, + MetaVarName<"">; + defm reproduce: EEq<"reproduce", "Write tar file containing inputs and command to reproduce link">; 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 @@ -495,6 +495,22 @@ .Fl -push-state. .It Fl --relax-gp Enable global pointer relaxation for RISC-V. +.It Fl -remap-inputs Ns = Ns Ar value +Remap inputs matching given pattern to another input. +The provided +.Ar value +is of the format +.Cm from-pattern,to +where inputs matching the +.Cm from-pattern +are mapped to the +.Cm to +file. +.It Fl -remap-inputs-file Ns = Ns Ar file +Remap inputs based on patterns in +.Ar file . +Each line in the mapping file is of the format +.Cm from-patternto . .It Fl -relocatable , Fl r Create relocatable object file. .It Fl -reproduce Ns = Ns Ar path diff --git a/lld/test/ELF/remap-inputs-file.s b/lld/test/ELF/remap-inputs-file.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/remap-inputs-file.s @@ -0,0 +1,55 @@ +# REQUIRES: x86 + +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=x86_64 a.s -o a.o +# RUN: llvm-as b.ll -o b.o +# RUN: llvm-mc -filetype=obj -triple=x86_64 c.s -o c.o && llvm-ar rc c.a c.o +# RUN: llvm-mc -filetype=obj -triple=x86_64 d.s -o d.o && ld.lld -shared -soname=d d.o -o d.so +# RUN: ld.lld --remap-inputs-file=1.map --reproduce=repro.tar aa.o bb.bc cc.a dd.so empty -o 1 +# RUN: tar tf repro.tar | FileCheck %s --check-prefix=REPRO + +# REPRO: 1.map +# REPRO-NEXT: a.o +# REPRO-NEXT: b.o +# REPRO-NEXT: c.a +# REPRO-NEXT: d.so + +# RUN: not ld.lld --remap-inputs-file=err.map --reproduce=repro.tar aa.o bb.bc -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR + +# ERR: error: err.map:2: parse error +# ERR-NEXT: error: cannot open bb.bc: {{.*}} + +#--- a.s +.globl _start +_start: + call b + call c + call d + +#--- b.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +define void @b() { + ret void +} + +#--- c.s +.globl c +c: + +#--- d.s +.globl d +d: + +#--- 1.map +aa.o a.o +bb.bc b.o +cc.a c.a +*dd* d.so +empty /dev/null + +#--- err.map +aa.o a.o +bb.bc +cc.a diff --git a/lld/test/ELF/remap-inputs.s b/lld/test/ELF/remap-inputs.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/remap-inputs.s @@ -0,0 +1,43 @@ +// REQUIRES: x86 + +// RUN: rm -rf %t && split-file %s %t && cd %t +// RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux foo.s -o foo.o +// RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux bar.s -o bar.o +// RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux baz.s -o baz.o +// RUN: llvm-ar rcs bar.a bar.o + +// RUN: ld.lld --remap-inputs='NO-MATCH,/dev/null' --remap-inputs='*foo*,/dev/null' --whole-archive foo.o bar.a -o exe +// RUN: llvm-readelf -s exe | FileCheck --check-prefix=OBJECT %s +// OBJECT-NOT: foo +// OBJECT: bar +// OBJECT-NOT: baz + +// RUN: ld.lld --remap-inputs='*bar*,/dev/null' --whole-archive foo.o bar.a -o exe +// RUN: llvm-readelf -s exe | FileCheck --check-prefix=ARCHIVE %s +// ARCHIVE: foo +// ARCHIVE-NOT: bar +// ARCHIVE-NOT: baz + +// RUN: ld.lld --remap-inputs='*bar*,baz.o' --whole-archive foo.o bar.a -o exe +// RUN: llvm-readelf -s exe | FileCheck --check-prefix=REMAP %s +// REMAP: foo +// REMAP-NOT: bar +// REMAP: baz + +// RUN: not ld.lld --remap-inputs='abc' 2>&1 | FileCheck --check-prefix=ERR %s +// ERR: error: --remap-inputs: invalid argument 'abc' + +#--- foo.s +.globl foo +foo: + nop + +#--- bar.s +.globl bar +bar: + nop + +#--- baz.s +.globl baz +baz: + nop