From a77336bd5dc4eb2f3cf300a88c918aa2f7d4c108 Mon Sep 17 00:00:00 2001 From: Rui Ueyama Date: Sun, 21 Jun 2015 22:31:52 +0000 Subject: [PATCH] COFF: Support delay-load import tables. DLLs are usually resolved at process startup, but you can delay-load them by passing /delayload option to the linker. If a /delayload is specified, the linker has to create data which is similar to regular import table. One notable difference is that the pointers in a delay-load import table are originally pointing to thunks that resolves themselves. Each thunk loads a DLL, resolve its name, and then overwrites the pointer with the result so that subsequent function calls directly call a desired function. The linker has to emit thunks. llvm-svn: 240250 --- lld/COFF/Config.h | 1 + lld/COFF/DLL.cpp | 175 ++++++++++++++++++++++++++++++-- lld/COFF/DLL.h | 27 +++++ lld/COFF/Driver.cpp | 6 ++ lld/COFF/Writer.cpp | 55 ++++++---- lld/COFF/Writer.h | 5 +- lld/test/COFF/delayimports.test | 26 +++++ 7 files changed, 265 insertions(+), 30 deletions(-) create mode 100644 lld/test/COFF/delayimports.test diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h index 2614f07e3ef0..24ff8c68e57a 100644 --- a/lld/COFF/Config.h +++ b/lld/COFF/Config.h @@ -60,6 +60,7 @@ struct Configuration { bool DLL = false; StringRef Implib; std::vector Exports; + std::set DelayLoads; // Options for manifest files. ManifestKind Manifest = SideBySide; diff --git a/lld/COFF/DLL.cpp b/lld/COFF/DLL.cpp index 288558b75801..c8a2c7202b2a 100644 --- a/lld/COFF/DLL.cpp +++ b/lld/COFF/DLL.cpp @@ -7,8 +7,8 @@ // //===----------------------------------------------------------------------===// // -// This file defines various types of chunks for the DLL import -// descriptor table. They are inherently Windows-specific. +// This file defines various types of chunks for the DLL import or export +// descriptor tables. They are inherently Windows-specific. // You need to read Microsoft PE/COFF spec to understand details // about the data structures. // @@ -114,6 +114,7 @@ public: explicit NullChunk(size_t N) : Size(N) {} bool hasData() const override { return false; } size_t getSize() const override { return Size; } + void setAlign(size_t N) { Align = N; } private: size_t Size; @@ -149,24 +150,34 @@ std::vector IdataContents::getChunks() { return V; } -void IdataContents::create() { +static std::map> +binImports(const std::vector &Imports) { // Group DLL-imported symbols by DLL name because that's how // symbols are layed out in the import descriptor table. - std::map> Map; + std::map> M; for (DefinedImportData *Sym : Imports) - Map[Sym->getDLLName()].push_back(Sym); + M[Sym->getDLLName()].push_back(Sym); + + for (auto &P : M) { + // Sort symbols by name for each group. + std::vector &Syms = P.second; + std::sort(Syms.begin(), Syms.end(), + [](DefinedImportData *A, DefinedImportData *B) { + return A->getName() < B->getName(); + }); + } + return M; +} + +void IdataContents::create() { + std::map> Map = + binImports(Imports); // Create .idata contents for each DLL. for (auto &P : Map) { StringRef Name = P.first; std::vector &Syms = P.second; - // Sort symbols by name for each group. - std::sort(Syms.begin(), Syms.end(), - [](DefinedImportData *A, DefinedImportData *B) { - return A->getName() < B->getName(); - }); - // Create lookup and address tables. If they have external names, // we need to create HintName chunks to store the names. // If they don't (if they are import-by-ordinals), we store only @@ -203,6 +214,148 @@ void IdataContents::create() { Dirs.push_back(make_unique(sizeof(ImportDirectoryTableEntry))); } +// Export table +// See Microsoft PE/COFF spec 4.3 for details. + +// A chunk for the delay import descriptor table etnry. +class DelayDirectoryChunk : public Chunk { +public: + explicit DelayDirectoryChunk(Chunk *N) : DLLName(N) {} + + size_t getSize() const override { + return sizeof(delay_import_directory_table_entry); + } + + void writeTo(uint8_t *Buf) override { + auto *E = (delay_import_directory_table_entry *)(Buf + FileOff); + E->Name = DLLName->getRVA(); + E->ModuleHandle = ModuleHandle->getRVA(); + E->DelayImportAddressTable = AddressTab->getRVA(); + E->DelayImportNameTable = NameTab->getRVA(); + } + + Chunk *DLLName; + Chunk *ModuleHandle; + Chunk *AddressTab; + Chunk *NameTab; +}; + +// Initial contents for delay-loaded functions. +// This code calls __delayLoadHerper2 function to resolve a symbol +// and then overwrites its jump table slot with the result +// for subsequent function calls. +static const uint8_t Thunk[] = { + 0x51, // push rcx + 0x52, // push rdx + 0x41, 0x50, // push r8 + 0x41, 0x51, // push r9 + 0x48, 0x83, 0xEC, 0x48, // sub rsp, 48h + 0x66, 0x0F, 0x7F, 0x04, 0x24, // movdqa xmmword ptr [rsp], xmm0 + 0x66, 0x0F, 0x7F, 0x4C, 0x24, 0x10, // movdqa xmmword ptr [rsp+10h], xmm1 + 0x66, 0x0F, 0x7F, 0x54, 0x24, 0x20, // movdqa xmmword ptr [rsp+20h], xmm2 + 0x66, 0x0F, 0x7F, 0x5C, 0x24, 0x30, // movdqa xmmword ptr [rsp+30h], xmm3 + 0x48, 0x8D, 0x15, 0, 0, 0, 0, // lea rdx, [__imp_] + 0x48, 0x8D, 0x0D, 0, 0, 0, 0, // lea rcx, [___DELAY_IMPORT_...] + 0xE8, 0, 0, 0, 0, // call __delayLoadHelper2 + 0x66, 0x0F, 0x6F, 0x04, 0x24, // movdqa xmm0, xmmword ptr [rsp] + 0x66, 0x0F, 0x6F, 0x4C, 0x24, 0x10, // movdqa xmm1, xmmword ptr [rsp+10h] + 0x66, 0x0F, 0x6F, 0x54, 0x24, 0x20, // movdqa xmm2, xmmword ptr [rsp+20h] + 0x66, 0x0F, 0x6F, 0x5C, 0x24, 0x30, // movdqa xmm3, xmmword ptr [rsp+30h] + 0x48, 0x83, 0xC4, 0x48, // add rsp, 48h + 0x41, 0x59, // pop r9 + 0x41, 0x58, // pop r8 + 0x5A, // pop rdx + 0x59, // pop rcx + 0xFF, 0xE0, // jmp rax +}; + +// A chunk for the delay import thunk. +class ThunkChunk : public Chunk { +public: + ThunkChunk(Defined *I, Defined *H) : Imp(I), Helper(H) {} + + size_t getSize() const override { return sizeof(Thunk); } + + void writeTo(uint8_t *Buf) override { + memcpy(Buf + FileOff, Thunk, sizeof(Thunk)); + write32le(Buf + FileOff + 36, Imp->getRVA()); + write32le(Buf + FileOff + 43, Desc->getRVA()); + write32le(Buf + FileOff + 48, Helper->getRVA()); + } + + Defined *Imp = nullptr; + Chunk *Desc = nullptr; + Defined *Helper = nullptr; +}; + +std::vector DelayLoadContents::getChunks(Defined *H) { + Helper = H; + create(); + std::vector V; + for (std::unique_ptr &C : Dirs) + V.push_back(C.get()); + for (std::unique_ptr &C : Addresses) + V.push_back(C.get()); + for (std::unique_ptr &C : Names) + V.push_back(C.get()); + for (std::unique_ptr &C : HintNames) + V.push_back(C.get()); + for (auto &P : DLLNames) { + std::unique_ptr &C = P.second; + V.push_back(C.get()); + } + return V; +} + +uint64_t DelayLoadContents::getDirSize() { + return Dirs.size() * sizeof(delay_import_directory_table_entry); +} + +void DelayLoadContents::create() { + std::map> Map = + binImports(Imports); + + // Create .didat contents for each DLL. + for (auto &P : Map) { + StringRef Name = P.first; + std::vector &Syms = P.second; + + size_t Base = Addresses.size(); + for (DefinedImportData *S : Syms) { + auto T = make_unique(S, Helper); + auto A = make_unique(T.get()); + T->Desc = A.get(); + Addresses.push_back(std::move(A)); + Thunks.push_back(std::move(T)); + auto C = + make_unique(S->getExternalName(), S->getOrdinal()); + Names.push_back(make_unique(C.get())); + HintNames.push_back(std::move(C)); + } + // Terminate with null values. + Addresses.push_back(make_unique(LookupChunkSize)); + Names.push_back(make_unique(LookupChunkSize)); + + for (int I = 0, E = Syms.size(); I < E; ++I) + Syms[I]->setLocation(Addresses[Base + I].get()); + auto *MH = new NullChunk(8); + MH->setAlign(8); + ModuleHandles.push_back(std::unique_ptr(MH)); + + // Create the delay import table header. + if (!DLLNames.count(Name)) + DLLNames[Name] = make_unique(Name); + auto Dir = make_unique(DLLNames[Name].get()); + Dir->ModuleHandle = MH; + Dir->AddressTab = Addresses[Base].get(); + Dir->NameTab = Names[Base].get(); + Dirs.push_back(std::move(Dir)); + } + // Add null terminator. + Dirs.push_back( + make_unique(sizeof(delay_import_directory_table_entry))); +} + // Export table // Read Microsoft PE/COFF spec 5.3 for details. diff --git a/lld/COFF/DLL.h b/lld/COFF/DLL.h index 94995daa1c54..ac55366c5a59 100644 --- a/lld/COFF/DLL.h +++ b/lld/COFF/DLL.h @@ -23,6 +23,7 @@ namespace coff { class IdataContents { public: void add(DefinedImportData *Sym) { Imports.push_back(Sym); } + bool empty() { return Imports.empty(); } std::vector getChunks(); uint64_t getDirRVA() { return Dirs[0]->getRVA(); } @@ -41,6 +42,32 @@ private: std::map> DLLNames; }; +// Windows-specific. +// DelayLoadContents creates all chunks for the delay-load DLL import table. +class DelayLoadContents { +public: + void add(DefinedImportData *Sym) { Imports.push_back(Sym); } + bool empty() { return Imports.empty(); } + std::vector getChunks(Defined *Helper); + std::vector> &getDataChunks() { return ModuleHandles; } + std::vector> &getCodeChunks() { return Thunks; } + + uint64_t getDirRVA() { return Dirs[0]->getRVA(); } + uint64_t getDirSize(); + +private: + void create(); + Defined *Helper; + std::vector Imports; + std::vector> Dirs; + std::vector> ModuleHandles; + std::vector> Addresses; + std::vector> Names; + std::vector> HintNames; + std::vector> Thunks; + std::map> DLLNames; +}; + // Windows-specific. // EdataContents creates all chunks for the DLL export table. class EdataContents { diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index 4c4a5a9562c1..bd153daeba4e 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -378,6 +378,12 @@ bool LinkerDriver::link(llvm::ArrayRef ArgsArr) { Config->Exports.push_back(E.get()); } + // Handle /delayload + for (auto *Arg : Args->filtered(OPT_delayload)) { + Config->DelayLoads.insert(Arg->getValue()); + Config->Includes.insert("__delayLoadHelper2"); + } + // Handle /failifmismatch for (auto *Arg : Args->filtered(OPT_failifmismatch)) if (checkFailIfMismatch(Arg->getValue())) diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp index 19ffae20b46c..f7e935275e15 100644 --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -159,29 +159,45 @@ void Writer::createImportTables() { if (Symtab->ImportFiles.empty()) return; OutputSection *Text = createSection(".text"); - Idata.reset(new IdataContents()); for (std::unique_ptr &File : Symtab->ImportFiles) { - for (SymbolBody *Body : File->getSymbols()) { - if (auto *Import = dyn_cast(Body)) { - Idata->add(Import); + for (SymbolBody *B : File->getSymbols()) { + auto *Import = dyn_cast(B); + if (!Import) { + // Linker-created function thunks for DLL symbols are added to + // .text section. + Text->addChunk(cast(B)->getChunk()); continue; } - // Linker-created function thunks for DLL symbols are added to - // .text section. - Text->addChunk(cast(Body)->getChunk()); + if (Config->DelayLoads.count(Import->getDLLName())) { + DelayIdata.add(Import); + } else { + Idata.add(Import); + } } } - OutputSection *Sec = createSection(".idata"); - for (Chunk *C : Idata->getChunks()) - Sec->addChunk(C); + if (!Idata.empty()) { + OutputSection *Sec = createSection(".idata"); + for (Chunk *C : Idata.getChunks()) + Sec->addChunk(C); + } + if (!DelayIdata.empty()) { + OutputSection *Sec = createSection(".didat"); + for (Chunk *C : DelayIdata.getChunks(Symtab->find("__delayLoadHelper2"))) + Sec->addChunk(C); + Sec = createSection(".text"); + for (std::unique_ptr &C : DelayIdata.getCodeChunks()) + Sec->addChunk(C.get()); + Sec = createSection(".data"); + for (std::unique_ptr &C : DelayIdata.getDataChunks()) + Sec->addChunk(C.get()); + } } void Writer::createExportTable() { if (Config->Exports.empty()) return; - Edata.reset(new EdataContents()); OutputSection *Sec = createSection(".edata"); - for (std::unique_ptr &C : Edata->Chunks) + for (std::unique_ptr &C : Edata.Chunks) Sec->addChunk(C.get()); } @@ -309,11 +325,16 @@ void Writer::writeHeader() { Dir[EXPORT_TABLE].RelativeVirtualAddress = Sec->getRVA(); Dir[EXPORT_TABLE].Size = Sec->getVirtualSize(); } - if (Idata) { - Dir[IMPORT_TABLE].RelativeVirtualAddress = Idata->getDirRVA(); - Dir[IMPORT_TABLE].Size = Idata->getDirSize(); - Dir[IAT].RelativeVirtualAddress = Idata->getIATRVA(); - Dir[IAT].Size = Idata->getIATSize(); + if (!Idata.empty()) { + Dir[IMPORT_TABLE].RelativeVirtualAddress = Idata.getDirRVA(); + Dir[IMPORT_TABLE].Size = Idata.getDirSize(); + Dir[IAT].RelativeVirtualAddress = Idata.getIATRVA(); + Dir[IAT].Size = Idata.getIATSize(); + } + if (!DelayIdata.empty()) { + Dir[DELAY_IMPORT_DESCRIPTOR].RelativeVirtualAddress = + DelayIdata.getDirRVA(); + Dir[DELAY_IMPORT_DESCRIPTOR].Size = DelayIdata.getDirSize(); } if (OutputSection *Sec = findSection(".rsrc")) { Dir[RESOURCE_TABLE].RelativeVirtualAddress = Sec->getRVA(); diff --git a/lld/COFF/Writer.h b/lld/COFF/Writer.h index a23dc2f3fbab..ccd40a95a090 100644 --- a/lld/COFF/Writer.h +++ b/lld/COFF/Writer.h @@ -100,8 +100,9 @@ private: llvm::SpecificBumpPtrAllocator CAlloc; llvm::SpecificBumpPtrAllocator BAlloc; std::vector OutputSections; - std::unique_ptr Idata; - std::unique_ptr Edata; + IdataContents Idata; + DelayLoadContents DelayIdata; + EdataContents Edata; uint64_t FileSize; uint64_t SizeOfImage; diff --git a/lld/test/COFF/delayimports.test b/lld/test/COFF/delayimports.test new file mode 100644 index 000000000000..5dff32a9ddab --- /dev/null +++ b/lld/test/COFF/delayimports.test @@ -0,0 +1,26 @@ +# RUN: lld -flavor link2 /out:%t.exe /entry:main /subsystem:console \ +# RUN: %p/Inputs/hello64.obj %p/Inputs/std64.lib /delayload:std64.dll \ +# RUN: /alternatename:__delayLoadHelper2=main +# RUN: llvm-readobj -coff-imports %t.exe | FileCheck %s + +CHECK: DelayImport { +CHECK-NEXT: Name: std64.dll +CHECK-NEXT: Attributes: 0x0 +CHECK-NEXT: ModuleHandle: 0x1018 +CHECK-NEXT: ImportAddressTable: 0x3040 +CHECK-NEXT: ImportNameTable: 0x3060 +CHECK-NEXT: BoundDelayImportTable: 0x0 +CHECK-NEXT: UnloadDelayImportTable: 0x0 +CHECK-NEXT: Import { +CHECK-NEXT: Symbol: ExitProcess (0) +CHECK-NEXT: Address: 0x2045 +CHECK-NEXT: } +CHECK-NEXT: Import { +CHECK-NEXT: Symbol: (50) +CHECK-NEXT: Address: 0x209C +CHECK-NEXT: } +CHECK-NEXT: Import { +CHECK-NEXT: Symbol: MessageBoxA (1) +CHECK-NEXT: Address: 0x20F3 +CHECK-NEXT: } +CHECK-NEXT: }