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
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
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
// 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 {
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;
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)
}
private:
+ size_t baseOrdinal;
size_t size;
const COFFLinkerContext &ctx;
};
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;
};
}
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)
}
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);
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);
}
e2.data = e1.Data;
e2.isPrivate = e1.Private;
e2.constant = e1.Constant;
+ e2.source = ExportSource::ModuleDefinition;
ctx.config.exports.push_back(e2);
}
}
if (!e.forwardTo.empty())
continue;
e.sym = addUndefined(e.name);
- if (!e.directives)
+ if (e.source != ExportSource::Directives)
e.symbolName = mangleMaybe(e.sym);
}
// 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())
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() {
}
// 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);
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
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
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
# 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
# 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
# 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
--- /dev/null
+# 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
+...