diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h --- a/lld/COFF/Config.h +++ b/lld/COFF/Config.h @@ -40,6 +40,13 @@ 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 @@ -58,8 +65,7 @@ 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 diff --git a/lld/COFF/DLL.cpp b/lld/COFF/DLL.cpp --- a/lld/COFF/DLL.cpp +++ b/lld/COFF/DLL.cpp @@ -555,8 +555,10 @@ // 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 @@ 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 @@ 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 @@ } private: + size_t baseOrdinal; size_t size; const COFFLinkerContext &ctx; }; @@ -634,22 +640,24 @@ 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 i) + : baseOrdinal(baseOrdinal), size(i), 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,15 @@ } 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); + } + assert(baseOrdinal >= 1); auto *dllName = make(sys::path::filename(ctx.config.outputFile)); - auto *addressTab = make(ctx, maxOrdinal); + auto *addressTab = make(ctx, baseOrdinal, maxOrdinal); std::vector names; for (Export &e : ctx.config.exports) if (!e.noname) @@ -851,9 +862,10 @@ } auto *nameTab = make(names); - auto *ordinalTab = make(ctx, names.size()); - auto *dir = make(maxOrdinal, names.size(), dllName, - addressTab, nameTab, ordinalTab); + auto *ordinalTab = make(ctx, baseOrdinal, names.size()); + auto *dir = + make(baseOrdinal, maxOrdinal, names.size(), dllName, + addressTab, nameTab, ordinalTab); chunks.push_back(dir); chunks.push_back(dllName); chunks.push_back(addressTab); diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -372,7 +372,7 @@ 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); } @@ -1045,6 +1045,7 @@ e2.data = e1.Data; e2.isPrivate = e1.Private; e2.constant = e1.Constant; + e2.source = ExportSource::ModuleDefinition; ctx.config.exports.push_back(e2); } } @@ -2241,7 +2242,7 @@ if (!e.forwardTo.empty()) continue; e.sym = addUndefined(e.name); - if (!e.directives) + if (e.source != ExportSource::Directives) e.symbolName = mangleMaybe(e.sym); } diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp --- a/lld/COFF/DriverUtils.cpp +++ b/lld/COFF/DriverUtils.cpp @@ -549,6 +549,8 @@ // 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 @@ 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 @@ } // Uniquefy by name. - DenseMap map(ctx.config.exports.size()); + DenseMap> map( + ctx.config.exports.size()); std::vector 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); diff --git a/lld/test/COFF/Inputs/ordinals-override.def b/lld/test/COFF/Inputs/ordinals-override.def new file mode 100644 --- /dev/null +++ b/lld/test/COFF/Inputs/ordinals-override.def @@ -0,0 +1,3 @@ +EXPORTS +foo @55 +bar @66 diff --git a/lld/test/COFF/export.test b/lld/test/COFF/export.test --- a/lld/test/COFF/export.test +++ b/lld/test/COFF/export.test @@ -14,11 +14,8 @@ CHECK2: Export Table: CHECK2: DLL name: export.test.tmp.dll +CHECK2: Ordinal base: 5 CHECK2: Ordinal RVA Name -CHECK2-NEXT: 1 0 -CHECK2-NEXT: 2 0 -CHECK2-NEXT: 3 0 -CHECK2-NEXT: 4 0 CHECK2-NEXT: 5 0x1008 exportfn1 CHECK2-NEXT: 6 0x1010 exportfn2 CHECK2-NEXT: 7 0x1010 exportfn3 @@ -28,11 +25,8 @@ CHECK3: Export Table: CHECK3: DLL name: export.test.tmp.dll +CHECK3: Ordinal base: 5 CHECK3: Ordinal RVA Name -CHECK3-NEXT: 1 0 -CHECK3-NEXT: 2 0 -CHECK3-NEXT: 3 0 -CHECK3-NEXT: 4 0 CHECK3-NEXT: 5 0x1008 CHECK3-NEXT: 6 0x1010 exportfn2 @@ -56,8 +50,8 @@ CHECK5: Export Table: CHECK5: DLL name: export.test.tmp.dll +CHECK5: Ordinal base: 2 CHECK5: Ordinal RVA Name -CHECK5-NEXT: 1 0 CHECK5-NEXT: 2 0x1010 fn2 CHECK5-NEXT: 3 0x1008 exportfn1 CHECK5-NEXT: 4 0x1010 exportfn3 diff --git a/lld/test/COFF/export32.test b/lld/test/COFF/export32.test --- a/lld/test/COFF/export32.test +++ b/lld/test/COFF/export32.test @@ -25,11 +25,8 @@ # CHECK2: Export Table: # CHECK2: DLL name: export32.test.tmp.dll +# CHECK2: Ordinal base: 5 # CHECK2: Ordinal RVA Name -# CHECK2-NEXT: 1 0 -# CHECK2-NEXT: 2 0 -# CHECK2-NEXT: 3 0 -# CHECK2-NEXT: 4 0 # CHECK2-NEXT: 5 0x1008 exportfn1 # CHECK2-NEXT: 6 0x1010 exportfn2 # CHECK2-NEXT: 7 0x1010 exportfn3 @@ -40,11 +37,8 @@ # CHECK3: Export Table: # CHECK3: DLL name: export32.test.tmp.dll +# CHECK3: Ordinal base: 5 # CHECK3: Ordinal RVA Name -# CHECK3-NEXT: 1 0 -# CHECK3-NEXT: 2 0 -# CHECK3-NEXT: 3 0 -# CHECK3-NEXT: 4 0 # CHECK3-NEXT: 5 0x1008 # CHECK3-NEXT: 6 0x1010 exportfn2 @@ -70,8 +64,8 @@ # CHECK5: Export Table: # CHECK5: DLL name: export32.test.tmp.dll +# CHECK5: Ordinal base: 2 # CHECK5: Ordinal RVA Name -# CHECK5-NEXT: 1 0 # CHECK5-NEXT: 2 0x1010 fn2 # CHECK5-NEXT: 3 0x1008 exportfn1 # CHECK5-NEXT: 4 0x1010 exportfn3 @@ -88,8 +82,9 @@ # CHECK7: Export Table: # CHECK7: DLL name: export32.test.tmp.dll +# CHECK7: Ordinal base: 1 # CHECK7: Ordinal RVA Name -# CHECK7-NEXT: 1 0 +# CHECK7-NEXT: 1 0x1010 exportfn3 # CHECK7-NEXT: 2 0x1010 foo --- !COFF diff --git a/lld/test/COFF/ordinals-override.test b/lld/test/COFF/ordinals-override.test new file mode 100644 --- /dev/null +++ b/lld/test/COFF/ordinals-override.test @@ -0,0 +1,124 @@ +# 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 directives section below contains the following: +# +# Linker Directives +# ----------------- +# /export:baz=?baz@@YAXXZ +# /DEFAULTLIB:LIBCMT +# /DEFAULTLIB:OLDNAMES +# /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: 2020202F6578706F72743A62617A3D3F62617A4040594158585A202F44454641554C544C49423A224C4942434D5422202F44454641554C544C49423A224F4C444E414D455322202F4558504F52543A3F666F6F4040594158585A202F4558504F52543A3F6261724040594158585A20 + - Name: '.text$mn' + Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ] + Alignment: 16 + SectionData: C20000CCCCCCCCCCCCCCCCCCCCCCCCCCC20000CCCCCCCCCCCCCCCCCCCCCCCCCCC20000 +symbols: + - Name: .drectve + Value: 0 + SectionNumber: 1 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_STATIC + SectionDefinition: + Length: 111 + NumberOfRelocations: 0 + NumberOfLinenumbers: 0 + CheckSum: 3793361033 + Number: 0 + - 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: 1911911868 + Number: 0 + - 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 +... diff --git a/llvm/include/llvm/Object/COFF.h b/llvm/include/llvm/Object/COFF.h --- a/llvm/include/llvm/Object/COFF.h +++ b/llvm/include/llvm/Object/COFF.h @@ -1177,6 +1177,8 @@ bool operator==(const ExportDirectoryEntryRef &Other) const; void moveNext(); + Error isEmpty(bool &Result) const; + Error getDllName(StringRef &Result) const; Error getOrdinalBase(uint32_t &Result) const; Error getOrdinal(uint32_t &Result) const; diff --git a/llvm/lib/Object/COFFObjectFile.cpp b/llvm/lib/Object/COFFObjectFile.cpp --- a/llvm/lib/Object/COFFObjectFile.cpp +++ b/llvm/lib/Object/COFFObjectFile.cpp @@ -1559,6 +1559,17 @@ ++Index; } +Error ExportDirectoryEntryRef::isEmpty(bool &Result) const { + uint32_t RVA; + if (Error EC = getExportRVA(RVA)) + return EC; + uintptr_t IntPtr = 0; + Error E = OwningObject->getRvaPtr(RVA, IntPtr); + Result = (bool)E; + consumeError(std::move(E)); + return Error::success(); +} + // Returns the name of the current export symbol. If the symbol is exported only // by ordinal, the empty string is set as a result. Error ExportDirectoryEntryRef::getDllName(StringRef &Result) const { diff --git a/llvm/tools/llvm-objdump/COFFDump.cpp b/llvm/tools/llvm-objdump/COFFDump.cpp --- a/llvm/tools/llvm-objdump/COFFDump.cpp +++ b/llvm/tools/llvm-objdump/COFFDump.cpp @@ -545,6 +545,11 @@ outs() << " Ordinal base: " << OrdinalBase << "\n"; outs() << " Ordinal RVA Name\n"; for (; I != E; I = ++I) { + bool Empty; + if (I->isEmpty(Empty)) + return; + if (Empty) + continue; uint32_t Ordinal; if (I->getOrdinal(Ordinal)) return; @@ -559,9 +564,9 @@ // Export table entries can be used to re-export symbols that // this COFF file is imported from some DLLs. This is rare. // In most cases IsForwarder is false. - outs() << format(" % 4d ", Ordinal); + outs() << format(" %5d ", Ordinal); } else { - outs() << format(" % 4d %# 8x", Ordinal, RVA); + outs() << format(" %5d %# 8x", Ordinal, RVA); } StringRef Name;