Index: COFF/DLL.h =================================================================== --- COFF/DLL.h +++ COFF/DLL.h @@ -19,19 +19,12 @@ // Windows-specific. // 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. +// call create() to populate the chunk vectors. 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(); } - uint64_t getDirSize(); - uint64_t getIATRVA() { return Addresses[0]->getRVA(); } - uint64_t getIATSize(); - -private: void create(); std::vector Imports; Index: COFF/DLL.cpp =================================================================== --- COFF/DLL.cpp +++ COFF/DLL.cpp @@ -470,30 +470,6 @@ } // anonymous namespace -uint64_t IdataContents::getDirSize() { - return Dirs.size() * sizeof(ImportDirectoryTableEntry); -} - -uint64_t IdataContents::getIATSize() { - return Addresses.size() * ptrSize(); -} - -// Returns a list of .idata contents. -// See Microsoft PE/COFF spec 5.4 for details. -std::vector IdataContents::getChunks() { - create(); - - // The loader assumes a specific order of data. - // Add each type in the correct order. - std::vector V; - V.insert(V.end(), Dirs.begin(), Dirs.end()); - V.insert(V.end(), Lookups.begin(), Lookups.end()); - V.insert(V.end(), Addresses.begin(), Addresses.end()); - V.insert(V.end(), Hints.begin(), Hints.end()); - V.insert(V.end(), DLLNames.begin(), DLLNames.end()); - return V; -} - void IdataContents::create() { std::vector> V = binImports(Imports); Index: COFF/Writer.cpp =================================================================== --- COFF/Writer.cpp +++ COFF/Writer.cpp @@ -167,6 +167,7 @@ void createSections(); void createMiscChunks(); void createImportTables(); + void appendImportThunks(); void createExportTable(); void mergeSections(); void assignAddresses(); @@ -203,6 +204,10 @@ std::vector Strtab; std::vector OutputSymtab; IdataContents Idata; + Chunk *ImportTableStart = nullptr; + uint64_t ImportTableSize = 0; + Chunk *IATStart = nullptr; + uint64_t IATSize = 0; DelayLoadContents DelayIdata; EdataContents Edata; bool SetNoSEHCharacteristic = false; @@ -296,9 +301,10 @@ void Writer::run() { ScopedTimer T1(CodeLayoutTimer); + createImportTables(); createSections(); createMiscChunks(); - createImportTables(); + appendImportThunks(); createExportTable(); mergeSections(); assignAddresses(); @@ -357,6 +363,67 @@ }); } +// Sort concrete section chunks from GNU import libraries. +// +// GNU binutils doesn't use short import files, but instead produces import +// libraries that consist of object files, with section chunks for the .idata$* +// sections. These are linked just as regular static libraries. Each import +// library consists of one header object, one object file for every imported +// symbol, and one trailer object. In order for the .idata tables/lists to +// be formed correctly, the section chunks within each .idata$* section need +// to be grouped by library, and sorted alphabetically within each library +// (which makes sure the header comes first and the trailer last). +static bool fixGnuImportChunks( + std::map, std::vector> &Map) { + uint32_t RDATA = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ; + + // Make sure all .idata$* section chunks are mapped as RDATA in order to + // be sorted into the same sections as our own synthesized .idata chunks. + for (auto &Pair : Map) { + const StringRef &SectionName = Pair.first.first; + uint32_t OutChars = Pair.first.second; + if (!SectionName.startswith(".idata")) + continue; + if (OutChars == RDATA) + continue; + std::vector &SrcVect = Pair.second; + std::vector &DestVect = Map[{SectionName, RDATA}]; + DestVect.insert(DestVect.end(), SrcVect.begin(), SrcVect.end()); + SrcVect.clear(); + } + + bool HasIdata = false; + // Sort all .idata$* chunks, grouping chunks from the same library, + // with alphabetical ordering of the object fils within a library. + for (auto &Pair : Map) { + const StringRef &SectionName = Pair.first.first; + if (!SectionName.startswith(".idata")) + continue; + + std::vector &Chunks = Pair.second; + if (!Chunks.empty()) + HasIdata = true; + std::stable_sort(Chunks.begin(), Chunks.end(), [&](Chunk *S, Chunk *T) { + SectionChunk *SC1 = dyn_cast_or_null(S); + SectionChunk *SC2 = dyn_cast_or_null(T); + if (!SC1 || !SC2) { + // if SC1, order them ascending. If SC2 or both null, + // S is not less than T. + return SC1 != nullptr; + } + // Make a string with "libraryname/objectfile" for sorting, achieving + // both grouping by library and sorting of objects within a library, + // at once. + std::string Key1 = + (SC1->File->ParentName + "/" + SC1->File->getName()).str(); + std::string Key2 = + (SC2->File->ParentName + "/" + SC2->File->getName()).str(); + return Key1 < Key2; + }); + } + return HasIdata; +} + // Create output section objects and add them to OutputSections. void Writer::createSections() { // First, create the builtin sections. @@ -405,11 +472,48 @@ Map[{C->getSectionName(), C->getOutputCharacteristics()}].push_back(C); } + // Even in non MinGW cases, we might need to link against GNU import + // libraries. + bool HasIdata = fixGnuImportChunks(Map); + + if (!Idata.empty() || HasIdata) { + // Even if Idata contains no imports, we need to create and add it since + // it provides the terminator for the descriptors in .idata$2. + Idata.create(); + // Add the .idata content in the right section groups, to allow + // chunks from other linked in object files to be grouped together. + // See Microsoft PE/COFF spec 5.4 for details. + auto Add = [&](StringRef N, std::vector &V) { + std::vector &DestVect = Map[{N, DATA | R}]; + DestVect.insert(DestVect.end(), V.begin(), V.end()); + }; + // The loader assumes a specific order of data. + // Add each type in the correct order. + Add(".idata$2", Idata.Dirs); + Add(".idata$4", Idata.Lookups); + Add(".idata$5", Idata.Addresses); + Add(".idata$6", Idata.Hints); + Add(".idata$7", Idata.DLLNames); + } + // Process an /order option. if (!Config->Order.empty()) for (auto &Pair : Map) sortBySectionOrder(Pair.second); + if (!Idata.empty() || HasIdata) { + std::vector &ImportTables = Map[{".idata$2", DATA | R}]; + ImportTableStart = ImportTables.front(); + for (Chunk *C : ImportTables) + ImportTableSize += C->getSize(); + + std::vector &IAT = Map[{".idata$5", DATA | R}]; + if (!IAT.empty()) + IATStart = IAT.front(); + for (Chunk *C : IAT) + IATSize += C->getSize(); + } + // Then create an OutputSection for each section. // '$' and all following characters in input section names are // discarded when determining output section. So, .text$foo @@ -500,9 +604,6 @@ // IdataContents class abstracted away the details for us, // so we just let it create chunks and add them to the section. void Writer::createImportTables() { - if (ImportFile::Instances.empty()) - return; - // Initialize DLLOrder so that import entries are ordered in // the same order as in the command line. (That affects DLL // initialization order, and this ordering is MSVC-compatible.) @@ -514,14 +615,6 @@ if (Config->DLLOrder.count(DLL) == 0) Config->DLLOrder[DLL] = Config->DLLOrder.size(); - if (File->ThunkSym) { - if (!isa(File->ThunkSym)) - fatal(toString(*File->ThunkSym) + " was replaced"); - DefinedImportThunk *Thunk = cast(File->ThunkSym); - if (File->ThunkLive) - TextSec->addChunk(Thunk->getChunk()); - } - if (File->ImpSym && !isa(File->ImpSym)) fatal(toString(*File->ImpSym) + " was replaced"); DefinedImportData *ImpSym = cast_or_null(File->ImpSym); @@ -534,10 +627,25 @@ Idata.add(ImpSym); } } +} - if (!Idata.empty()) - for (Chunk *C : Idata.getChunks()) - IdataSec->addChunk(C); +void Writer::appendImportThunks() { + if (ImportFile::Instances.empty()) + return; + + for (ImportFile *File : ImportFile::Instances) { + if (!File->Live) + continue; + + if (!File->ThunkSym) + continue; + + if (!isa(File->ThunkSym)) + fatal(toString(*File->ThunkSym) + " was replaced"); + DefinedImportThunk *Thunk = cast(File->ThunkSym); + if (File->ThunkLive) + TextSec->addChunk(Thunk->getChunk()); + } if (!DelayIdata.empty()) { Defined *Helper = cast(Config->DelayLoadHelper); @@ -849,11 +957,13 @@ Dir[EXPORT_TABLE].RelativeVirtualAddress = Edata.getRVA(); Dir[EXPORT_TABLE].Size = Edata.getSize(); } - 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 (ImportTableStart) { + Dir[IMPORT_TABLE].RelativeVirtualAddress = ImportTableStart->getRVA(); + Dir[IMPORT_TABLE].Size = ImportTableSize; + } + if (IATStart) { + Dir[IAT].RelativeVirtualAddress = IATStart->getRVA(); + Dir[IAT].Size = IATSize; } if (RsrcSec->getVirtualSize()) { Dir[RESOURCE_TABLE].RelativeVirtualAddress = RsrcSec->getRVA(); Index: test/COFF/Inputs/gnu-implib-func.s =================================================================== --- /dev/null +++ test/COFF/Inputs/gnu-implib-func.s @@ -0,0 +1,27 @@ + .text + .global func + .global __imp_func +func: + jmp *__imp_func + + # The data that is emitted into .idata$7 here is isn't needed for + # the import data structures, but we need to emit something which + # produces a relocation against _head_test_lib, to pull in the + # header and trailer objects. + + .section .idata$7 + .rva _head_test_lib + + .section .idata$5 +__imp_func: + .rva .Lhint_name + .long 0 + + .section .idata$4 + .rva .Lhint_name + .long 0 + + .section .idata$6 +.Lhint_name: + .short 0 + .asciz "func" Index: test/COFF/Inputs/gnu-implib-head.s =================================================================== --- /dev/null +++ test/COFF/Inputs/gnu-implib-head.s @@ -0,0 +1,13 @@ + .section .idata$2 + .global _head_test_lib +_head_test_lib: + .rva hname + .long 0 + .long 0 + .rva __test_lib_iname + .rva fthunk + + .section .idata$5 +fthunk: + .section .idata$4 +hname: Index: test/COFF/Inputs/gnu-implib-tail.s =================================================================== --- /dev/null +++ test/COFF/Inputs/gnu-implib-tail.s @@ -0,0 +1,11 @@ + .section .idata$4 + .long 0 + .long 0 + .section .idata$5 + .long 0 + .long 0 + + .section .idata$7 + .global __test_lib_iname +__test_lib_iname: + .asciz "foo.dll" Index: test/COFF/imports-gnu-only.s =================================================================== --- /dev/null +++ test/COFF/imports-gnu-only.s @@ -0,0 +1,28 @@ +# REQUIRES: x86 +# +# RUN: llvm-mc -triple=x86_64-windows-gnu %p/Inputs/gnu-implib-head.s -filetype=obj -o %t-dabcdh.o +# RUN: llvm-mc -triple=x86_64-windows-gnu %p/Inputs/gnu-implib-func.s -filetype=obj -o %t-dabcds00000.o +# RUN: llvm-mc -triple=x86_64-windows-gnu %p/Inputs/gnu-implib-tail.s -filetype=obj -o %t-dabcdt.o +# RUN: rm -f %t-implib.a +# RUN: llvm-ar rcs %t-implib.a %t-dabcdh.o %t-dabcds00000.o %t-dabcdt.o +# RUN: llvm-mc -triple=x86_64-windows-gnu %s -filetype=obj -o %t.obj +# RUN: lld-link -out:%t.exe -entry:main -subsystem:console \ +# RUN: %t.obj %t-implib.a +# RUN: llvm-objdump -s %t.exe | FileCheck -check-prefix=DATA %s + + .text + .global main +main: + call func + ret + +# Check that the linker inserted the null terminating import descriptor, +# even if there were no normal import libraries, only gnu ones. + +# DATA: Contents of section .rdata: +# First import descriptor +# DATA: 140002000 28200000 00000000 00000000 53200000 +# Last word from first import descriptor, null terminator descriptor +# DATA: 140002010 38200000 00000000 00000000 00000000 +# Null terminator descriptor and import lookup table. +# DATA: 140002020 00000000 00000000 48200000 00000000 Index: test/COFF/imports-gnu.test =================================================================== --- /dev/null +++ test/COFF/imports-gnu.test @@ -0,0 +1,29 @@ +# REQUIRES: x86 +# Verify that the lld can link to GNU import libs. +# +# RUN: llvm-mc -triple=x86_64-windows-gnu %p/Inputs/gnu-implib-head.s -filetype=obj -o %t-dabcdh.o +# RUN: llvm-mc -triple=x86_64-windows-gnu %p/Inputs/gnu-implib-func.s -filetype=obj -o %t-dabcds00000.o +# RUN: llvm-mc -triple=x86_64-windows-gnu %p/Inputs/gnu-implib-tail.s -filetype=obj -o %t-dabcdt.o +# RUN: rm -f %t-implib.a +# RUN: llvm-ar rcs %t-implib.a %t-dabcdh.o %t-dabcds00000.o %t-dabcdt.o +# Not linking with -lldmingw; one can link to GNU import libs even if not targeting MinGW. +# RUN: lld-link -out:%t.exe -entry:main -subsystem:console \ +# RUN: %p/Inputs/hello64.obj %p/Inputs/std64.lib %t-implib.a -include:func +# RUN: llvm-readobj -coff-imports %t.exe | FileCheck -check-prefix=IMPORT %s + +# Check that import entries from both libraries show up. + +IMPORT: Import { +IMPORT-NEXT: Name: foo.dll +IMPORT-NEXT: ImportLookupTableRVA: +IMPORT-NEXT: ImportAddressTableRVA: +IMPORT-NEXT: Symbol: func (0) +IMPORT-NEXT: } +IMPORT-NEXT: Import { +IMPORT-NEXT: Name: std64.dll +IMPORT-NEXT: ImportLookupTableRVA: +IMPORT-NEXT: ImportAddressTableRVA: +IMPORT-NEXT: Symbol: ExitProcess (0) +IMPORT-NEXT: Symbol: (50) +IMPORT-NEXT: Symbol: MessageBoxA (1) +IMPORT-NEXT: } Index: test/COFF/no-idata.s =================================================================== --- /dev/null +++ test/COFF/no-idata.s @@ -0,0 +1,20 @@ +# REQUIRES: x86 +# +# RUN: llvm-mc -triple=x86_64-windows-gnu %s -filetype=obj -o %t.obj +# RUN: lld-link -out:%t.exe -entry:main -subsystem:console %t.obj +# RUN: llvm-objdump -s %t.exe | FileCheck -check-prefix=DUMP %s +# RUN: llvm-readobj -file-headers %t.exe | FileCheck -check-prefix=DIRECTORY %s + + .text + .global main +main: + ret + +# Check that no .idata (.rdata) entries were added, no null terminator +# for the import descriptor table. +# DUMP: Contents of section .text: +# DUMP-NEXT: 140001000 c3 +# DUMP-NEXT-EMPTY: + +# DIRECTORY: ImportTableRVA: 0x0 +# DIRECTORY: ImportTableSize: 0x0