llvm-project/lld/COFF/DriverUtils.cpp
Chandler Carruth dd647e3e60
Rework the Option library to reduce dynamic relocations (#119198)
Apologies for the large change, I looked for ways to break this up and
all of the ones I saw added real complexity. This change focuses on the
option's prefixed names and the array of prefixes. These are present in
every option and the dominant source of dynamic relocations for PIE or
PIC users of LLVM and Clang tooling. In some cases, 100s or 1000s of
them for the Clang driver which has a huge number of options.

This PR addresses this by building a string table and a prefixes table
that can be referenced with indices rather than pointers that require
dynamic relocations. This removes almost 7k dynmaic relocations from the
`clang` binary, roughly 8% of the remaining dynmaic relocations outside
of vtables. For busy-boxing use cases where many different option tables
are linked into the same binary, the savings add up a bit more.

The string table is a straightforward mechanism, but the prefixes
required some subtlety. They are encoded in a Pascal-string fashion with
a size followed by a sequence of offsets. This works relatively well for
the small realistic prefixes arrays in use.

Lots of code has to change in order to land this though: both all the
option library code has to be updated to use the string table and
prefixes table, and all the users of the options library have to be
updated to correctly instantiate the objects.

Some follow-up patches in the works to provide an abstraction for this
style of code, and to start using the same technique for some of the
other strings here now that the infrastructure is in place.
2024-12-11 15:44:44 -08:00

1041 lines
35 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/STLExtras.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/FileUtilities.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/WindowsManifest/WindowsManifestMerger.h"
#include <limits>
#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::parseAlternateName(StringRef s) {
auto [from, to] = s.split('=');
if (from.empty() || to.empty())
Fatal(ctx) << "/alternatename: invalid argument: " << s;
auto it = ctx.config.alternateNames.find(from);
if (it != ctx.config.alternateNames.end() && it->second != to)
Fatal(ctx) << "/alternatename: conflicts: " << s;
ctx.config.alternateNames.insert(it, std::make_pair(from, to));
}
// 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);
}
// Parses /aligncomm option argument.
void LinkerDriver::parseAligncomm(StringRef s) {
auto [name, align] = s.split(',');
if (name.empty() || align.empty()) {
Err(ctx) << "/aligncomm: invalid argument: " << s;
return;
}
int v;
if (align.getAsInteger(0, v)) {
Err(ctx) << "/aligncomm: invalid argument: " << s;
return;
}
ctx.config.alignComm[std::string(name)] =
std::max(ctx.config.alignComm[std::string(name)], 1 << v);
}
// 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());
}
// 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.get()))
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().get()->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("");
}
// Convert stdcall/fastcall style symbols into unsuffixed symbols,
// with or without a leading underscore. (MinGW specific.)
static StringRef killAt(StringRef sym, bool prefix) {
if (sym.empty())
return sym;
// Strip any trailing stdcall suffix
sym = sym.substr(0, sym.find('@', 1));
if (!sym.starts_with("@")) {
if (prefix && !sym.starts_with("_"))
return saver().save("_" + sym);
return sym;
}
// For fastcall, remove the leading @ and replace it with an
// underscore, if prefixes are used.
sym = sym.substr(1);
if (prefix)
sym = saver().save("_" + sym);
return sym;
}
static StringRef exportSourceName(ExportSource s) {
switch (s) {
case ExportSource::Directives:
return "source file (directives)";
case ExportSource::Export:
return "/export";
case ExportSource::ModuleDefinition:
return "/def";
default:
llvm_unreachable("unknown ExportSource");
}
}
// Performs error checking on all /export arguments.
// It also sets ordinals.
void LinkerDriver::fixupExports() {
llvm::TimeTraceScope timeScope("Fixup exports");
// Symbol ordinals must be unique.
std::set<uint16_t> ords;
for (Export &e : ctx.config.exports) {
if (e.ordinal == 0)
continue;
if (!ords.insert(e.ordinal).second)
Fatal(ctx) << "duplicate export ordinal: " << e.name;
}
for (Export &e : ctx.config.exports) {
if (!e.exportAs.empty()) {
e.exportName = e.exportAs;
continue;
}
StringRef sym =
!e.forwardTo.empty() || e.extName.empty() ? e.name : e.extName;
if (ctx.config.machine == I386 && sym.starts_with("_")) {
// In MSVC mode, a fully decorated stdcall function is exported
// as-is with the leading underscore (with type IMPORT_NAME).
// In MinGW mode, a decorated stdcall function gets the underscore
// removed, just like normal cdecl functions.
if (ctx.config.mingw || !sym.contains('@')) {
e.exportName = sym.substr(1);
continue;
}
}
if (isArm64EC(ctx.config.machine) && !e.data && !e.constant) {
if (std::optional<std::string> demangledName =
getArm64ECDemangledFunctionName(sym)) {
e.exportName = saver().save(*demangledName);
continue;
}
}
e.exportName = sym;
}
if (ctx.config.killAt && ctx.config.machine == I386) {
for (Export &e : ctx.config.exports) {
e.name = killAt(e.name, true);
e.exportName = killAt(e.exportName, false);
e.extName = killAt(e.extName, true);
e.symbolName = killAt(e.symbolName, true);
}
}
// Uniquefy by name.
DenseMap<StringRef, std::pair<Export *, unsigned>> map(
ctx.config.exports.size());
std::vector<Export> v;
for (Export &e : ctx.config.exports) {
auto pair = map.insert(std::make_pair(e.exportName, std::make_pair(&e, 0)));
bool inserted = pair.second;
if (inserted) {
pair.first->second.second = v.size();
v.push_back(e);
continue;
}
Export *existing = pair.first->second.first;
if (e == *existing || e.name != existing->name)
continue;
// If the existing export comes from .OBJ directives, we are allowed to
// overwrite it with /DEF: or /EXPORT without any warning, as MSVC link.exe
// does.
if (existing->source == ExportSource::Directives) {
*existing = e;
v[pair.first->second.second] = e;
continue;
}
if (existing->source == e.source) {
Warn(ctx) << "duplicate " << exportSourceName(existing->source)
<< " option: " << e.name;
} else {
Warn(ctx) << "duplicate export: " << e.name << " first seen in "
<< exportSourceName(existing->source) << ", now in "
<< exportSourceName(e.source);
}
}
ctx.config.exports = std::move(v);
// Sort by name.
llvm::sort(ctx.config.exports, [](const Export &a, const Export &b) {
return a.exportName < b.exportName;
});
}
void LinkerDriver::assignExportOrdinals() {
// Assign unique ordinals if default (= 0).
uint32_t max = 0;
for (Export &e : ctx.config.exports)
max = std::max(max, (uint32_t)e.ordinal);
for (Export &e : ctx.config.exports)
if (e.ordinal == 0)
e.ordinal = ++max;
if (max > std::numeric_limits<uint16_t>::max())
Fatal(ctx) << "too many exported symbols (got " << max << ", max "
<< Twine(std::numeric_limits<uint16_t>::max()) << ")";
}
// 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