[lld] r342777 - [COFF] Support linking to import libraries from GNU binutils

Martin Storsjo via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 21 15:01:07 PDT 2018


Author: mstorsjo
Date: Fri Sep 21 15:01:06 2018
New Revision: 342777

URL: http://llvm.org/viewvc/llvm-project?rev=342777&view=rev
Log:
[COFF] Support linking to import libraries from GNU binutils

GNU binutils import libraries aren't the same kind of short import
libraries as link.exe and LLD produce, but are a plain static library
containing .idata section chunks. MSVC link.exe can successfully link
to them.

In order for imports from GNU import libraries to mix properly with the
normal import chunks, the chunks from the existing mechanism needs to
be added into named sections like .idata$2.

These GNU import libraries consist of one header object, a number of
object files, one for each imported function/variable, and one trailer.
Within the import libraries, the object files are ordered alphabetically
in this order. The chunks stemming from these libraries have to be
grouped by what library they originate from and sorted, to make sure
the section chunks for headers and trailers for the lists are ordered
as intended. This is done on all sections named .idata$*, before adding
the synthesized chunks to them.

Differential Revision: https://reviews.llvm.org/D38513

Added:
    lld/trunk/test/COFF/Inputs/gnu-implib-func.s
    lld/trunk/test/COFF/Inputs/gnu-implib-head.s
    lld/trunk/test/COFF/Inputs/gnu-implib-tail.s
    lld/trunk/test/COFF/imports-gnu-only.s
    lld/trunk/test/COFF/imports-gnu.test
    lld/trunk/test/COFF/no-idata.s
Modified:
    lld/trunk/COFF/DLL.cpp
    lld/trunk/COFF/DLL.h
    lld/trunk/COFF/Writer.cpp

Modified: lld/trunk/COFF/DLL.cpp
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/DLL.cpp?rev=342777&r1=342776&r2=342777&view=diff
==============================================================================
--- lld/trunk/COFF/DLL.cpp (original)
+++ lld/trunk/COFF/DLL.cpp Fri Sep 21 15:01:06 2018
@@ -470,30 +470,6 @@ private:
 
 } // 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<Chunk *> IdataContents::getChunks() {
-  create();
-
-  // The loader assumes a specific order of data.
-  // Add each type in the correct order.
-  std::vector<Chunk *> 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<std::vector<DefinedImportData *>> V = binImports(Imports);
 

Modified: lld/trunk/COFF/DLL.h
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/DLL.h?rev=342777&r1=342776&r2=342777&view=diff
==============================================================================
--- lld/trunk/COFF/DLL.h (original)
+++ lld/trunk/COFF/DLL.h Fri Sep 21 15:01:06 2018
@@ -19,19 +19,12 @@ namespace coff {
 // 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<Chunk *> 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<DefinedImportData *> Imports;

Modified: lld/trunk/COFF/Writer.cpp
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/Writer.cpp?rev=342777&r1=342776&r2=342777&view=diff
==============================================================================
--- lld/trunk/COFF/Writer.cpp (original)
+++ lld/trunk/COFF/Writer.cpp Fri Sep 21 15:01:06 2018
@@ -167,6 +167,9 @@ private:
   void createSections();
   void createMiscChunks();
   void createImportTables();
+  void appendImportThunks();
+  void locateImportTables(
+      std::map<std::pair<StringRef, uint32_t>, std::vector<Chunk *>> &Map);
   void createExportTable();
   void mergeSections();
   void assignAddresses();
@@ -203,6 +206,10 @@ private:
   std::vector<char> Strtab;
   std::vector<llvm::object::coff_symbol16> 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 +303,10 @@ void OutputSection::writeHeaderTo(uint8_
 void Writer::run() {
   ScopedTimer T1(CodeLayoutTimer);
 
+  createImportTables();
   createSections();
   createMiscChunks();
-  createImportTables();
+  appendImportThunks();
   createExportTable();
   mergeSections();
   assignAddresses();
@@ -357,6 +365,110 @@ static void sortBySectionOrder(std::vect
                    });
 }
 
+// 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::pair<StringRef, uint32_t>, std::vector<Chunk *>> &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) {
+    StringRef SectionName = Pair.first.first;
+    uint32_t OutChars = Pair.first.second;
+    if (!SectionName.startswith(".idata"))
+      continue;
+    if (OutChars == RDATA)
+      continue;
+    std::vector<Chunk *> &SrcVect = Pair.second;
+    std::vector<Chunk *> &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) {
+    StringRef SectionName = Pair.first.first;
+    if (!SectionName.startswith(".idata"))
+      continue;
+
+    std::vector<Chunk *> &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<SectionChunk>(S);
+      SectionChunk *SC2 = dyn_cast_or_null<SectionChunk>(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;
+}
+
+// Add generated idata chunks, for imported symbols and DLLs, and a
+// terminator in .idata$2.
+static void addSyntheticIdata(
+    IdataContents &Idata,
+    std::map<std::pair<StringRef, uint32_t>, std::vector<Chunk *>> &Map) {
+  uint32_t RDATA = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ;
+  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<Chunk *> &V) {
+    std::vector<Chunk *> &DestVect = Map[{N, RDATA}];
+    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);
+}
+
+// Locate the first Chunk and size of the import directory list and the
+// IAT.
+void Writer::locateImportTables(
+    std::map<std::pair<StringRef, uint32_t>, std::vector<Chunk *>> &Map) {
+  uint32_t RDATA = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ;
+  std::vector<Chunk *> &ImportTables = Map[{".idata$2", RDATA}];
+  if (!ImportTables.empty())
+    ImportTableStart = ImportTables.front();
+  for (Chunk *C : ImportTables)
+    ImportTableSize += C->getSize();
+
+  std::vector<Chunk *> &IAT = Map[{".idata$5", RDATA}];
+  if (!IAT.empty())
+    IATStart = IAT.front();
+  for (Chunk *C : IAT)
+    IATSize += C->getSize();
+}
+
 // Create output section objects and add them to OutputSections.
 void Writer::createSections() {
   // First, create the builtin sections.
@@ -405,11 +517,23 @@ void Writer::createSections() {
     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 = true;
+
+  if (HasIdata)
+    addSyntheticIdata(Idata, Map);
+
   // Process an /order option.
   if (!Config->Order.empty())
     for (auto &Pair : Map)
       sortBySectionOrder(Pair.second);
 
+  if (HasIdata)
+    locateImportTables(Map);
+
   // 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 +624,6 @@ void Writer::createMiscChunks() {
 // 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 +635,6 @@ void Writer::createImportTables() {
     if (Config->DLLOrder.count(DLL) == 0)
       Config->DLLOrder[DLL] = Config->DLLOrder.size();
 
-    if (File->ThunkSym) {
-      if (!isa<DefinedImportThunk>(File->ThunkSym))
-        fatal(toString(*File->ThunkSym) + " was replaced");
-      DefinedImportThunk *Thunk = cast<DefinedImportThunk>(File->ThunkSym);
-      if (File->ThunkLive)
-        TextSec->addChunk(Thunk->getChunk());
-    }
-
     if (File->ImpSym && !isa<DefinedImportData>(File->ImpSym))
       fatal(toString(*File->ImpSym) + " was replaced");
     DefinedImportData *ImpSym = cast_or_null<DefinedImportData>(File->ImpSym);
@@ -534,10 +647,25 @@ void Writer::createImportTables() {
       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<DefinedImportThunk>(File->ThunkSym))
+      fatal(toString(*File->ThunkSym) + " was replaced");
+    DefinedImportThunk *Thunk = cast<DefinedImportThunk>(File->ThunkSym);
+    if (File->ThunkLive)
+      TextSec->addChunk(Thunk->getChunk());
+  }
 
   if (!DelayIdata.empty()) {
     Defined *Helper = cast<Defined>(Config->DelayLoadHelper);
@@ -849,11 +977,13 @@ template <typename PEHeaderTy> void Writ
     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();

Added: lld/trunk/test/COFF/Inputs/gnu-implib-func.s
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/Inputs/gnu-implib-func.s?rev=342777&view=auto
==============================================================================
--- lld/trunk/test/COFF/Inputs/gnu-implib-func.s (added)
+++ lld/trunk/test/COFF/Inputs/gnu-implib-func.s Fri Sep 21 15:01:06 2018
@@ -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"

Added: lld/trunk/test/COFF/Inputs/gnu-implib-head.s
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/Inputs/gnu-implib-head.s?rev=342777&view=auto
==============================================================================
--- lld/trunk/test/COFF/Inputs/gnu-implib-head.s (added)
+++ lld/trunk/test/COFF/Inputs/gnu-implib-head.s Fri Sep 21 15:01:06 2018
@@ -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:

Added: lld/trunk/test/COFF/Inputs/gnu-implib-tail.s
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/Inputs/gnu-implib-tail.s?rev=342777&view=auto
==============================================================================
--- lld/trunk/test/COFF/Inputs/gnu-implib-tail.s (added)
+++ lld/trunk/test/COFF/Inputs/gnu-implib-tail.s Fri Sep 21 15:01:06 2018
@@ -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"

Added: lld/trunk/test/COFF/imports-gnu-only.s
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/imports-gnu-only.s?rev=342777&view=auto
==============================================================================
--- lld/trunk/test/COFF/imports-gnu-only.s (added)
+++ lld/trunk/test/COFF/imports-gnu-only.s Fri Sep 21 15:01:06 2018
@@ -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

Added: lld/trunk/test/COFF/imports-gnu.test
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/imports-gnu.test?rev=342777&view=auto
==============================================================================
--- lld/trunk/test/COFF/imports-gnu.test (added)
+++ lld/trunk/test/COFF/imports-gnu.test Fri Sep 21 15:01:06 2018
@@ -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: }

Added: lld/trunk/test/COFF/no-idata.s
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/no-idata.s?rev=342777&view=auto
==============================================================================
--- lld/trunk/test/COFF/no-idata.s (added)
+++ lld/trunk/test/COFF/no-idata.s Fri Sep 21 15:01:06 2018
@@ -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




More information about the llvm-commits mailing list