DLL files are in the same format as executables but they have export tables.
The format of the export table is described in PE/COFF spec section 5.3.
A new class, EdataContents, takes care of creating chunks for export tables.
What we need to do is to parse command line flags for dllexports, and then
instantiate the class to create chunks. For the writer, export table chunks
are opaque data -- it just add chunks to .edata section.
llvm-svn: 239869
using llvm::COFF::WindowsSubsystem;
using llvm::StringRef;
+class Defined;
-class Configuration {
-public:
+// Represents an /export option.
+struct Export {
+ StringRef Name;
+ StringRef ExtName;
+ Defined *Sym = nullptr;
+ uint16_t Ordinal = 0;
+ bool Noname = false;
+ bool Data = false;
+ bool Private = false;
+};
+
+// Global configuration.
+struct Configuration {
llvm::COFF::MachineTypes MachineType = llvm::COFF::IMAGE_FILE_MACHINE_AMD64;
bool Verbose = false;
WindowsSubsystem Subsystem = llvm::COFF::IMAGE_SUBSYSTEM_UNKNOWN;
std::set<StringRef> NoDefaultLibs;
bool NoDefaultLibAll = false;
+ // True if we are creating a DLL.
+ bool DLL = false;
+ std::vector<Export> Exports;
+
// Used by /failifmismatch option.
std::map<StringRef, StringRef> MustMatch;
#include "llvm/ADT/STLExtras.h"
#include "llvm/Object/COFF.h"
#include "llvm/Support/Endian.h"
+#include "llvm/Support/Path.h"
using namespace llvm;
using namespace llvm::object;
using llvm::RoundUpToAlignment;
static const size_t LookupChunkSize = sizeof(uint64_t);
-static const size_t DirectoryChunkSize = sizeof(ImportDirectoryTableEntry);
namespace lld {
namespace coff {
+// Import table
+
// A chunk for the import descriptor table.
class HintNameChunk : public Chunk {
public:
};
// A chunk for the import descriptor table.
-class DirectoryChunk : public Chunk {
+class ImportDirectoryChunk : public Chunk {
public:
- explicit DirectoryChunk(Chunk *N) : DLLName(N) {}
- size_t getSize() const override { return DirectoryChunkSize; }
+ explicit ImportDirectoryChunk(Chunk *N) : DLLName(N) {}
+ size_t getSize() const override { return sizeof(ImportDirectoryTableEntry); }
void writeTo(uint8_t *Buf) override {
auto *E = (coff_import_directory_table_entry *)(Buf + FileOff);
};
uint64_t IdataContents::getDirSize() {
- return Dirs.size() * DirectoryChunkSize;
+ return Dirs.size() * sizeof(ImportDirectoryTableEntry);
}
uint64_t IdataContents::getIATSize() {
// Create the import table header.
if (!DLLNames.count(Name))
DLLNames[Name] = make_unique<StringChunk>(Name);
- auto Dir = make_unique<DirectoryChunk>(DLLNames[Name].get());
+ auto Dir = make_unique<ImportDirectoryChunk>(DLLNames[Name].get());
Dir->LookupTab = Lookups[Base].get();
Dir->AddressTab = Addresses[Base].get();
Dirs.push_back(std::move(Dir));
}
// Add null terminator.
- Dirs.push_back(make_unique<NullChunk>(DirectoryChunkSize));
+ Dirs.push_back(make_unique<NullChunk>(sizeof(ImportDirectoryTableEntry)));
+}
+
+// Export table
+// Read Microsoft PE/COFF spec 5.3 for details.
+
+// A chunk for the export descriptor table.
+class ExportDirectoryChunk : public Chunk {
+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) {}
+
+ size_t getSize() const override {
+ return sizeof(export_directory_table_entry);
+ }
+
+ void writeTo(uint8_t *Buf) override {
+ auto *E = (export_directory_table_entry *)(Buf + FileOff);
+ E->NameRVA = DLLName->getRVA();
+ E->OrdinalBase = 0;
+ E->AddressTableEntries = MaxOrdinal + 1;
+ E->NumberOfNamePointers = NameTabSize;
+ E->ExportAddressTableRVA = AddressTab->getRVA();
+ E->NamePointerRVA = NameTab->getRVA();
+ E->OrdinalTableRVA = OrdinalTab->getRVA();
+ }
+
+ uint16_t MaxOrdinal;
+ uint16_t NameTabSize;
+ Chunk *DLLName;
+ Chunk *AddressTab;
+ Chunk *NameTab;
+ Chunk *OrdinalTab;
+};
+
+class AddressTableChunk : public Chunk {
+public:
+ explicit AddressTableChunk(size_t MaxOrdinal) : Size(MaxOrdinal + 1) {}
+ size_t getSize() const override { return Size * 4; }
+
+ void writeTo(uint8_t *Buf) override {
+ for (Export &E : Config->Exports)
+ write32le(Buf + FileOff + E.Ordinal * 4, E.Sym->getRVA());
+ }
+
+private:
+ size_t Size;
+};
+
+class NamePointersChunk : public Chunk {
+public:
+ explicit NamePointersChunk(std::vector<Chunk *> &V) : Chunks(V) {}
+ size_t getSize() const override { return Chunks.size() * 4; }
+
+ void writeTo(uint8_t *Buf) override {
+ uint8_t *P = Buf + FileOff;
+ for (Chunk *C : Chunks) {
+ write32le(P, C->getRVA());
+ P += 4;
+ }
+ }
+
+private:
+ std::vector<Chunk *> Chunks;
+};
+
+class ExportOrdinalChunk : public Chunk {
+public:
+ explicit ExportOrdinalChunk(size_t I) : Size(I) {}
+ size_t getSize() const override { return Size * 2; }
+
+ void writeTo(uint8_t *Buf) override {
+ uint8_t *P = Buf + FileOff;
+ for (Export &E : Config->Exports) {
+ if (E.Noname)
+ continue;
+ write16le(P, E.Ordinal);
+ P += 2;
+ }
+ }
+
+private:
+ size_t Size;
+};
+
+EdataContents::EdataContents() {
+ uint16_t MaxOrdinal = 0;
+ for (Export &E : Config->Exports)
+ MaxOrdinal = std::max(MaxOrdinal, E.Ordinal);
+
+ auto *DLLName = new StringChunk(sys::path::filename(Config->OutputFile));
+ auto *AddressTab = new AddressTableChunk(MaxOrdinal);
+ std::vector<Chunk *> Names;
+ for (Export &E : Config->Exports)
+ if (!E.Noname)
+ Names.push_back(new StringChunk(E.ExtName));
+ auto *NameTab = new NamePointersChunk(Names);
+ auto *OrdinalTab = new ExportOrdinalChunk(Names.size());
+ auto *Dir = new ExportDirectoryChunk(MaxOrdinal, Names.size(), DLLName,
+ AddressTab, NameTab, OrdinalTab);
+ Chunks.push_back(std::unique_ptr<Chunk>(Dir));
+ Chunks.push_back(std::unique_ptr<Chunk>(DLLName));
+ Chunks.push_back(std::unique_ptr<Chunk>(AddressTab));
+ Chunks.push_back(std::unique_ptr<Chunk>(NameTab));
+ Chunks.push_back(std::unique_ptr<Chunk>(OrdinalTab));
+ for (Chunk *C : Names)
+ Chunks.push_back(std::unique_ptr<Chunk>(C));
}
} // namespace coff
namespace coff {
// Windows-specific.
-// IdataContents creates all chunks for the .idata section.
+// IdataContents creates all chunks for the DLL import table.
// You are supposed to call add() to add symbols and then
// call getChunks() to get a list of chunks.
class IdataContents {
std::map<StringRef, std::unique_ptr<Chunk>> DLLNames;
};
+// Windows-specific.
+// EdataContents creates all chunks for the DLL export table.
+class EdataContents {
+public:
+ EdataContents();
+ std::vector<std::unique_ptr<Chunk>> Chunks;
+};
+
} // namespace coff
} // namespace lld
return EC;
std::unique_ptr<llvm::opt::InputArgList> Args = std::move(ArgsOrErr.get());
+ // Handle /export
+ for (auto *Arg : Args->filtered(OPT_export)) {
+ ErrorOr<Export> E = parseExport(Arg->getValue());
+ if (auto EC = E.getError())
+ return EC;
+ Config->Exports.push_back(E.get());
+ }
+
// Handle /failifmismatch
if (auto EC = checkFailIfMismatch(Args.get()))
return EC;
return Ret;
}
+static WindowsSubsystem inferSubsystem() {
+ if (Config->DLL)
+ return IMAGE_SUBSYSTEM_WINDOWS_GUI;
+ return StringSwitch<WindowsSubsystem>(Config->EntryName)
+ .Case("mainCRTStartup", IMAGE_SUBSYSTEM_WINDOWS_CUI)
+ .Case("wmainCRTStartup", IMAGE_SUBSYSTEM_WINDOWS_CUI)
+ .Case("WinMainCRTStartup", IMAGE_SUBSYSTEM_WINDOWS_GUI)
+ .Case("wWinMainCRTStartup", IMAGE_SUBSYSTEM_WINDOWS_GUI)
+ .Default(IMAGE_SUBSYSTEM_UNKNOWN);
+}
+
bool LinkerDriver::link(int Argc, const char *Argv[]) {
// Needed for LTO.
llvm::InitializeAllTargetInfos();
if (Args->hasArg(OPT_verbose))
Config->Verbose = true;
+ // Handle /dll
+ if (Args->hasArg(OPT_dll))
+ Config->DLL = true;
+
// Handle /entry
if (auto *Arg = Args->getLastArg(OPT_entry))
Config->EntryName = Arg->getValue();
}
}
+ // Handle /export
+ for (auto *Arg : Args->filtered(OPT_export)) {
+ ErrorOr<Export> E = parseExport(Arg->getValue());
+ if (E.getError())
+ return false;
+ Config->Exports.push_back(E.get());
+ }
+
// Handle /failifmismatch
if (auto EC = checkFailIfMismatch(Args.get())) {
llvm::errs() << "/failifmismatch: " << EC.message() << "\n";
}
}
+ // Windows specific -- Make sure we resolve all dllexported symbols.
+ // (We don't cache the size here because Symtab.resolve() may add
+ // new entries to Config->Exports.)
+ for (size_t I = 0; I < Config->Exports.size(); ++I) {
+ StringRef Sym = Config->Exports[I].Name;
+ Symtab.addUndefined(Sym);
+ Config->GCRoots.insert(Sym);
+ }
+
// Windows specific -- If entry point name is not given, we need to
// infer that from user-defined entry name. The symbol table takes
// care of details.
// Windows specific -- if no /subsystem is given, we need to infer
// that from entry point name.
if (Config->Subsystem == IMAGE_SUBSYSTEM_UNKNOWN) {
- Config->Subsystem =
- StringSwitch<WindowsSubsystem>(Config->EntryName)
- .Case("mainCRTStartup", IMAGE_SUBSYSTEM_WINDOWS_CUI)
- .Case("wmainCRTStartup", IMAGE_SUBSYSTEM_WINDOWS_CUI)
- .Case("WinMainCRTStartup", IMAGE_SUBSYSTEM_WINDOWS_GUI)
- .Case("wWinMainCRTStartup", IMAGE_SUBSYSTEM_WINDOWS_GUI)
- .Default(IMAGE_SUBSYSTEM_UNKNOWN);
+ Config->Subsystem = inferSubsystem();
if (Config->Subsystem == IMAGE_SUBSYSTEM_UNKNOWN) {
llvm::errs() << "subsystem must be defined\n";
return false;
}
}
+ // Windows specific -- fix up dllexported symbols.
+ if (!Config->Exports.empty()) {
+ for (Export &E : Config->Exports)
+ E.Sym = Symtab.find(E.Name);
+ if (fixupExports())
+ return false;
+ }
+
// Write the result.
Writer Out(&Symtab);
if (auto EC = Out.write(Config->OutputFile)) {
#ifndef LLD_COFF_DRIVER_H
#define LLD_COFF_DRIVER_H
+#include "Config.h"
#include "lld/Core/LLVM.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringRef.h"
std::error_code parseSubsystem(StringRef Arg, WindowsSubsystem *Sys,
uint32_t *Major, uint32_t *Minor);
+// Used for dllexported symbols.
+ErrorOr<Export> parseExport(StringRef Arg);
+std::error_code fixupExports();
+
// Parses a string in the form of "key=value" and check
// if value matches previous values for the key.
// This feature used in the directive section to reject
return std::error_code();
}
+// Parse a string in the form of
+// "<name>[=<internalname>][,@ordinal[,NONAME]][,DATA][,PRIVATE]".
+// Used for parsing /export arguments.
+ErrorOr<Export> parseExport(StringRef Arg) {
+ Export E;
+ StringRef Rest;
+ std::tie(E.Name, Rest) = Arg.split(",");
+ if (E.Name.empty())
+ goto err;
+ if (E.Name.find('=') != StringRef::npos) {
+ std::tie(E.ExtName, E.Name) = E.Name.split("=");
+ if (E.Name.empty())
+ goto err;
+ } else {
+ E.ExtName = E.Name;
+ }
+
+ while (!Rest.empty()) {
+ StringRef Tok;
+ std::tie(Tok, Rest) = Rest.split(",");
+ if (Tok.equals_lower("noname")) {
+ if (E.Ordinal == 0)
+ goto err;
+ E.Noname = true;
+ continue;
+ }
+ if (Tok.equals_lower("data")) {
+ E.Data = true;
+ continue;
+ }
+ if (Tok.equals_lower("private")) {
+ E.Private = true;
+ continue;
+ }
+ if (Tok.startswith("@")) {
+ int32_t Ord;
+ if (Tok.substr(1).getAsInteger(0, Ord))
+ goto err;
+ if (Ord <= 0 || 65535 < Ord)
+ goto err;
+ E.Ordinal = Ord;
+ continue;
+ }
+ goto err;
+ }
+ return E;
+
+err:
+ llvm::errs() << "invalid /export: " << Arg << "\n";
+ return make_error_code(LLDError::InvalidOption);
+}
+
+// Performs error checking on all /export arguments.
+// It also sets ordinals.
+std::error_code fixupExports() {
+ // Symbol ordinals must be unique.
+ std::set<uint16_t> Ords;
+ for (Export &E : Config->Exports) {
+ if (E.Ordinal == 0)
+ continue;
+ if (!Ords.insert(E.Ordinal).second) {
+ llvm::errs() << "duplicate export ordinal: " << E.Name << "\n";
+ return make_error_code(LLDError::InvalidOption);
+ }
+ }
+
+ // Uniquefy by name.
+ std::set<StringRef> Names;
+ std::vector<Export> V;
+ for (Export &E : Config->Exports) {
+ if (!Names.insert(E.Name).second) {
+ llvm::errs() << "warning: duplicate /export option: " << E.Name << "\n";
+ continue;
+ }
+ V.push_back(E);
+ }
+ Config->Exports = std::move(V);
+
+ // Sort by name.
+ std::sort(
+ Config->Exports.begin(), Config->Exports.end(),
+ [](const Export &A, const Export &B) { return A.ExtName < B.ExtName; });
+
+ // Assign unique ordinals if default (= 0).
+ uint16_t Max = 0;
+ for (Export &E : Config->Exports)
+ Max = std::max(Max, E.Ordinal);
+ for (Export &E : Config->Exports)
+ if (E.Ordinal == 0)
+ E.Ordinal = ++Max;
+ return std::error_code();
+}
+
// Parses a string in the form of "key=value" and check
// if value matches previous values for the same key.
std::error_code checkFailIfMismatch(llvm::opt::InputArgList *Args) {
// Windows specific -- Link default entry point name.
ErrorOr<StringRef> SymbolTable::findDefaultEntry() {
+ // If it's DLL, the rule is easy.
+ if (Config->DLL) {
+ StringRef Sym = "_DllMainCRTStartup";
+ if (auto EC = resolve(new (Alloc) Undefined(Sym)))
+ return EC;
+ return Sym;
+ }
+
// User-defined main functions and their corresponding entry points.
static const char *Entries[][2] = {
{"main", "mainCRTStartup"},
markLive();
createSections();
createImportTables();
+ createExportTable();
if (Config->Relocatable)
createSection(".reloc");
assignAddresses();
Sec->addChunk(C);
}
+void Writer::createExportTable() {
+ if (Config->Exports.empty())
+ return;
+ Edata.reset(new EdataContents());
+ OutputSection *Sec = createSection(".edata");
+ for (std::unique_ptr<Chunk> &C : Edata->Chunks)
+ Sec->addChunk(C.get());
+}
+
// The Windows loader doesn't seem to like empty sections,
// so we remove them if any.
void Writer::removeEmptySections() {
COFF->NumberOfSections = OutputSections.size();
COFF->Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE;
COFF->Characteristics |= IMAGE_FILE_LARGE_ADDRESS_AWARE;
+ if (Config->DLL)
+ COFF->Characteristics |= IMAGE_FILE_DLL;
if (!Config->Relocatable)
COFF->Characteristics |= IMAGE_FILE_RELOCS_STRIPPED;
COFF->SizeOfOptionalHeader =
// Write data directory
auto *DataDirectory = reinterpret_cast<data_directory *>(Buf);
Buf += sizeof(*DataDirectory) * NumberfOfDataDirectory;
+ if (OutputSection *Sec = findSection(".edata")) {
+ DataDirectory[EXPORT_TABLE].RelativeVirtualAddress = Sec->getRVA();
+ DataDirectory[EXPORT_TABLE].Size = Sec->getVirtualSize();
+ }
if (Idata) {
DataDirectory[IMPORT_TABLE].RelativeVirtualAddress = Idata->getDirRVA();
DataDirectory[IMPORT_TABLE].Size = Idata->getDirSize();
.Case(".bss", BSS | R | W)
.Case(".data", DATA | R | W)
.Case(".didat", DATA | R)
+ .Case(".edata", DATA | R)
.Case(".idata", DATA | R)
.Case(".rdata", DATA | R)
.Case(".reloc", DATA | DISCARDABLE | R)
void markLive();
void createSections();
void createImportTables();
+ void createExportTable();
void assignAddresses();
void removeEmptySections();
std::error_code openFile(StringRef OutputPath);
llvm::SpecificBumpPtrAllocator<BaserelChunk> BAlloc;
std::vector<OutputSection *> OutputSections;
std::unique_ptr<IdataContents> Idata;
+ std::unique_ptr<EdataContents> Edata;
uint64_t FileSize;
uint64_t SizeOfImage;
--- /dev/null
+---
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: []
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ Alignment: 4
+ SectionData: B800000000506800000000680000000050E80000000050E800000000
+ - Name: .drectve
+ Characteristics: [ IMAGE_SCN_LNK_INFO, IMAGE_SCN_LNK_REMOVE ]
+ Alignment: 2147483648
+ SectionData: 2f6578706f72743a6578706f7274666e3300 # /export:exportfn3
+symbols:
+ - Name: .text
+ Value: 0
+ SectionNumber: 1
+ SimpleType: IMAGE_SYM_TYPE_NULL
+ ComplexType: IMAGE_SYM_DTYPE_NULL
+ StorageClass: IMAGE_SYM_CLASS_STATIC
+ SectionDefinition:
+ Length: 28
+ NumberOfRelocations: 4
+ NumberOfLinenumbers: 0
+ CheckSum: 0
+ Number: 0
+ - Name: _DllMainCRTStartup
+ Value: 0
+ SectionNumber: 1
+ SimpleType: IMAGE_SYM_TYPE_NULL
+ ComplexType: IMAGE_SYM_DTYPE_NULL
+ StorageClass: IMAGE_SYM_CLASS_EXTERNAL
+ - Name: exportfn1
+ Value: 8
+ SectionNumber: 1
+ SimpleType: IMAGE_SYM_TYPE_NULL
+ ComplexType: IMAGE_SYM_DTYPE_NULL
+ StorageClass: IMAGE_SYM_CLASS_EXTERNAL
+ - Name: exportfn2
+ Value: 16
+ SectionNumber: 1
+ SimpleType: IMAGE_SYM_TYPE_NULL
+ ComplexType: IMAGE_SYM_DTYPE_NULL
+ StorageClass: IMAGE_SYM_CLASS_EXTERNAL
+ - Name: exportfn3
+ Value: 16
+ SectionNumber: 1
+ SimpleType: IMAGE_SYM_TYPE_NULL
+ ComplexType: IMAGE_SYM_DTYPE_NULL
+ StorageClass: IMAGE_SYM_CLASS_EXTERNAL
+...
--- /dev/null
+# RUN: yaml2obj < %p/Inputs/export.yaml > %t.obj
+#
+# RUN: lld -flavor link2 /out:%t.dll /dll %t.obj /export:exportfn1 /export:exportfn2
+# RUN: llvm-objdump -p %t.dll | FileCheck -check-prefix=CHECK1 %s
+
+CHECK1: Export Table:
+CHECK1: DLL name: export.test.tmp.dll
+CHECK1: Ordinal RVA Name
+CHECK1-NEXT: 0 0
+CHECK1-NEXT: 1 0x1008 exportfn1
+CHECK1-NEXT: 2 0x1010 exportfn2
+
+# RUN: lld -flavor link2 /out:%t.dll /dll %t.obj /export:exportfn1,@5 /export:exportfn2
+# RUN: llvm-objdump -p %t.dll | FileCheck -check-prefix=CHECK2 %s
+
+CHECK2: Export Table:
+CHECK2: DLL name: export.test.tmp.dll
+CHECK2: Ordinal RVA Name
+CHECK2-NEXT: 0 0
+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
+
+# RUN: lld -flavor link2 /out:%t.dll /dll %t.obj /export:exportfn1,@5,noname /export:exportfn2
+# RUN: llvm-objdump -p %t.dll | FileCheck -check-prefix=CHECK3 %s
+
+CHECK3: Export Table:
+CHECK3: DLL name: export.test.tmp.dll
+CHECK3: Ordinal RVA Name
+CHECK3-NEXT: 0 0
+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
+
+# RUN: lld -flavor link2 /out:%t.dll /dll %t.obj /export:f1=exportfn1 /export:f2@4=exportfn2
+# RUN: llvm-objdump -p %t.dll | FileCheck -check-prefix=CHECK4 %s
+
+CHECK4: Export Table:
+CHECK4: DLL name: export.test.tmp.dll
+CHECK4: Ordinal RVA Name
+CHECK4-NEXT: 0 0
+CHECK4-NEXT: 1 0x1010 exportfn3
+CHECK4-NEXT: 2 0x1008 f1
+CHECK4-NEXT: 3 0x1010 f2