[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:
parent
5360258bce
commit
adcdc9cc37
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
115
lld/test/COFF/ordinals-override.test
Normal file
115
lld/test/COFF/ordinals-override.test
Normal 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
|
||||
...
|
Loading…
x
Reference in New Issue
Block a user