[lld/mac] Add --reproduce option
This adds support for ld.lld's --reproduce / lld-link's /reproduce: flag to the MachO port. This flag can be added to a link command to make the link write a tar file containing all inputs to the link and a response file containing the link command. This can be used to reproduce the link on another machine, which is useful for sharing bug report inputs or performance test loads. Since the linker is usually called through the clang driver and adding linker flags can be a bit cumbersome, setting the env var `LLD_REPRODUCE=foo.tar` triggers the feature as well. The file response.txt in the archive can be used with `ld64.lld.darwinnew $(cat response.txt)` as long as the contents are smaller than the command-line limit, or with `ld64.lld.darwinnew @response.txt` once D92149 is in. The support in this patch is sufficient to create a tar file for Chromium's base_unittests that can link after unpacking on a different machine. Differential Revision: https://reviews.llvm.org/D92274
This commit is contained in:
parent
25d54abca5
commit
83e60f5a55
|
@ -54,7 +54,12 @@ std::string lld::toString(const opt::Arg &arg) {
|
|||
std::string k = std::string(arg.getSpelling());
|
||||
if (arg.getNumValues() == 0)
|
||||
return k;
|
||||
std::string v = quote(arg.getValue());
|
||||
std::string v;
|
||||
for (size_t i = 0; i < arg.getNumValues(); ++i) {
|
||||
if (i > 0)
|
||||
v.push_back(' ');
|
||||
v += quote(arg.getValue(i));
|
||||
}
|
||||
if (arg.getOption().getRenderStyle() == opt::Option::RenderJoinedStyle)
|
||||
return k + v;
|
||||
return k + " " + v;
|
||||
|
|
|
@ -184,7 +184,7 @@ std::string elf::createResponseFile(const opt::InputArgList &args) {
|
|||
// fail because the archive we are creating doesn't contain empty
|
||||
// directories for the output path (-o doesn't create directories).
|
||||
// Strip directories to prevent the issue.
|
||||
os << "-o " << quote(sys::path::filename(arg->getValue())) << "\n";
|
||||
os << "-o " << quote(path::filename(arg->getValue())) << "\n";
|
||||
break;
|
||||
case OPT_dynamic_list:
|
||||
case OPT_library_path:
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "llvm/Support/Host.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/TarWriter.h"
|
||||
#include "llvm/Support/TargetSelect.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -548,6 +549,12 @@ static void warnIfUnimplementedOption(const opt::Option &opt) {
|
|||
}
|
||||
}
|
||||
|
||||
static const char *getReproduceOption(opt::InputArgList &args) {
|
||||
if (auto *arg = args.getLastArg(OPT_reproduce))
|
||||
return arg->getValue();
|
||||
return getenv("LLD_REPRODUCE");
|
||||
}
|
||||
|
||||
bool macho::link(llvm::ArrayRef<const char *> argsArr, bool canExitEarly,
|
||||
raw_ostream &stdoutOS, raw_ostream &stderrOS) {
|
||||
lld::stdoutOS = &stdoutOS;
|
||||
|
@ -569,6 +576,20 @@ bool macho::link(llvm::ArrayRef<const char *> argsArr, bool canExitEarly,
|
|||
return true;
|
||||
}
|
||||
|
||||
if (const char *path = getReproduceOption(args)) {
|
||||
// Note that --reproduce is a debug option so you can ignore it
|
||||
// if you are trying to understand the whole picture of the code.
|
||||
Expected<std::unique_ptr<TarWriter>> errOrWriter =
|
||||
TarWriter::create(path, path::stem(path));
|
||||
if (errOrWriter) {
|
||||
tar = std::move(*errOrWriter);
|
||||
tar->append("response.txt", createResponseFile(args));
|
||||
tar->append("version.txt", getLLDVersion() + "\n");
|
||||
} else {
|
||||
error("--reproduce: " + toString(errOrWriter.takeError()));
|
||||
}
|
||||
}
|
||||
|
||||
config = make<Configuration>();
|
||||
symtab = make<SymbolTable>();
|
||||
target = createTargetInfo(args);
|
||||
|
|
|
@ -35,6 +35,8 @@ enum {
|
|||
#undef OPTION
|
||||
};
|
||||
|
||||
std::string createResponseFile(const llvm::opt::InputArgList &args);
|
||||
|
||||
// Check for both libfoo.dylib and libfoo.tbd (in that order).
|
||||
llvm::Optional<std::string> resolveDylibPath(llvm::StringRef path);
|
||||
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
#include "Driver.h"
|
||||
#include "InputFiles.h"
|
||||
|
||||
#include "lld/Common/Args.h"
|
||||
#include "lld/Common/ErrorHandler.h"
|
||||
#include "lld/Common/Memory.h"
|
||||
#include "lld/Common/Reproduce.h"
|
||||
#include "llvm/Option/Arg.h"
|
||||
#include "llvm/Option/ArgList.h"
|
||||
#include "llvm/Option/Option.h"
|
||||
|
@ -96,6 +98,55 @@ void MachOOptTable::printHelp(const char *argv0, bool showHidden) const {
|
|||
lld::outs() << "\n";
|
||||
}
|
||||
|
||||
static std::string rewritePath(StringRef s) {
|
||||
if (fs::exists(s))
|
||||
return relativeToRoot(s);
|
||||
return std::string(s);
|
||||
}
|
||||
|
||||
// Reconstructs command line arguments so that so that you can re-run
|
||||
// the same command with the same inputs. This is for --reproduce.
|
||||
std::string macho::createResponseFile(const opt::InputArgList &args) {
|
||||
SmallString<0> data;
|
||||
raw_svector_ostream os(data);
|
||||
|
||||
// Copy the command line to the output while rewriting paths.
|
||||
for (auto *arg : args) {
|
||||
switch (arg->getOption().getID()) {
|
||||
case OPT_reproduce:
|
||||
break;
|
||||
case OPT_INPUT:
|
||||
os << quote(rewritePath(arg->getValue())) << "\n";
|
||||
break;
|
||||
case OPT_o:
|
||||
os << "-o " << quote(path::filename(arg->getValue())) << "\n";
|
||||
break;
|
||||
case OPT_filelist:
|
||||
if (Optional<MemoryBufferRef> buffer = readFile(arg->getValue()))
|
||||
for (StringRef path : args::getLines(*buffer))
|
||||
os << quote(rewritePath(path)) << "\n";
|
||||
break;
|
||||
case OPT_force_load:
|
||||
case OPT_rpath:
|
||||
case OPT_syslibroot:
|
||||
case OPT_F:
|
||||
case OPT_L:
|
||||
case OPT_order_file:
|
||||
os << arg->getSpelling() << " " << quote(rewritePath(arg->getValue()))
|
||||
<< "\n";
|
||||
break;
|
||||
case OPT_sectcreate:
|
||||
os << arg->getSpelling() << " " << quote(arg->getValue(0)) << " "
|
||||
<< quote(arg->getValue(1)) << " "
|
||||
<< quote(rewritePath(arg->getValue(2))) << "\n";
|
||||
break;
|
||||
default:
|
||||
os << toString(*arg) << "\n";
|
||||
}
|
||||
}
|
||||
return std::string(data.str());
|
||||
}
|
||||
|
||||
Optional<std::string> macho::resolveDylibPath(StringRef path) {
|
||||
// TODO: if a tbd and dylib are both present, we should check to make sure
|
||||
// they are consistent.
|
||||
|
|
|
@ -56,12 +56,14 @@
|
|||
|
||||
#include "lld/Common/ErrorHandler.h"
|
||||
#include "lld/Common/Memory.h"
|
||||
#include "lld/Common/Reproduce.h"
|
||||
#include "llvm/ADT/iterator.h"
|
||||
#include "llvm/BinaryFormat/MachO.h"
|
||||
#include "llvm/LTO/LTO.h"
|
||||
#include "llvm/Support/Endian.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/TarWriter.h"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace llvm::MachO;
|
||||
|
@ -71,6 +73,7 @@ using namespace lld;
|
|||
using namespace lld::macho;
|
||||
|
||||
std::vector<InputFile *> macho::inputFiles;
|
||||
std::unique_ptr<TarWriter> macho::tar;
|
||||
|
||||
// Open a given file path and return it as a memory-mapped file.
|
||||
Optional<MemoryBufferRef> macho::readFile(StringRef path) {
|
||||
|
@ -88,8 +91,11 @@ Optional<MemoryBufferRef> macho::readFile(StringRef path) {
|
|||
// If this is a regular non-fat file, return it.
|
||||
const char *buf = mbref.getBufferStart();
|
||||
auto *hdr = reinterpret_cast<const MachO::fat_header *>(buf);
|
||||
if (read32be(&hdr->magic) != MachO::FAT_MAGIC)
|
||||
if (read32be(&hdr->magic) != MachO::FAT_MAGIC) {
|
||||
if (tar)
|
||||
tar->append(relativeToRoot(path), mbref.getBuffer());
|
||||
return mbref;
|
||||
}
|
||||
|
||||
// Object files and archive files may be fat files, which contains
|
||||
// multiple real files for different CPU ISAs. Here, we search for a
|
||||
|
@ -112,6 +118,8 @@ Optional<MemoryBufferRef> macho::readFile(StringRef path) {
|
|||
uint32_t size = read32be(&arch[i].size);
|
||||
if (offset + size > mbref.getBufferSize())
|
||||
error(path + ": slice extends beyond end of file");
|
||||
if (tar)
|
||||
tar->append(relativeToRoot(path), mbref.getBuffer());
|
||||
return MemoryBufferRef(StringRef(buf + offset, size), path.copy(bAlloc));
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ namespace llvm {
|
|||
namespace lto {
|
||||
class InputFile;
|
||||
} // namespace lto
|
||||
class TarWriter;
|
||||
} // namespace llvm
|
||||
|
||||
namespace lld {
|
||||
|
@ -36,6 +37,10 @@ class InputSection;
|
|||
class Symbol;
|
||||
struct Reloc;
|
||||
|
||||
// If --reproduce option is given, all input files are written
|
||||
// to this tar archive.
|
||||
extern std::unique_ptr<llvm::TarWriter> tar;
|
||||
|
||||
// If .subsections_via_symbols is set, each InputSection will be split along
|
||||
// symbol boundaries. The keys of a SubsectionMap represent the offsets of
|
||||
// each subsection from the start of the original pre-split InputSection.
|
||||
|
|
|
@ -14,6 +14,10 @@ def no_color_diagnostics: Flag<["--"], "no-color-diagnostics">,
|
|||
def color_diagnostics_eq: Joined<["--"], "color-diagnostics=">,
|
||||
HelpText<"Use colors in diagnostics (default: auto)">,
|
||||
MetaVarName<"[auto,always,never]">;
|
||||
def reproduce: Separate<["--"], "reproduce">;
|
||||
def reproduce_eq: Joined<["--"], "reproduce=">,
|
||||
Alias<!cast<Separate>(reproduce)>,
|
||||
HelpText<"Write tar file containing inputs and command to reproduce link">;
|
||||
|
||||
|
||||
// This is a complete Options.td compiled from Apple's ld(1) manpage
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# REQUIRES: x86
|
||||
|
||||
# RUN: rm -rf %t.dir
|
||||
# RUN: mkdir -p %t.dir/build1
|
||||
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %s -o %t.dir/build1/foo.o
|
||||
# RUN: cd %t.dir
|
||||
# RUN: %lld -platform_version macos 10.10.0 11.0 build1/foo.o -o bar --reproduce repro1.tar
|
||||
# RUN: tar xOf repro1.tar repro1/%:t.dir/build1/foo.o > build1-foo.o
|
||||
# RUN: cmp build1/foo.o build1-foo.o
|
||||
|
||||
# RUN: tar xf repro1.tar repro1/response.txt repro1/version.txt
|
||||
# RUN: FileCheck %s --check-prefix=RSP1 < repro1/response.txt
|
||||
# RSP1: {{^}}-platform_version macos 10.10.0 11.0{{$}}
|
||||
# RSP1-NOT: {{^}}repro1{{[/\\]}}
|
||||
# RSP1-NEXT: {{[/\\]}}foo.o
|
||||
# RSP1-NEXT: -o bar
|
||||
# RSP1-NOT: --reproduce
|
||||
|
||||
# RUN: FileCheck %s --check-prefix=VERSION < repro1/version.txt
|
||||
# VERSION: LLD
|
||||
|
||||
# RUN: mkdir -p %t.dir/build2/a/b/c
|
||||
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %s -o %t.dir/build2/foo.o
|
||||
# RUN: cd %t.dir/build2/a/b/c
|
||||
# RUN: echo ./../../../foo.o > %t.dir/build2/filelist
|
||||
# RUN: env LLD_REPRODUCE=repro2.tar %lld -filelist %t.dir/build2/filelist -o /dev/null
|
||||
# RUN: tar xOf repro2.tar repro2/%:t.dir/build2/foo.o > build2-foo.o
|
||||
# RUN: cmp %t.dir/build2/foo.o build2-foo.o
|
||||
|
||||
# RUN: tar xf repro2.tar repro2/response.txt repro2/version.txt
|
||||
# RUN: FileCheck %s --check-prefix=RSP2 < repro2/response.txt
|
||||
# RSP2-NOT: {{^}}repro2{{[/\\]}}
|
||||
# RSP2: {{[/\\]}}foo.o
|
||||
|
||||
.globl _main
|
||||
_main:
|
||||
ret
|
Loading…
Reference in New Issue