[LLD][COFF] Allow overwriting directives exports with cmd-line exports

MSVC link.exe allows overriding exports on the cmd-line with exports seen in OBJ directives. The typical case is what is described in #62329.

Before this patch, trying to override an export with `/export` or `/def` would generate a duplicate warning. This patches tries to replicate the MSVC behavior. A second override on the cmd-line would still generate the warning.

There's still a case which we don't cover: MSVC link.exe is able to demangle an exported OBJ directive function, and match it with a unmangled export function in a .def file. In the meanwhile, one can use the mangled export in the .def to cover that case.

This fixes #62329

Differential revision: https://reviews.llvm.org/D149611
This commit is contained in:
Alexandre Ganea 2023-06-13 15:12:55 -04:00
parent 5360258bce
commit adcdc9cc37
7 changed files with 203 additions and 35 deletions

View File

@ -42,6 +42,13 @@ static const auto ARM64X = llvm::COFF::IMAGE_FILE_MACHINE_ARM64X;
static const auto ARMNT = llvm::COFF::IMAGE_FILE_MACHINE_ARMNT;
static const auto I386 = llvm::COFF::IMAGE_FILE_MACHINE_I386;
enum class ExportSource {
Unset,
Directives,
Export,
ModuleDefinition,
};
// Represents an /export option.
struct Export {
StringRef name; // N in /export:N or /export:E=N
@ -60,8 +67,7 @@ struct Export {
StringRef forwardTo;
StringChunk *forwardChunk = nullptr;
// True if this /export option was in .drectves section.
bool directives = false;
ExportSource source = ExportSource::Unset;
StringRef symbolName;
StringRef exportName; // Name in DLL

View File

@ -555,8 +555,10 @@ private:
// A chunk for the export descriptor table.
class ExportDirectoryChunk : public NonSectionChunk {
public:
ExportDirectoryChunk(int i, int j, Chunk *d, Chunk *a, Chunk *n, Chunk *o)
: maxOrdinal(i), nameTabSize(j), dllName(d), addressTab(a), nameTab(n),
ExportDirectoryChunk(int baseOrdinal, int maxOrdinal, int nameTabSize,
Chunk *d, Chunk *a, Chunk *n, Chunk *o)
: baseOrdinal(baseOrdinal), maxOrdinal(maxOrdinal),
nameTabSize(nameTabSize), dllName(d), addressTab(a), nameTab(n),
ordinalTab(o) {}
size_t getSize() const override {
@ -568,14 +570,15 @@ public:
auto *e = (export_directory_table_entry *)(buf);
e->NameRVA = dllName->getRVA();
e->OrdinalBase = 1;
e->AddressTableEntries = maxOrdinal;
e->OrdinalBase = baseOrdinal;
e->AddressTableEntries = (maxOrdinal - baseOrdinal) + 1;
e->NumberOfNamePointers = nameTabSize;
e->ExportAddressTableRVA = addressTab->getRVA();
e->NamePointerRVA = nameTab->getRVA();
e->OrdinalTableRVA = ordinalTab->getRVA();
}
uint16_t baseOrdinal;
uint16_t maxOrdinal;
uint16_t nameTabSize;
Chunk *dllName;
@ -586,17 +589,19 @@ public:
class AddressTableChunk : public NonSectionChunk {
public:
explicit AddressTableChunk(COFFLinkerContext &ctx, size_t maxOrdinal)
: size(maxOrdinal), ctx(ctx) {}
explicit AddressTableChunk(COFFLinkerContext &ctx, size_t baseOrdinal,
size_t maxOrdinal)
: baseOrdinal(baseOrdinal), size((maxOrdinal - baseOrdinal) + 1),
ctx(ctx) {}
size_t getSize() const override { return size * 4; }
void writeTo(uint8_t *buf) const override {
memset(buf, 0, getSize());
for (const Export &e : ctx.config.exports) {
assert(e.ordinal != 0 && "Export symbol has invalid ordinal");
// OrdinalBase is 1, so subtract 1 to get the index.
uint8_t *p = buf + (e.ordinal - 1) * 4;
assert(e.ordinal >= baseOrdinal && "Export symbol has invalid ordinal");
// Subtract the OrdinalBase to get the index.
uint8_t *p = buf + (e.ordinal - baseOrdinal) * 4;
uint32_t bit = 0;
// Pointer to thumb code must have the LSB set, so adjust it.
if (ctx.config.machine == ARMNT && !e.data)
@ -612,6 +617,7 @@ public:
}
private:
size_t baseOrdinal;
size_t size;
const COFFLinkerContext &ctx;
};
@ -634,22 +640,24 @@ private:
class ExportOrdinalChunk : public NonSectionChunk {
public:
explicit ExportOrdinalChunk(const COFFLinkerContext &ctx, size_t i)
: size(i), ctx(ctx) {}
explicit ExportOrdinalChunk(const COFFLinkerContext &ctx, size_t baseOrdinal,
size_t tableSize)
: baseOrdinal(baseOrdinal), size(tableSize), ctx(ctx) {}
size_t getSize() const override { return size * 2; }
void writeTo(uint8_t *buf) const override {
for (const Export &e : ctx.config.exports) {
if (e.noname)
continue;
assert(e.ordinal != 0 && "Export symbol has invalid ordinal");
// This table stores unbiased indices, so subtract 1 (OrdinalBase).
write16le(buf, e.ordinal - 1);
assert(e.ordinal >= baseOrdinal && "Export symbol has invalid ordinal");
// This table stores unbiased indices, so subtract OrdinalBase.
write16le(buf, e.ordinal - baseOrdinal);
buf += 2;
}
}
private:
size_t baseOrdinal;
size_t size;
const COFFLinkerContext &ctx;
};
@ -831,12 +839,17 @@ Chunk *DelayLoadContents::newThunkChunk(DefinedImportData *s,
}
EdataContents::EdataContents(COFFLinkerContext &ctx) : ctx(ctx) {
uint16_t maxOrdinal = 0;
for (Export &e : ctx.config.exports)
maxOrdinal = std::max(maxOrdinal, e.ordinal);
unsigned baseOrdinal = 1 << 16, maxOrdinal = 0;
for (Export &e : ctx.config.exports) {
baseOrdinal = std::min(baseOrdinal, (unsigned)e.ordinal);
maxOrdinal = std::max(maxOrdinal, (unsigned)e.ordinal);
}
// Ordinals must start at 1 as suggested in:
// https://learn.microsoft.com/en-us/cpp/build/reference/export-exports-a-function?view=msvc-170
assert(baseOrdinal >= 1);
auto *dllName = make<StringChunk>(sys::path::filename(ctx.config.outputFile));
auto *addressTab = make<AddressTableChunk>(ctx, maxOrdinal);
auto *addressTab = make<AddressTableChunk>(ctx, baseOrdinal, maxOrdinal);
std::vector<Chunk *> names;
for (Export &e : ctx.config.exports)
if (!e.noname)
@ -851,9 +864,10 @@ EdataContents::EdataContents(COFFLinkerContext &ctx) : ctx(ctx) {
}
auto *nameTab = make<NamePointersChunk>(names);
auto *ordinalTab = make<ExportOrdinalChunk>(ctx, names.size());
auto *dir = make<ExportDirectoryChunk>(maxOrdinal, names.size(), dllName,
addressTab, nameTab, ordinalTab);
auto *ordinalTab = make<ExportOrdinalChunk>(ctx, baseOrdinal, names.size());
auto *dir =
make<ExportDirectoryChunk>(baseOrdinal, maxOrdinal, names.size(), dllName,
addressTab, nameTab, ordinalTab);
chunks.push_back(dir);
chunks.push_back(dllName);
chunks.push_back(addressTab);

View File

@ -390,7 +390,7 @@ void LinkerDriver::parseDirectives(InputFile *file) {
if (!exp.extName.empty() && !isDecorated(exp.extName))
exp.extName = saver().save("_" + exp.extName);
}
exp.directives = true;
exp.source = ExportSource::Directives;
ctx.config.exports.push_back(exp);
}
@ -1063,6 +1063,7 @@ void LinkerDriver::parseModuleDefs(StringRef path) {
e2.data = e1.Data;
e2.isPrivate = e1.Private;
e2.constant = e1.Constant;
e2.source = ExportSource::ModuleDefinition;
ctx.config.exports.push_back(e2);
}
}
@ -2259,7 +2260,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
if (!e.forwardTo.empty())
continue;
e.sym = addUndefined(e.name);
if (!e.directives)
if (e.source != ExportSource::Directives)
e.symbolName = mangleMaybe(e.sym);
}

View File

@ -549,6 +549,8 @@ void LinkerDriver::createSideBySideManifest() {
// 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())
@ -641,6 +643,19 @@ static StringRef killAt(StringRef sym, bool prefix) {
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() {
@ -671,19 +686,36 @@ void LinkerDriver::fixupExports() {
}
// Uniquefy by name.
DenseMap<StringRef, Export *> map(ctx.config.exports.size());
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, &e));
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;
Export *existing = pair.first->second.first;
if (e == *existing || e.name != existing->name)
continue;
warn("duplicate /export option: " + e.name);
// 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(Twine("duplicate ") + exportSourceName(existing->source) +
" option: " + e.name);
} else {
warn("duplicate export: " + e.name +
Twine(" first seen in " + exportSourceName(existing->source) +
Twine(", now in " + exportSourceName(e.source))));
}
}
ctx.config.exports = std::move(v);

View File

@ -15,7 +15,7 @@ CHECK1-NEXT: 2 0x1010 exportfn2
CHECK2: Export Table:
CHECK2-NEXT: DLL name: export.test.tmp.dll
CHECK2-NEXT: Ordinal base: 1
CHECK2-NEXT: Ordinal base: 5
CHECK2-NEXT: Ordinal RVA Name
CHECK2-NEXT: 5 0x1008 exportfn1
CHECK2-NEXT: 6 0x1010 exportfn2
@ -26,7 +26,7 @@ CHECK2-NEXT: 7 0x1010 exportfn3
CHECK3: Export Table:
CHECK3-NEXT: DLL name: export.test.tmp.dll
CHECK3-NEXT: Ordinal base: 1
CHECK3-NEXT: Ordinal base: 5
CHECK3-NEXT: Ordinal RVA Name
CHECK3-NEXT: 5 0x1008
CHECK3-NEXT: 6 0x1010 exportfn2
@ -52,7 +52,7 @@ CHECK4-NM: 00000000 T f2
CHECK5: Export Table:
CHECK5-NEXT: DLL name: export.test.tmp.dll
CHECK5-NEXT: Ordinal base: 1
CHECK5-NEXT: Ordinal base: 2
CHECK5-NEXT: Ordinal RVA Name
CHECK5-NEXT: 2 0x1010 fn2
CHECK5-NEXT: 3 0x1008 exportfn1

View File

@ -26,7 +26,7 @@
# CHECK2: Export Table:
# CHECK2-NEXT: DLL name: export32.test.tmp.dll
# CHECK2-NEXT: Ordinal base: 1
# CHECK2-NEXT: Ordinal base: 5
# CHECK2-NEXT: Ordinal RVA Name
# CHECK2-NEXT: 5 0x1008 exportfn1
# CHECK2-NEXT: 6 0x1010 exportfn2
@ -38,7 +38,7 @@
# CHECK3: Export Table:
# CHECK3-NEXT: DLL name: export32.test.tmp.dll
# CHECK3-NEXT: Ordinal base: 1
# CHECK3-NEXT: Ordinal base: 5
# CHECK3-NEXT: Ordinal RVA Name
# CHECK3-NEXT: 5 0x1008
# CHECK3-NEXT: 6 0x1010 exportfn2
@ -66,7 +66,7 @@
# CHECK5: Export Table:
# CHECK5-NEXT: DLL name: export32.test.tmp.dll
# CHECK5-NEXT: Ordinal base: 1
# CHECK5-NEXT: Ordinal base: 2
# CHECK5-NEXT: Ordinal RVA Name
# CHECK5-NEXT: 2 0x1010 fn2
# CHECK5-NEXT: 3 0x1008 exportfn1

View File

@ -0,0 +1,115 @@
# RUN: yaml2obj %s -o %t.obj
#
# RUN: lld-link /out:%t.dll /dll %t.obj
# RUN: llvm-objdump -p %t.dll | FileCheck --check-prefix=CHECK1 %s
#
# CHECK1: Export Table:
# CHECK1: DLL name: ordinals-override.test.tmp.dll
# CHECK1: Ordinal base: 1
# CHECK1: Ordinal RVA Name
# CHECK1-NEXT: 1 0x1010 ?bar@@YAXXZ
# CHECK1-NEXT: 2 0x1000 ?foo@@YAXXZ
# CHECK1-NEXT: 3 0x1020 baz
#
# RUN: lld-link /out:%t.dll /dll %t.obj /EXPORT:?foo@@YAXXZ,@55
# RUN: llvm-objdump -p %t.dll | FileCheck --check-prefix=CHECK2 %s
#
# CHECK2: Export Table:
# CHECK2: DLL name: ordinals-override.test.tmp.dll
# CHECK2: Ordinal base: 55
# CHECK2: Ordinal RVA Name
# CHECK2-NEXT: 55 0x1000 ?foo@@YAXXZ
# CHECK2-NEXT: 56 0x1010 ?bar@@YAXXZ
# CHECK2-NEXT: 57 0x1020 baz
#
# RUN: lld-link /out:%t.dll /dll %t.obj /EXPORT:?foo@@YAXXZ,@55 /EXPORT:?bar@@YAXXZ,@122
# RUN: llvm-objdump -p %t.dll | FileCheck --check-prefix=CHECK3 %s
#
# CHECK3: Export Table:
# CHECK3: DLL name: ordinals-override.test.tmp.dll
# CHECK3: Ordinal base: 55
# CHECK3: Ordinal RVA Name
# CHECK3-NEXT: 55 0x1000 ?foo@@YAXXZ
# CHECK3-NEXT: 122 0x1010 ?bar@@YAXXZ
# CHECK3-NEXT: 123 0x1020 baz
#
# RUN: echo "EXPORTS" > %t.def
# RUN: echo "?foo@@YAXXZ @55" >> %t.def
# RUN: echo "?bar@@YAXXZ @122" >> %t.def
# RUN: lld-link /out:%t.dll /dll %t.obj /DEF:%t.def 2>&1 | FileCheck --check-prefix=WARN --allow-empty %s
# WARN-NOT: lld-link: warning
#
# RUN: llvm-objdump -p %t.dll | FileCheck --check-prefix=CHECK3 %s
#
# RUN: lld-link /out:%t.dll /dll %t.obj /DEF:%t.def /EXPORT:?foo@@YAXXZ,@10000 2>&1 | FileCheck --check-prefix=DUPLICATED %s
# DUPLICATED: lld-link: warning: duplicate export: ?foo@@YAXXZ first seen in /export, now in /def
#
# RUN: llvm-objdump -p %t.dll | FileCheck --check-prefix=CHECK4 %s
#
# CHECK4: Export Table:
# CHECK4: DLL name: ordinals-override.test.tmp.dll
# CHECK4: Ordinal base: 122
# CHECK4: Ordinal RVA Name
# CHECK4-NEXT: 122 0x1010 ?bar@@YAXXZ
# CHECK4-NEXT: 10000 0x1000 ?foo@@YAXXZ
# CHECK4-NEXT: 10001 0x1020 baz
# The .drectve section below contains the following:
#
# Linker Directives
# -----------------
# /export:baz=?baz@@YAXXZ
# /EXPORT:?foo@@YAXXZ
# /EXPORT:?bar@@YAXXZ
--- !COFF
header:
Machine: IMAGE_FILE_MACHINE_AMD64
Characteristics: [ ]
sections:
- Name: .drectve
Characteristics: [ IMAGE_SCN_LNK_INFO, IMAGE_SCN_LNK_REMOVE ]
Alignment: 1
SectionData: 2f6578706f72743a62617a3d3f62617a4040594158585a202f4558504f52543a3f666f6f4040594158585a202f4558504f52543a3f6261724040594158585a
- Name: '.text$mn'
Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
Alignment: 16
SectionData: C20000CCCCCCCCCCCCCCCCCCCCCCCCCCC20000CCCCCCCCCCCCCCCCCCCCCCCCCCC20000
symbols:
- Name: '.text$mn'
Value: 0
SectionNumber: 2
SimpleType: IMAGE_SYM_TYPE_NULL
ComplexType: IMAGE_SYM_DTYPE_NULL
StorageClass: IMAGE_SYM_CLASS_STATIC
SectionDefinition:
Length: 35
NumberOfRelocations: 0
NumberOfLinenumbers: 0
CheckSum: 0
Number: 0
- Name: _DllMainCRTStartup
Value: 0
SectionNumber: 2
SimpleType: IMAGE_SYM_TYPE_NULL
ComplexType: IMAGE_SYM_DTYPE_NULL
StorageClass: IMAGE_SYM_CLASS_EXTERNAL
- Name: '?foo@@YAXXZ'
Value: 0
SectionNumber: 2
SimpleType: IMAGE_SYM_TYPE_NULL
ComplexType: IMAGE_SYM_DTYPE_FUNCTION
StorageClass: IMAGE_SYM_CLASS_EXTERNAL
- Name: '?bar@@YAXXZ'
Value: 16
SectionNumber: 2
SimpleType: IMAGE_SYM_TYPE_NULL
ComplexType: IMAGE_SYM_DTYPE_FUNCTION
StorageClass: IMAGE_SYM_CLASS_EXTERNAL
- Name: '?baz@@YAXXZ'
Value: 32
SectionNumber: 2
SimpleType: IMAGE_SYM_TYPE_NULL
ComplexType: IMAGE_SYM_DTYPE_FUNCTION
StorageClass: IMAGE_SYM_CLASS_EXTERNAL
...