llvm-project/lld/COFF/DriverUtils.cpp
Jacek Caban dcc71f22ca
[LLD][COFF] Add support for ARM64X same-address thunks (#151255)
Fixes MSVC CRT thread-local constructors support on hybrid ARM64X
targets.

`-arm64xsameaddress` is an undocumented option that ensures the
specified function has the same address in both native and EC views of
hybrid images. To achieve this, the linker emits additional thunks and
replaces the symbols
of those functions with the thunk symbol (the same thunk is used in both
views). The thunk code jumps to the native function (similar to range
extension thunks), but additional ARM64X relocations are emitted to
replace the target with the EC function in the EC view.

MSVC appears to generate thunks even for non-hybrid ARM64EC images. As a
side effect, the native symbol is pulled in. Since this is used in the
CRT for thread-local constructors, it results in the image containing
unnecessary native code. Because these thunks do not appear to be useful
in that context, we limit this behavior to actual hybrid targets. This
may change if compatibility requires it.

The tricky part is that thunks should be skipped if the symbol is not
live in either view, and symbol replacement must be reflected in weak
aliases. This requires thunk generation to happen before resolving weak
aliases but after the GC pass. To enable this, the `markLive` call was
moved earlier, and the final weak alias resolution was postponed until
afterward. This requires more code to be aware of weak aliases, which
previously could assume they were already resolved.
2025-07-31 13:17:36 +02:00

905 lines
31 KiB
C++

//===- DriverUtils.cpp ----------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file contains utility functions for the driver. Because there
// are so many small functions, we created this separate file to make
// Driver.cpp less cluttered.
//
//===----------------------------------------------------------------------===//
#include "COFFLinkerContext.h"
#include "Driver.h"
#include "Symbols.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/BinaryFormat/COFF.h"
#include "llvm/IR/Mangler.h"
#include "llvm/Object/COFF.h"
#include "llvm/Object/WindowsResource.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/WindowsManifest/WindowsManifestMerger.h"
#include <memory>
#include <optional>
using namespace llvm::COFF;
using namespace llvm::object;
using namespace llvm::opt;
using namespace llvm;
using llvm::sys::Process;
namespace lld {
namespace coff {
namespace {
const uint16_t SUBLANG_ENGLISH_US = 0x0409;
const uint16_t RT_MANIFEST = 24;
class Executor {
public:
explicit Executor(StringRef s) : prog(saver().save(s)) {}
void add(StringRef s) { args.push_back(saver().save(s)); }
void add(std::string &s) { args.push_back(saver().save(s)); }
void add(Twine s) { args.push_back(saver().save(s)); }
void add(const char *s) { args.push_back(saver().save(s)); }
void run() {
ErrorOr<std::string> exeOrErr = sys::findProgramByName(prog);
if (auto ec = exeOrErr.getError())
fatal("unable to find " + prog + " in PATH: " + ec.message());
StringRef exe = saver().save(*exeOrErr);
args.insert(args.begin(), exe);
if (sys::ExecuteAndWait(args[0], args) != 0)
fatal("ExecuteAndWait failed: " +
llvm::join(args.begin(), args.end(), " "));
}
private:
StringRef prog;
std::vector<StringRef> args;
};
} // anonymous namespace
// Parses a string in the form of "<integer>[,<integer>]".
void LinkerDriver::parseNumbers(StringRef arg, uint64_t *addr, uint64_t *size) {
auto [s1, s2] = arg.split(',');
if (s1.getAsInteger(0, *addr))
Fatal(ctx) << "invalid number: " << s1;
if (size && !s2.empty() && s2.getAsInteger(0, *size))
Fatal(ctx) << "invalid number: " << s2;
}
// Parses a string in the form of "<integer>[.<integer>]".
// If second number is not present, Minor is set to 0.
void LinkerDriver::parseVersion(StringRef arg, uint32_t *major,
uint32_t *minor) {
auto [s1, s2] = arg.split('.');
if (s1.getAsInteger(10, *major))
Fatal(ctx) << "invalid number: " << s1;
*minor = 0;
if (!s2.empty() && s2.getAsInteger(10, *minor))
Fatal(ctx) << "invalid number: " << s2;
}
void LinkerDriver::parseGuard(StringRef fullArg) {
SmallVector<StringRef, 1> splitArgs;
fullArg.split(splitArgs, ",");
for (StringRef arg : splitArgs) {
if (arg.equals_insensitive("no"))
ctx.config.guardCF = GuardCFLevel::Off;
else if (arg.equals_insensitive("nolongjmp"))
ctx.config.guardCF &= ~GuardCFLevel::LongJmp;
else if (arg.equals_insensitive("noehcont"))
ctx.config.guardCF &= ~GuardCFLevel::EHCont;
else if (arg.equals_insensitive("cf") || arg.equals_insensitive("longjmp"))
ctx.config.guardCF |= GuardCFLevel::CF | GuardCFLevel::LongJmp;
else if (arg.equals_insensitive("ehcont"))
ctx.config.guardCF |= GuardCFLevel::CF | GuardCFLevel::EHCont;
else
Fatal(ctx) << "invalid argument to /guard: " << arg;
}
}
// Parses a string in the form of "<subsystem>[,<integer>[.<integer>]]".
void LinkerDriver::parseSubsystem(StringRef arg, WindowsSubsystem *sys,
uint32_t *major, uint32_t *minor,
bool *gotVersion) {
auto [sysStr, ver] = arg.split(',');
std::string sysStrLower = sysStr.lower();
*sys = StringSwitch<WindowsSubsystem>(sysStrLower)
.Case("boot_application", IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION)
.Case("console", IMAGE_SUBSYSTEM_WINDOWS_CUI)
.Case("default", IMAGE_SUBSYSTEM_UNKNOWN)
.Case("efi_application", IMAGE_SUBSYSTEM_EFI_APPLICATION)
.Case("efi_boot_service_driver", IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER)
.Case("efi_rom", IMAGE_SUBSYSTEM_EFI_ROM)
.Case("efi_runtime_driver", IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER)
.Case("native", IMAGE_SUBSYSTEM_NATIVE)
.Case("posix", IMAGE_SUBSYSTEM_POSIX_CUI)
.Case("windows", IMAGE_SUBSYSTEM_WINDOWS_GUI)
.Default(IMAGE_SUBSYSTEM_UNKNOWN);
if (*sys == IMAGE_SUBSYSTEM_UNKNOWN && sysStrLower != "default")
Fatal(ctx) << "unknown subsystem: " << sysStr;
if (!ver.empty())
parseVersion(ver, major, minor);
if (gotVersion)
*gotVersion = !ver.empty();
}
// Parse a string of the form of "<from>=<to>".
// Results are directly written to Config.
void LinkerDriver::parseMerge(StringRef s) {
auto [from, to] = s.split('=');
if (from.empty() || to.empty())
Fatal(ctx) << "/merge: invalid argument: " << s;
if (from == ".rsrc" || to == ".rsrc")
Fatal(ctx) << "/merge: cannot merge '.rsrc' with any section";
if (from == ".reloc" || to == ".reloc")
Fatal(ctx) << "/merge: cannot merge '.reloc' with any section";
auto pair = ctx.config.merge.insert(std::make_pair(from, to));
bool inserted = pair.second;
if (!inserted) {
StringRef existing = pair.first->second;
if (existing != to)
Warn(ctx) << s << ": already merged into " << existing;
}
}
void LinkerDriver::parsePDBPageSize(StringRef s) {
int v;
if (s.getAsInteger(0, v)) {
Err(ctx) << "/pdbpagesize: invalid argument: " << s;
return;
}
if (v != 4096 && v != 8192 && v != 16384 && v != 32768) {
Err(ctx) << "/pdbpagesize: invalid argument: " << s;
return;
}
ctx.config.pdbPageSize = v;
}
static uint32_t parseSectionAttributes(COFFLinkerContext &ctx, StringRef s) {
uint32_t ret = 0;
for (char c : s.lower()) {
switch (c) {
case 'd':
ret |= IMAGE_SCN_MEM_DISCARDABLE;
break;
case 'e':
ret |= IMAGE_SCN_MEM_EXECUTE;
break;
case 'k':
ret |= IMAGE_SCN_MEM_NOT_CACHED;
break;
case 'p':
ret |= IMAGE_SCN_MEM_NOT_PAGED;
break;
case 'r':
ret |= IMAGE_SCN_MEM_READ;
break;
case 's':
ret |= IMAGE_SCN_MEM_SHARED;
break;
case 'w':
ret |= IMAGE_SCN_MEM_WRITE;
break;
default:
Fatal(ctx) << "/section: invalid argument: " << s;
}
}
return ret;
}
// Parses /section option argument.
void LinkerDriver::parseSection(StringRef s) {
auto [name, attrs] = s.split(',');
if (name.empty() || attrs.empty())
Fatal(ctx) << "/section: invalid argument: " << s;
ctx.config.section[name] = parseSectionAttributes(ctx, attrs);
}
void LinkerDriver::parseDosStub(StringRef path) {
std::unique_ptr<MemoryBuffer> stub =
CHECK(MemoryBuffer::getFile(path), "could not open " + path);
size_t bufferSize = stub->getBufferSize();
const char *bufferStart = stub->getBufferStart();
// MS link.exe compatibility:
// 1. stub must be greater than or equal to 64 bytes
// 2. stub must start with a valid dos signature 'MZ'
if (bufferSize < 64)
Err(ctx) << "/stub: stub must be greater than or equal to 64 bytes: "
<< path;
if (bufferStart[0] != 'M' || bufferStart[1] != 'Z')
Err(ctx) << "/stub: invalid DOS signature: " << path;
ctx.config.dosStub = std::move(stub);
}
// Parses /functionpadmin option argument.
void LinkerDriver::parseFunctionPadMin(llvm::opt::Arg *a) {
StringRef arg = a->getNumValues() ? a->getValue() : "";
if (!arg.empty()) {
// Optional padding in bytes is given.
if (arg.getAsInteger(0, ctx.config.functionPadMin))
Err(ctx) << "/functionpadmin: invalid argument: " << arg;
return;
}
// No optional argument given.
// Set default padding based on machine, similar to link.exe.
// There is no default padding for ARM platforms.
if (ctx.config.machine == I386) {
ctx.config.functionPadMin = 5;
} else if (ctx.config.machine == AMD64) {
ctx.config.functionPadMin = 6;
} else {
Err(ctx) << "/functionpadmin: invalid argument for this machine: " << arg;
}
}
// Parses /dependentloadflag option argument.
void LinkerDriver::parseDependentLoadFlags(llvm::opt::Arg *a) {
StringRef arg = a->getNumValues() ? a->getValue() : "";
if (!arg.empty()) {
if (arg.getAsInteger(0, ctx.config.dependentLoadFlags))
Err(ctx) << "/dependentloadflag: invalid argument: " << arg;
return;
}
// MSVC linker reports error "no argument specified", although MSDN describes
// argument as optional.
Err(ctx) << "/dependentloadflag: no argument specified";
}
// Parses a string in the form of "EMBED[,=<integer>]|NO".
// Results are directly written to
// Config.
void LinkerDriver::parseManifest(StringRef arg) {
if (arg.equals_insensitive("no")) {
ctx.config.manifest = Configuration::No;
return;
}
if (!arg.starts_with_insensitive("embed"))
Fatal(ctx) << "invalid option " << arg;
ctx.config.manifest = Configuration::Embed;
arg = arg.substr(strlen("embed"));
if (arg.empty())
return;
if (!arg.starts_with_insensitive(",id="))
Fatal(ctx) << "invalid option " << arg;
arg = arg.substr(strlen(",id="));
if (arg.getAsInteger(0, ctx.config.manifestID))
Fatal(ctx) << "invalid option " << arg;
}
// Parses a string in the form of "level=<string>|uiAccess=<string>|NO".
// Results are directly written to Config.
void LinkerDriver::parseManifestUAC(StringRef arg) {
if (arg.equals_insensitive("no")) {
ctx.config.manifestUAC = false;
return;
}
for (;;) {
arg = arg.ltrim();
if (arg.empty())
return;
if (arg.consume_front_insensitive("level=")) {
std::tie(ctx.config.manifestLevel, arg) = arg.split(" ");
continue;
}
if (arg.consume_front_insensitive("uiaccess=")) {
std::tie(ctx.config.manifestUIAccess, arg) = arg.split(" ");
continue;
}
Fatal(ctx) << "invalid option " << arg;
}
}
// Parses a string in the form of "cd|net[,(cd|net)]*"
// Results are directly written to Config.
void LinkerDriver::parseSwaprun(StringRef arg) {
do {
auto [swaprun, newArg] = arg.split(',');
if (swaprun.equals_insensitive("cd"))
ctx.config.swaprunCD = true;
else if (swaprun.equals_insensitive("net"))
ctx.config.swaprunNet = true;
else if (swaprun.empty())
Err(ctx) << "/swaprun: missing argument";
else
Err(ctx) << "/swaprun: invalid argument: " << swaprun;
// To catch trailing commas, e.g. `/spawrun:cd,`
if (newArg.empty() && arg.ends_with(","))
Err(ctx) << "/swaprun: missing argument";
arg = newArg;
} while (!arg.empty());
}
void LinkerDriver::parseSameAddress(StringRef arg) {
auto mangledName = getArm64ECMangledFunctionName(arg);
Symbol *sym = ctx.symtab.addUndefined(mangledName ? *mangledName : arg);
// MSVC appears to generate thunks even for non-hybrid ARM64EC images.
// As a side effect, the native symbol is pulled in. Since this is used
// in the CRT for thread-local constructors, it results in the image
// containing unnecessary native code. As these thunks don't appear to
// be useful, we limit this behavior to actual hybrid targets. This may
// change if compatibility becomes necessary.
if (ctx.config.machine != ARM64X)
return;
Symbol *nativeSym = ctx.hybridSymtab->addUndefined(arg);
ctx.config.sameAddresses.emplace_back(sym, nativeSym);
}
// An RAII temporary file class that automatically removes a temporary file.
namespace {
class TemporaryFile {
public:
TemporaryFile(COFFLinkerContext &ctx, StringRef prefix, StringRef extn,
StringRef contents = "")
: ctx(ctx) {
SmallString<128> s;
if (auto ec = sys::fs::createTemporaryFile("lld-" + prefix, extn, s))
Fatal(ctx) << "cannot create a temporary file: " << ec.message();
path = std::string(s);
if (!contents.empty()) {
std::error_code ec;
raw_fd_ostream os(path, ec, sys::fs::OF_None);
if (ec)
Fatal(ctx) << "failed to open " << path << ": " << ec.message();
os << contents;
}
}
TemporaryFile(TemporaryFile &&obj) noexcept : ctx(obj.ctx) {
std::swap(path, obj.path);
}
~TemporaryFile() {
if (path.empty())
return;
if (sys::fs::remove(path))
Fatal(ctx) << "failed to remove " << path;
}
// Returns a memory buffer of this temporary file.
// Note that this function does not leave the file open,
// so it is safe to remove the file immediately after this function
// is called (you cannot remove an opened file on Windows.)
std::unique_ptr<MemoryBuffer> getMemoryBuffer() {
// IsVolatile=true forces MemoryBuffer to not use mmap().
return CHECK(MemoryBuffer::getFile(path, /*IsText=*/false,
/*RequiresNullTerminator=*/false,
/*IsVolatile=*/true),
"could not open " + path);
}
COFFLinkerContext &ctx;
std::string path;
};
}
std::string LinkerDriver::createDefaultXml() {
std::string ret;
raw_string_ostream os(ret);
// Emit the XML. Note that we do *not* verify that the XML attributes are
// syntactically correct. This is intentional for link.exe compatibility.
os << "<?xml version=\"1.0\" standalone=\"yes\"?>\n"
<< "<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\"\n"
<< " manifestVersion=\"1.0\">\n";
if (ctx.config.manifestUAC) {
os << " <trustInfo>\n"
<< " <security>\n"
<< " <requestedPrivileges>\n"
<< " <requestedExecutionLevel level=" << ctx.config.manifestLevel
<< " uiAccess=" << ctx.config.manifestUIAccess << "/>\n"
<< " </requestedPrivileges>\n"
<< " </security>\n"
<< " </trustInfo>\n";
}
for (auto manifestDependency : ctx.config.manifestDependencies) {
os << " <dependency>\n"
<< " <dependentAssembly>\n"
<< " <assemblyIdentity " << manifestDependency << " />\n"
<< " </dependentAssembly>\n"
<< " </dependency>\n";
}
os << "</assembly>\n";
return ret;
}
std::string
LinkerDriver::createManifestXmlWithInternalMt(StringRef defaultXml) {
std::unique_ptr<MemoryBuffer> defaultXmlCopy =
MemoryBuffer::getMemBufferCopy(defaultXml);
windows_manifest::WindowsManifestMerger merger;
if (auto e = merger.merge(*defaultXmlCopy))
Fatal(ctx) << "internal manifest tool failed on default xml: "
<< toString(std::move(e));
for (StringRef filename : ctx.config.manifestInput) {
std::unique_ptr<MemoryBuffer> manifest =
check(MemoryBuffer::getFile(filename));
// Call takeBuffer to include in /reproduce: output if applicable.
if (auto e = merger.merge(takeBuffer(std::move(manifest))))
Fatal(ctx) << "internal manifest tool failed on file " << filename << ": "
<< toString(std::move(e));
}
return std::string(merger.getMergedManifest()->getBuffer());
}
std::string
LinkerDriver::createManifestXmlWithExternalMt(StringRef defaultXml) {
// Create the default manifest file as a temporary file.
TemporaryFile Default(ctx, "defaultxml", "manifest");
std::error_code ec;
raw_fd_ostream os(Default.path, ec, sys::fs::OF_TextWithCRLF);
if (ec)
Fatal(ctx) << "failed to open " << Default.path << ": " << ec.message();
os << defaultXml;
os.close();
// Merge user-supplied manifests if they are given. Since libxml2 is not
// enabled, we must shell out to Microsoft's mt.exe tool.
TemporaryFile user(ctx, "user", "manifest");
Executor e("mt.exe");
e.add("/manifest");
e.add(Default.path);
for (StringRef filename : ctx.config.manifestInput) {
e.add("/manifest");
e.add(filename);
// Manually add the file to the /reproduce: tar if needed.
if (tar)
if (auto mbOrErr = MemoryBuffer::getFile(filename))
takeBuffer(std::move(*mbOrErr));
}
e.add("/nologo");
e.add("/out:" + StringRef(user.path));
e.run();
return std::string(
CHECK(MemoryBuffer::getFile(user.path), "could not open " + user.path)
.get()
->getBuffer());
}
std::string LinkerDriver::createManifestXml() {
std::string defaultXml = createDefaultXml();
if (ctx.config.manifestInput.empty())
return defaultXml;
if (windows_manifest::isAvailable())
return createManifestXmlWithInternalMt(defaultXml);
return createManifestXmlWithExternalMt(defaultXml);
}
std::unique_ptr<WritableMemoryBuffer>
LinkerDriver::createMemoryBufferForManifestRes(size_t manifestSize) {
size_t resSize = alignTo(
object::WIN_RES_MAGIC_SIZE + object::WIN_RES_NULL_ENTRY_SIZE +
sizeof(object::WinResHeaderPrefix) + sizeof(object::WinResIDs) +
sizeof(object::WinResHeaderSuffix) + manifestSize,
object::WIN_RES_DATA_ALIGNMENT);
return WritableMemoryBuffer::getNewMemBuffer(resSize, ctx.config.outputFile +
".manifest.res");
}
static void writeResFileHeader(char *&buf) {
memcpy(buf, COFF::WinResMagic, sizeof(COFF::WinResMagic));
buf += sizeof(COFF::WinResMagic);
memset(buf, 0, object::WIN_RES_NULL_ENTRY_SIZE);
buf += object::WIN_RES_NULL_ENTRY_SIZE;
}
static void writeResEntryHeader(char *&buf, size_t manifestSize,
int manifestID) {
// Write the prefix.
auto *prefix = reinterpret_cast<object::WinResHeaderPrefix *>(buf);
prefix->DataSize = manifestSize;
prefix->HeaderSize = sizeof(object::WinResHeaderPrefix) +
sizeof(object::WinResIDs) +
sizeof(object::WinResHeaderSuffix);
buf += sizeof(object::WinResHeaderPrefix);
// Write the Type/Name IDs.
auto *iDs = reinterpret_cast<object::WinResIDs *>(buf);
iDs->setType(RT_MANIFEST);
iDs->setName(manifestID);
buf += sizeof(object::WinResIDs);
// Write the suffix.
auto *suffix = reinterpret_cast<object::WinResHeaderSuffix *>(buf);
suffix->DataVersion = 0;
suffix->MemoryFlags = object::WIN_RES_PURE_MOVEABLE;
suffix->Language = SUBLANG_ENGLISH_US;
suffix->Version = 0;
suffix->Characteristics = 0;
buf += sizeof(object::WinResHeaderSuffix);
}
// Create a resource file containing a manifest XML.
std::unique_ptr<MemoryBuffer> LinkerDriver::createManifestRes() {
std::string manifest = createManifestXml();
std::unique_ptr<WritableMemoryBuffer> res =
createMemoryBufferForManifestRes(manifest.size());
char *buf = res->getBufferStart();
writeResFileHeader(buf);
writeResEntryHeader(buf, manifest.size(), ctx.config.manifestID);
// Copy the manifest data into the .res file.
std::copy(manifest.begin(), manifest.end(), buf);
return std::move(res);
}
void LinkerDriver::createSideBySideManifest() {
std::string path = std::string(ctx.config.manifestFile);
if (path == "")
path = ctx.config.outputFile + ".manifest";
std::error_code ec;
raw_fd_ostream out(path, ec, sys::fs::OF_TextWithCRLF);
if (ec)
Fatal(ctx) << "failed to create manifest: " << ec.message();
out << createManifestXml();
}
// Parse a string in the form of
// "<name>[=<internalname>][,@ordinal[,NONAME]][,DATA][,PRIVATE]"
// or "<name>=<dllname>.<name>".
// Used for parsing /export arguments.
Export LinkerDriver::parseExport(StringRef arg) {
Export e;
e.source = ExportSource::Export;
StringRef rest;
std::tie(e.name, rest) = arg.split(",");
if (e.name.empty())
goto err;
if (e.name.contains('=')) {
auto [x, y] = e.name.split("=");
// If "<name>=<dllname>.<name>".
if (y.contains(".")) {
e.name = x;
e.forwardTo = y;
} else {
e.extName = x;
e.name = y;
if (e.name.empty())
goto err;
}
}
// Optional parameters
// "[,@ordinal[,NONAME]][,DATA][,PRIVATE][,EXPORTAS,exportname]"
while (!rest.empty()) {
StringRef tok;
std::tie(tok, rest) = rest.split(",");
if (tok.equals_insensitive("noname")) {
if (e.ordinal == 0)
goto err;
e.noname = true;
continue;
}
if (tok.equals_insensitive("data")) {
e.data = true;
continue;
}
if (tok.equals_insensitive("constant")) {
e.constant = true;
continue;
}
if (tok.equals_insensitive("private")) {
e.isPrivate = true;
continue;
}
if (tok.equals_insensitive("exportas")) {
if (!rest.empty() && !rest.contains(','))
e.exportAs = rest;
else
Err(ctx) << "invalid EXPORTAS value: " << rest;
break;
}
if (tok.starts_with("@")) {
int32_t ord;
if (tok.substr(1).getAsInteger(0, ord))
goto err;
if (ord <= 0 || 65535 < ord)
goto err;
e.ordinal = ord;
continue;
}
goto err;
}
return e;
err:
Fatal(ctx) << "invalid /export: " << arg;
llvm_unreachable("");
}
// Parses a string in the form of "key=value" and check
// if value matches previous values for the same key.
void LinkerDriver::checkFailIfMismatch(StringRef arg, InputFile *source) {
auto [k, v] = arg.split('=');
if (k.empty() || v.empty())
Fatal(ctx) << "/failifmismatch: invalid argument: " << arg;
std::pair<StringRef, InputFile *> existing = ctx.config.mustMatch[k];
if (!existing.first.empty() && v != existing.first) {
std::string sourceStr = source ? toString(source) : "cmd-line";
std::string existingStr =
existing.second ? toString(existing.second) : "cmd-line";
Fatal(ctx) << "/failifmismatch: mismatch detected for '" << k << "':\n>>> "
<< existingStr << " has value " << existing.first << "\n>>> "
<< sourceStr << " has value " << v;
}
ctx.config.mustMatch[k] = {v, source};
}
// Convert Windows resource files (.res files) to a .obj file.
// Does what cvtres.exe does, but in-process and cross-platform.
MemoryBufferRef LinkerDriver::convertResToCOFF(ArrayRef<MemoryBufferRef> mbs,
ArrayRef<ObjFile *> objs) {
object::WindowsResourceParser parser(/* MinGW */ ctx.config.mingw);
std::vector<std::string> duplicates;
for (MemoryBufferRef mb : mbs) {
std::unique_ptr<object::Binary> bin = check(object::createBinary(mb));
object::WindowsResource *rf = dyn_cast<object::WindowsResource>(bin.get());
if (!rf)
Fatal(ctx) << "cannot compile non-resource file as resource";
if (auto ec = parser.parse(rf, duplicates))
Fatal(ctx) << toString(std::move(ec));
}
// Note: This processes all .res files before all objs. Ideally they'd be
// handled in the same order they were linked (to keep the right one, if
// there are duplicates that are tolerated due to forceMultipleRes).
for (ObjFile *f : objs) {
object::ResourceSectionRef rsf;
if (auto ec = rsf.load(f->getCOFFObj()))
Fatal(ctx) << toString(f) << ": " << toString(std::move(ec));
if (auto ec = parser.parse(rsf, f->getName(), duplicates))
Fatal(ctx) << toString(std::move(ec));
}
if (ctx.config.mingw)
parser.cleanUpManifests(duplicates);
for (const auto &dupeDiag : duplicates)
if (ctx.config.forceMultipleRes)
Warn(ctx) << dupeDiag;
else
Err(ctx) << dupeDiag;
Expected<std::unique_ptr<MemoryBuffer>> e =
llvm::object::writeWindowsResourceCOFF(ctx.config.machine, parser,
ctx.config.timestamp);
if (!e)
Fatal(ctx) << "failed to write .res to COFF: " << toString(e.takeError());
MemoryBufferRef mbref = **e;
make<std::unique_ptr<MemoryBuffer>>(std::move(*e)); // take ownership
return mbref;
}
// Create OptTable
#define OPTTABLE_STR_TABLE_CODE
#include "Options.inc"
#undef OPTTABLE_STR_TABLE_CODE
// Create prefix string literals used in Options.td
#define OPTTABLE_PREFIXES_TABLE_CODE
#include "Options.inc"
#undef OPTTABLE_PREFIXES_TABLE_CODE
// Create table mapping all options defined in Options.td
static constexpr llvm::opt::OptTable::Info infoTable[] = {
#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
#include "Options.inc"
#undef OPTION
};
COFFOptTable::COFFOptTable()
: GenericOptTable(OptionStrTable, OptionPrefixesTable, infoTable, true) {}
// Set color diagnostics according to --color-diagnostics={auto,always,never}
// or --no-color-diagnostics flags.
static void handleColorDiagnostics(COFFLinkerContext &ctx,
opt::InputArgList &args) {
auto *arg = args.getLastArg(OPT_color_diagnostics, OPT_color_diagnostics_eq,
OPT_no_color_diagnostics);
if (!arg)
return;
if (arg->getOption().getID() == OPT_color_diagnostics) {
ctx.e.errs().enable_colors(true);
} else if (arg->getOption().getID() == OPT_no_color_diagnostics) {
ctx.e.errs().enable_colors(false);
} else {
StringRef s = arg->getValue();
if (s == "always")
ctx.e.errs().enable_colors(true);
else if (s == "never")
ctx.e.errs().enable_colors(false);
else if (s != "auto")
Err(ctx) << "unknown option: --color-diagnostics=" << s;
}
}
static cl::TokenizerCallback getQuotingStyle(COFFLinkerContext &ctx,
opt::InputArgList &args) {
if (auto *arg = args.getLastArg(OPT_rsp_quoting)) {
StringRef s = arg->getValue();
if (s != "windows" && s != "posix")
Err(ctx) << "invalid response file quoting: " << s;
if (s == "windows")
return cl::TokenizeWindowsCommandLine;
return cl::TokenizeGNUCommandLine;
}
// The COFF linker always defaults to Windows quoting.
return cl::TokenizeWindowsCommandLine;
}
ArgParser::ArgParser(COFFLinkerContext &c) : ctx(c) {}
// Parses a given list of options.
opt::InputArgList ArgParser::parse(ArrayRef<const char *> argv) {
// Make InputArgList from string vectors.
unsigned missingIndex;
unsigned missingCount;
// We need to get the quoting style for response files before parsing all
// options so we parse here before and ignore all the options but
// --rsp-quoting and /lldignoreenv.
// (This means --rsp-quoting can't be added through %LINK%.)
opt::InputArgList args =
ctx.optTable.ParseArgs(argv, missingIndex, missingCount);
// Expand response files (arguments in the form of @<filename>) and insert
// flags from %LINK% and %_LINK_%, and then parse the argument again.
SmallVector<const char *, 256> expandedArgv(argv.data(),
argv.data() + argv.size());
if (!args.hasArg(OPT_lldignoreenv))
addLINK(expandedArgv);
cl::ExpandResponseFiles(saver(), getQuotingStyle(ctx, args), expandedArgv);
args = ctx.optTable.ParseArgs(ArrayRef(expandedArgv).drop_front(),
missingIndex, missingCount);
// Print the real command line if response files are expanded.
if (args.hasArg(OPT_verbose) && argv.size() != expandedArgv.size()) {
std::string msg = "Command line:";
for (const char *s : expandedArgv)
msg += " " + std::string(s);
Msg(ctx) << msg;
}
// Save the command line after response file expansion so we can write it to
// the PDB if necessary. Mimic MSVC, which skips input files.
ctx.config.argv = {argv[0]};
for (opt::Arg *arg : args) {
if (arg->getOption().getKind() != opt::Option::InputClass) {
ctx.config.argv.emplace_back(args.getArgString(arg->getIndex()));
}
}
// Handle /WX early since it converts missing argument warnings to errors.
ctx.e.fatalWarnings = args.hasFlag(OPT_WX, OPT_WX_no, false);
if (missingCount)
Fatal(ctx) << args.getArgString(missingIndex) << ": missing argument";
handleColorDiagnostics(ctx, args);
for (opt::Arg *arg : args.filtered(OPT_UNKNOWN)) {
std::string nearest;
if (ctx.optTable.findNearest(arg->getAsString(args), nearest) > 1)
Warn(ctx) << "ignoring unknown argument '" << arg->getAsString(args)
<< "'";
else
Warn(ctx) << "ignoring unknown argument '" << arg->getAsString(args)
<< "', did you mean '" << nearest << "'";
}
if (args.hasArg(OPT_lib))
Warn(ctx) << "ignoring /lib since it's not the first argument";
return args;
}
// Tokenizes and parses a given string as command line in .drective section.
ParsedDirectives ArgParser::parseDirectives(StringRef s) {
ParsedDirectives result;
SmallVector<const char *, 16> rest;
// Handle /EXPORT and /INCLUDE in a fast path. These directives can appear for
// potentially every symbol in the object, so they must be handled quickly.
SmallVector<StringRef, 16> tokens;
cl::TokenizeWindowsCommandLineNoCopy(s, saver(), tokens);
for (StringRef tok : tokens) {
if (tok.starts_with_insensitive("/export:") ||
tok.starts_with_insensitive("-export:"))
result.exports.push_back(tok.substr(strlen("/export:")));
else if (tok.starts_with_insensitive("/include:") ||
tok.starts_with_insensitive("-include:"))
result.includes.push_back(tok.substr(strlen("/include:")));
else if (tok.starts_with_insensitive("/exclude-symbols:") ||
tok.starts_with_insensitive("-exclude-symbols:"))
result.excludes.push_back(tok.substr(strlen("/exclude-symbols:")));
else {
// Copy substrings that are not valid C strings. The tokenizer may have
// already copied quoted arguments for us, so those do not need to be
// copied again.
bool HasNul = tok.end() != s.end() && tok.data()[tok.size()] == '\0';
rest.push_back(HasNul ? tok.data() : saver().save(tok).data());
}
}
// Make InputArgList from unparsed string vectors.
unsigned missingIndex;
unsigned missingCount;
result.args = ctx.optTable.ParseArgs(rest, missingIndex, missingCount);
if (missingCount)
Fatal(ctx) << result.args.getArgString(missingIndex)
<< ": missing argument";
for (auto *arg : result.args.filtered(OPT_UNKNOWN))
Warn(ctx) << "ignoring unknown argument: " << arg->getAsString(result.args);
return result;
}
// link.exe has an interesting feature. If LINK or _LINK_ environment
// variables exist, their contents are handled as command line strings.
// So you can pass extra arguments using them.
void ArgParser::addLINK(SmallVector<const char *, 256> &argv) {
// Concatenate LINK env and command line arguments, and then parse them.
if (std::optional<std::string> s = Process::GetEnv("LINK")) {
std::vector<const char *> v = tokenize(*s);
argv.insert(std::next(argv.begin()), v.begin(), v.end());
}
if (std::optional<std::string> s = Process::GetEnv("_LINK_")) {
std::vector<const char *> v = tokenize(*s);
argv.insert(std::next(argv.begin()), v.begin(), v.end());
}
}
std::vector<const char *> ArgParser::tokenize(StringRef s) {
SmallVector<const char *, 16> tokens;
cl::TokenizeWindowsCommandLine(s, saver(), tokens);
return std::vector<const char *>(tokens.begin(), tokens.end());
}
void LinkerDriver::printHelp(const char *argv0) {
ctx.optTable.printHelp(ctx.e.outs(),
(std::string(argv0) + " [options] file...").c_str(),
"LLVM Linker", false);
}
} // namespace coff
} // namespace lld