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,9 +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), - ordinalTab(o) {} + ExportDirectoryChunk(int i, int j, int k, Chunk *d, Chunk *a, Chunk *n, + Chunk *o) + : baseOrdinal(i), maxOrdinal(j), nameTabSize(k), dllName(d), + addressTab(a), nameTab(n), ordinalTab(o) {} size_t getSize() const override { return sizeof(export_directory_table_entry); @@ -568,14 +569,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,8 +588,10 @@ 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 { @@ -595,8 +599,8 @@ 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; + // 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 +616,7 @@ } private: + size_t baseOrdinal; size_t size; const COFFLinkerContext &ctx; }; @@ -634,8 +639,9 @@ 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 { @@ -643,13 +649,14 @@ 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); + // 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 +838,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 +861,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 @@ -15,7 +15,7 @@ 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 @@ 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 @@ 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 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 @@ -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 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,182 @@ +# 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 + +--- !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: '.debug$S' + Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_DISCARDABLE, IMAGE_SCN_MEM_READ ] + Alignment: 1 + SectionData: 04000000F1000000670000002900011100000000433A5C6769745C746573745F6F7264696E616C735C6275696C645C6C69622E6F626A003A003C1101620000D000130021008F7B0000130021008F7B00004D6963726F736F667420285229204F7074696D697A696E6720436F6D70696C65720000 + Subsections: + - !Symbols + Records: + - Kind: S_OBJNAME + ObjNameSym: + Signature: 0 + ObjectName: 'C:\git\test_ordinals\build\lib.obj' + - Kind: S_COMPILE3 + Compile3Sym: + Flags: [ NoDbgInfo, SecurityChecks, HotPatch ] + Machine: X64 + FrontendMajor: 19 + FrontendMinor: 33 + FrontendBuild: 31631 + FrontendQFE: 0 + BackendMajor: 19 + BackendMinor: 33 + BackendBuild: 31631 + BackendQFE: 0 + Version: 'Microsoft (R) Optimizing Compiler' + - Name: '.text$mn' + Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ] + Alignment: 16 + SectionData: C20000CCCCCCCCCCCCCCCCCCCCCCCCCCC20000CCCCCCCCCCCCCCCCCCCCCCCCCCC20000 + - Name: .chks64 + Characteristics: [ IMAGE_SCN_LNK_INFO, IMAGE_SCN_LNK_REMOVE ] + SectionData: D8FCB3B2D22EA6A98C9353D17E359992135E943A2071A7060000000000000000 +symbols: + - Name: '@comp.id' + Value: 17136527 + SectionNumber: -1 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_STATIC + - Name: '@feat.00' + Value: 2147549584 + SectionNumber: -1 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_STATIC + - Name: '@vol.md' + Value: 2 + SectionNumber: -1 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_STATIC + - 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: '.debug$S' + Value: 0 + SectionNumber: 2 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_STATIC + SectionDefinition: + Length: 116 + NumberOfRelocations: 0 + NumberOfLinenumbers: 0 + CheckSum: 0 + Number: 0 + - Name: '.text$mn' + Value: 0 + SectionNumber: 3 + 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: 3 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_FUNCTION + StorageClass: IMAGE_SYM_CLASS_EXTERNAL + - Name: '?bar@@YAXXZ' + Value: 16 + SectionNumber: 3 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_FUNCTION + StorageClass: IMAGE_SYM_CLASS_EXTERNAL + - Name: '?baz@@YAXXZ' + Value: 32 + SectionNumber: 3 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_FUNCTION + StorageClass: IMAGE_SYM_CLASS_EXTERNAL + - Name: .chks64 + Value: 0 + SectionNumber: 4 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_STATIC + SectionDefinition: + Length: 32 + NumberOfRelocations: 0 + NumberOfLinenumbers: 0 + CheckSum: 0 + Number: 0 +...