Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 46 additions & 11 deletions lld/COFF/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,26 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
}
}

void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) {
void LinkerDriver::handleReproFile(StringRef path, InputType inputType) {
if (!reproFile)
return;

*reproFile << '"';
if (inputType == InputType::DefaultLib)
*reproFile << "/defaultlib:";
else if (inputType == InputType::WholeArchive)
*reproFile << "/wholearchive:";

SmallString<128> absPath = path;
std::error_code ec = sys::fs::make_absolute(absPath);
if (ec)
Err(ctx) << "cannot find absolute path for reproFile for " << absPath
<< ": " << ec.message();
sys::path::remove_dots(absPath, true);
*reproFile << absPath << "\"\n";
}

void LinkerDriver::enqueuePath(StringRef path, bool lazy, InputType pathType) {
auto future = std::make_shared<std::future<MBErrPair>>(
createFutureForFile(std::string(path)));
std::string pathStr = std::string(path);
Expand Down Expand Up @@ -356,8 +375,11 @@ void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) {
Err(ctx) << msg;
else
Err(ctx) << msg << "; did you mean '" << nearest << "'";
} else
ctx.driver.addBuffer(std::move(mb), wholeArchive, lazy);
} else {
handleReproFile(pathStr, pathType);
ctx.driver.addBuffer(std::move(mb), pathType == InputType::WholeArchive,
lazy);
}
});
}

Expand Down Expand Up @@ -405,10 +427,9 @@ void LinkerDriver::enqueueArchiveMember(const Archive::Child &c,
StringRef parentName) {

auto reportBufferError = [=](Error &&e) {
StringRef childName =
CHECK(c.getName(),
"could not get child name for archive " + parentName +
" while loading symbol " + toCOFFString(ctx, sym));
StringRef childName = CHECK(
c.getName(), "could not get child name for archive " + parentName +
" while loading symbol " + toCOFFString(ctx, sym));
Fatal(ctx) << "could not get the buffer for the member defining symbol "
<< &sym << ": " << parentName << "(" << childName
<< "): " << std::move(e);
Expand Down Expand Up @@ -514,7 +535,7 @@ void LinkerDriver::parseDirectives(InputFile *file) {
break;
case OPT_defaultlib:
if (std::optional<StringRef> path = findLibIfNew(arg->getValue()))
enqueuePath(*path, false, false);
enqueuePath(*path, false, InputType::DefaultLib);
break;
case OPT_entry:
if (!arg->getValue()[0])
Expand Down Expand Up @@ -1607,6 +1628,15 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
}
}
}
// Handle /linkreprofullpathrsp
if (auto *arg = args.getLastArg(OPT_linkreprofullpathrsp)) {
std::error_code ec;
reproFile = std::make_unique<raw_fd_ostream>(arg->getValue(), ec);
if (ec) {
Err(ctx) << "cannot open " << arg->getValue() << ": " << ec.message();
reproFile.reset();
}
}

if (!args.hasArg(OPT_INPUT, OPT_wholearchive_file)) {
if (args.hasArg(OPT_deffile))
Expand Down Expand Up @@ -2245,11 +2275,13 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
break;
case OPT_wholearchive_file:
if (std::optional<StringRef> path = findFileIfNew(arg->getValue()))
enqueuePath(*path, true, inLib);
enqueuePath(*path, inLib, InputType::WholeArchive);
break;
case OPT_INPUT:
if (std::optional<StringRef> path = findFileIfNew(arg->getValue()))
enqueuePath(*path, isWholeArchive(*path), inLib);
enqueuePath(*path, inLib,
isWholeArchive(*path) ? InputType::WholeArchive
: InputType::Direct);
break;
default:
// Ignore other options.
Expand Down Expand Up @@ -2289,7 +2321,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
// addWinSysRootLibSearchPaths(), which is why they are in a separate loop.
for (auto *arg : args.filtered(OPT_defaultlib))
if (std::optional<StringRef> path = findLibIfNew(arg->getValue()))
enqueuePath(*path, false, false);
enqueuePath(*path, false, InputType::DefaultLib);
run();
if (errorCount())
return;
Expand Down Expand Up @@ -2856,6 +2888,9 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
if (config->showTiming)
ctx.rootTimer.print();

// Clean up /linkreprofullpathrsp file
reproFile.reset();

if (config->timeTraceEnabled) {
// Manually stop the topmost "COFF link" scope, since we're shutting down.
timeTraceProfilerEnd();
Expand Down
13 changes: 11 additions & 2 deletions lld/COFF/Driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,13 @@ class LinkerDriver {
void enqueueArchiveMember(const Archive::Child &c, const Archive::Symbol &sym,
StringRef parentName);

void enqueuePDB(StringRef Path) { enqueuePath(Path, false, false); }
enum class InputType { Direct, DefaultLib, WholeArchive };
void enqueuePDB(StringRef Path) { enqueuePath(Path, false); }

MemoryBufferRef takeBuffer(std::unique_ptr<MemoryBuffer> mb);

void enqueuePath(StringRef path, bool wholeArchive, bool lazy);
void enqueuePath(StringRef path, bool lazy,
InputType inputType = InputType::Direct);

// Returns a list of chunks of selected symbols.
std::vector<Chunk *> getChunks() const;
Expand Down Expand Up @@ -138,6 +140,10 @@ class LinkerDriver {
//
std::string getImportName(bool asLib);

// Write fullly resolved path to repro file if /linkreprofullpathrsp
// is specified.
void handleReproFile(StringRef path, InputType inputType);

void createImportLibrary(bool asLib);

// Used by the resolver to parse .drectve section contents.
Expand Down Expand Up @@ -193,6 +199,9 @@ class LinkerDriver {
int sdkMajor = 0;
llvm::SmallString<128> windowsSdkLibPath;

// For linkreprofullpathrsp
std::unique_ptr<llvm::raw_fd_ostream> reproFile = nullptr;

// Functions below this line are defined in DriverUtils.cpp.

void printHelp(const char *argv0);
Expand Down
3 changes: 3 additions & 0 deletions lld/COFF/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ def libpath : P<"libpath", "Additional library search path">;
def linkrepro : Joined<["/", "-", "/?", "-?"], "linkrepro:">,
MetaVarName<"directory">,
HelpText<"Write repro.tar containing inputs and command to reproduce link">;
def linkreprofullpathrsp : Joined<["/", "-", "/?", "-?"], "linkreprofullpathrsp:">,
MetaVarName<"directory">,
HelpText<"Write .rsp file containing inputs used to link with full paths">;
def lldignoreenv : F<"lldignoreenv">,
HelpText<"Ignore environment variables like %LIB%">;
def lldltocache : P<"lldltocache",
Expand Down
43 changes: 43 additions & 0 deletions lld/test/COFF/linkreprofullpathrsp.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# REQUIRES: x86

# RUN: rm -rf %t.dir %t.obj
# RUN: yaml2obj %p/Inputs/hello32.yaml -o %t.obj
# RUN: yaml2obj %p/Inputs/empty.yaml -o %t.archive.obj
# RUN: rm -f %t.archive.lib
# RUN: llvm-ar rcs %t.archive.lib %t.archive.obj
# RUN: llvm-pdbutil yaml2pdb %S/Inputs/pdb-type-server-simple-ts.yaml -pdb %t.pdb


Test link.exe-style /linkreprofullpathrsp: flag.
# RUN: mkdir -p %t.dir/build1
# RUN: cd %t.dir/build1
# RUN: lld-link %t.obj %p/Inputs/std32.lib /subsystem:console /defaultlib:%p/Inputs/library.lib \
# RUN: /libpath:%p/Inputs /defaultlib:std64.lib ret42.lib /entry:main@0 /linkreprofullpathrsp:%t.rsp \
# RUN: /wholearchive:%t.archive.lib /out:%t.exe %t.pdb
# RUN: FileCheck %s --check-prefix=RSP -DT=%t -DP=%p < %t.rsp

# RUN: lld-link @%t.rsp /out:%t2.exe /entry:main@0
# RUN: diff %t.exe %t2.exe

# RSP: "[[T]].obj"
# RSP-NEXT: "[[P]]{{[/\\]}}Inputs{{[/\\]}}std32.lib"
# RSP-NEXT: "[[P]]{{[/\\]}}Inputs{{[/\\]}}ret42.lib"
# RSP-NEXT: "/wholearchive:[[T]].archive.lib"
# RSP-NEXT: "[[T]].pdb"
# RSP-NEXT: "/defaultlib:[[P]]{{[/\\]}}Inputs{{[/\\]}}library.lib"
# RSP-NEXT: "/defaultlib:[[P]]{{[/\\]}}Inputs{{[/\\]}}std64.lib"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably exercice the link command a second time, by using the generated .rsp to ensure it works as expected. It wouldn't be bad if we checked afterwards some expected outcomes, such as the presence of symbols in the binary as well as the presence of debug info (and maybe ensure no warnings are generated).

Copy link
Member Author

@DavidTruby DavidTruby Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any advice on how I could go about doing that? I'm not really a linker expert and this is my first LLD patch in a long while so I'm kinda learning as I'm going 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you do lld-link input1.obj input2.obj /listreprofullpathrsp:file.rsp then in a second execution you could do lld-link @file.rsp which should yield the same outcomes (same warnings if any, same outputs).

As for warnings, you can do RSP-NOT: warning to ensure the absence of warnings. Same thing for debug info, assuming that /debug is used during the link phase, you can use llvm-pdbutil dump -all %t.pdb | FileCheck %s ... to ascertain that same important debug information is there. Usually you can run that llvm-pdbutil command manually on your local machine, and copy-paste in the test a few lines that should be important.

#--- drectve.s
.section .drectve, "yn"
.ascii "/defaultlib:std32"

#--- archive.s
.text
.intel_syntax noprefix
.globl exportfn3
.p2align 4
exportfn3:
ret

.section .drectve,"yni"
.ascii " /EXPORT:exportfn3"