Load dependent dlls.
authorTamar Christina <tamar@zhox.com>
Thu, 23 Feb 2017 23:32:28 +0000 (18:32 -0500)
committerBen Gamari <ben@smart-cactus.org>
Sun, 26 Feb 2017 15:54:07 +0000 (10:54 -0500)
When the `GCC` driver envokes the pipeline a `SPEC` is used to determine
how to configure the compiler and which libraries to pass along.

For Windows/mingw, this specfile is
https://github.com/gcc-mirror/gcc/blob/master/gcc/config/i386/mingw32.h

This has a lot of interesting things that we need to emulate in order to
be able to link as many things out of the box as GCC. In particular this
is why you never need to specify `-lgcc_s` when compiling, but you do
when using `GHCi`.

Unfortunately due to time constraints I can't set up the framework
required in `GHC` to do this before the feature freeze.

So I suggest this alternate implementation:
When we load a dll, also bring it's dependencies into scope of the
interpeter.

This has pros and cons. Pro is, we'll fix many packages on hackage which
specify just `-lstdc++`. Since this points to `libstdc++-6.dll` which
will bring `libgcc` into scope.

The downside is, we'll be more lenient than GCC, in that the interpreter
will link much easier since it has implicit dependencies in scope.
Whereas for compilation to work you will have to specify it as an
argument to GHC.

This will make the Windows runtime linker more consistent with the unix
ones. The difference in semantics came about because of the differences
between `dlsym` and `GetProcAddress`.  The former seems to search the
given library and all it's dependencies, while the latter only searches
the export table of the library. So we need some extra manual work to
search the dependencies which this patch provides.

Test Plan:
```
./validate
```
```
$ echo :q | inplace/bin/ghc-stage2.exe --interactive +RTS -Dl -RTS
-lstdc++ 2>&1 | grep "Loading dependency"
```

```
$ echo :q | ../inplace/bin/ghc-stage2.exe --interactive -lstdc++ +RTS
-Dl -RTS 2>&1 | grep "Loading dependency"
Loading dependency *.exe -> GDI32.dll.
Loading dependency GDI32.dll -> ntdll.dll.
Loading dependency *.exe -> KERNEL32.dll.
Loading dependency KERNEL32.dll -> KERNELBASE.dll.
Loading dependency *.exe -> msvcrt.dll.
Loading dependency *.exe -> SHELL32.dll.
Loading dependency SHELL32.dll -> USER32.dll.
Loading dependency USER32.dll -> win32u.dll.
Loading dependency *.exe -> WINMM.dll.
Loading dependency WINMM.dll -> WINMMBASE.dll.
Loading dependency *.exe -> WSOCK32.dll.
Loading dependency WSOCK32.dll -> WS2_32.dll.
Loading dependency WS2_32.dll -> RPCRT4.dll.
Loading dependency libstdc++-6.dll -> libwinpthread-1.dll.
Loading dependency libstdc++-6.dll -> libgcc_s_seh-1.dll.
```

Trac tickets: #13093, #13189

Reviewers: simonmar, rwbarton, austin, bgamari, erikd

Reviewed By: bgamari

Subscribers: rwbarton, RyanGlScott, thomie, #ghc_windows_task_force

Differential Revision: https://phabricator.haskell.org/D3028

rts/linker/PEi386.c
testsuite/tests/ghci/linking/Makefile

index b62f1eb..9c7f4db 100644 (file)
@@ -86,6 +86,9 @@ static bool verifyCOFFHeader(
     COFF_header *hdr,
     pathchar *filename);
 
     COFF_header *hdr,
     pathchar *filename);
 
+static bool checkIfDllLoaded(
+    HINSTANCE instance);
+
 /* Add ld symbol for PE image base. */
 #if defined(__GNUC__)
 #define __ImageBase __MINGW_LSYMBOL(_image_base__)
 /* Add ld symbol for PE image base. */
 #if defined(__GNUC__)
 #define __ImageBase __MINGW_LSYMBOL(_image_base__)
@@ -104,20 +107,19 @@ void initLinker_PEi386()
     }
 
 #if defined(mingw32_HOST_OS)
     }
 
 #if defined(mingw32_HOST_OS)
-    /*
-     * These two libraries cause problems when added to the static link,
-     * but are necessary for resolving symbols in GHCi, hence we load
-     * them manually here.
-     *
-     * Most of these are included by base, but GCC always includes them
-     * So lets make sure we always have them too.
-     */
+    addDLLHandle(WSTR("*.exe"), GetModuleHandle(NULL));
+   /*
+    * Most of these are included by base, but GCC always includes them
+    * So lets make sure we always have them too.
+    *
+    * In most cases they would have been loaded by the
+    * addDLLHandle above.
+    */
     addDLL(WSTR("msvcrt"));
     addDLL(WSTR("kernel32"));
     addDLL(WSTR("advapi32"));
     addDLL(WSTR("shell32"));
     addDLL(WSTR("user32"));
     addDLL(WSTR("msvcrt"));
     addDLL(WSTR("kernel32"));
     addDLL(WSTR("advapi32"));
     addDLL(WSTR("shell32"));
     addDLL(WSTR("user32"));
-    addDLLHandle(WSTR("*.exe"), GetModuleHandle(NULL));
 #endif
 }
 
 #endif
 }
 
@@ -129,12 +131,61 @@ static IndirectAddr* indirects = NULL;
 
 /* Adds a DLL instance to the list of DLLs in which to search for symbols. */
 static void addDLLHandle(pathchar* dll_name, HINSTANCE instance) {
 
 /* Adds a DLL instance to the list of DLLs in which to search for symbols. */
 static void addDLLHandle(pathchar* dll_name, HINSTANCE instance) {
+
+    /* At this point, we actually know what was loaded.
+       So bail out if it's already been loaded.  */
+    if (checkIfDllLoaded(instance))
+    {
+        return;
+    }
+
     OpenedDLL* o_dll;
     OpenedDLL* o_dll;
-    o_dll = stgMallocBytes( sizeof(OpenedDLL), "addDLLHandle" );
+    o_dll           = stgMallocBytes( sizeof(OpenedDLL), "addDLLHandle" );
     o_dll->name     = dll_name ? pathdup(dll_name) : NULL;
     o_dll->instance = instance;
     o_dll->next     = opened_dlls;
     opened_dlls     = o_dll;
     o_dll->name     = dll_name ? pathdup(dll_name) : NULL;
     o_dll->instance = instance;
     o_dll->next     = opened_dlls;
     opened_dlls     = o_dll;
+
+    /* Now discover the dependencies of dll_name that were
+       just loaded in our process space. The reason is we have access to them
+       without the user having to explicitly specify them.  */
+    PIMAGE_NT_HEADERS header =
+        (PIMAGE_NT_HEADERS)((BYTE *)instance +
+         ((PIMAGE_DOS_HEADER)instance)->e_lfanew);
+    PIMAGE_IMPORT_DESCRIPTOR imports =
+        (PIMAGE_IMPORT_DESCRIPTOR)((BYTE *)instance + header->
+        OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
+
+    /* Ignore these compatibility shims.  */
+    const pathchar* ms_dll = WSTR("api-ms-win-");
+    const int len = wcslen(ms_dll);
+
+    do {
+        pathchar* module = mkPath((char*)(BYTE *)instance + imports->Name);
+        HINSTANCE module_instance = GetModuleHandleW(module);
+        if (0 != wcsncmp(module, ms_dll, len)
+            && module_instance
+            && !checkIfDllLoaded(module_instance))
+        {
+            IF_DEBUG(linker, debugBelch("Loading dependency %" PATH_FMT " -> %" PATH_FMT ".\n", dll_name, module));
+            /* Now recursively load dependencies too.  */
+            addDLLHandle(module, module_instance);
+        }
+        stgFree(module);
+        imports++;
+    } while (imports->Name);
+}
+
+static bool checkIfDllLoaded(HINSTANCE instance)
+{
+    for (OpenedDLL* o_dll = opened_dlls; o_dll != NULL; o_dll = o_dll->next) {
+        if (o_dll->instance == instance)
+        {
+            return true;
+        }
+    }
+
+    return false;
 }
 
 void freePreloadObjectFile_PEi386(ObjectCode *oc)
 }
 
 void freePreloadObjectFile_PEi386(ObjectCode *oc)
@@ -157,19 +208,12 @@ addDLL_PEi386( pathchar *dll_name )
    /* ------------------- Win32 DLL loader ------------------- */
 
    pathchar*  buf;
    /* ------------------- Win32 DLL loader ------------------- */
 
    pathchar*  buf;
-   OpenedDLL* o_dll;
    HINSTANCE  instance;
 
    IF_DEBUG(linker, debugBelch("addDLL; dll_name = `%" PATH_FMT "'\n", dll_name));
 
    HINSTANCE  instance;
 
    IF_DEBUG(linker, debugBelch("addDLL; dll_name = `%" PATH_FMT "'\n", dll_name));
 
-   /* See if we've already got it, and ignore if so. */
-   for (o_dll = opened_dlls; o_dll != NULL; o_dll = o_dll->next) {
-      if (0 == pathcmp(o_dll->name, dll_name))
-         return NULL;
-   }
-
-   /* The file name has no suffix (yet) so that we can try
-      both foo.dll and foo.drv
+    /* The file name has no suffix (yet) so that we can try
+       both foo.dll and foo.drv
 
       The documentation for LoadLibrary says:
         If no file name extension is specified in the lpFileName
 
       The documentation for LoadLibrary says:
         If no file name extension is specified in the lpFileName
@@ -178,60 +222,59 @@ addDLL_PEi386( pathchar *dll_name )
         point character (.) to indicate that the module name has no
         extension. */
 
         point character (.) to indicate that the module name has no
         extension. */
 
-   size_t bufsize = pathlen(dll_name) + 10;
-   buf = stgMallocBytes(bufsize * sizeof(wchar_t), "addDLL");
+    size_t bufsize = pathlen(dll_name) + 10;
+    buf = stgMallocBytes(bufsize * sizeof(wchar_t), "addDLL");
 
 
-   /* These are ordered by probability of success and order we'd like them */
-   const wchar_t *formats[] = { L"%ls.DLL", L"%ls.DRV", L"lib%ls.DLL", L"%ls" };
-   const DWORD flags[]      = { LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, 0 };
+    /* These are ordered by probability of success and order we'd like them.  */
+    const wchar_t *formats[] = { L"%ls.DLL", L"%ls.DRV", L"lib%ls.DLL", L"%ls" };
+    const DWORD flags[] = { LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, 0 };
 
 
-   int cFormat, cFlag;
-   int flags_start = 1; // Assume we don't support the new API
+    int cFormat, cFlag;
+    int flags_start = 1; /* Assume we don't support the new API.  */
 
 
-   /* Detect if newer API are available, if not, skip the first flags entry */
-   if (GetProcAddress((HMODULE)LoadLibraryW(L"Kernel32.DLL"), "AddDllDirectory")) {
-       flags_start = 0;
-   }
-
-   /* Iterate through the possible flags and formats */
-   for (cFlag = flags_start; cFlag < 2; cFlag++)
-   {
-       for (cFormat = 0; cFormat < 4; cFormat++)
-       {
-           snwprintf(buf, bufsize, formats[cFormat], dll_name);
-           instance = LoadLibraryExW(buf, NULL, flags[cFlag]);
-           if (instance == NULL)
-           {
-               if (GetLastError() != ERROR_MOD_NOT_FOUND)
-               {
-                   goto error;
-               }
-           }
-           else
-           {
-               break; // We're done. DLL has been loaded.
-           }
-       }
-   }
+    /* Detect if newer API are available, if not, skip the first flags entry.  */
+    if (GetProcAddress((HMODULE)LoadLibraryW(L"Kernel32.DLL"), "AddDllDirectory")) {
+        flags_start = 0;
+    }
 
 
-   // Check if we managed to load the DLL
-   if (instance == NULL) {
-       goto error;
-   }
+    /* Iterate through the possible flags and formats.  */
+    for (cFlag = flags_start; cFlag < 2; cFlag++)
+    {
+        for (cFormat = 0; cFormat < 4; cFormat++)
+        {
+            snwprintf(buf, bufsize, formats[cFormat], dll_name);
+            instance = LoadLibraryExW(buf, NULL, flags[cFlag]);
+            if (instance == NULL)
+            {
+                if (GetLastError() != ERROR_MOD_NOT_FOUND)
+                {
+                    goto error;
+                }
+            }
+            else
+            {
+                break; /* We're done. DLL has been loaded.  */
+            }
+        }
+    }
 
 
-   stgFree(buf);
+    /* Check if we managed to load the DLL.  */
+    if (instance == NULL) {
+        goto error;
+    }
 
 
-   addDLLHandle(dll_name, instance);
+    addDLLHandle(buf, instance);
+    stgFree(buf);
 
 
-   return NULL;
+    return NULL;
 
 error:
 
 error:
-   stgFree(buf);
+    stgFree(buf);
 
 
-   char* errormsg = malloc(sizeof(char) * 80);
-   snprintf(errormsg, 80, "addDLL: %" PATH_FMT " or dependencies not loaded. (Win32 error %lu)", dll_name, GetLastError());
-   /* LoadLibrary failed; return a ptr to the error msg. */
-   return errormsg;
+    char* errormsg = malloc(sizeof(char) * 80);
+    snprintf(errormsg, 80, "addDLL: %" PATH_FMT " or dependencies not loaded. (Win32 error %lu)", dll_name, GetLastError());
+    /* LoadLibrary failed; return a ptr to the error msg. */
+    return errormsg;
 }
 
 pathchar* findSystemLibrary_PEi386( pathchar* dll_name )
 }
 
 pathchar* findSystemLibrary_PEi386( pathchar* dll_name )
index f8c5e19..793152d 100644 (file)
@@ -38,11 +38,7 @@ ghcilink002 :
 
 .PHONY: ghcilink003
 ghcilink003 :
 
 .PHONY: ghcilink003
 ghcilink003 :
-ifeq "$(WINDOWS)" "YES"
-       echo ":q" | "$(TEST_HC)" $(TEST_HC_OPTS_INTERACTIVE) -lstdc++-6
-else
        echo ":q" | "$(TEST_HC)" $(TEST_HC_OPTS_INTERACTIVE) -lstdc++
        echo ":q" | "$(TEST_HC)" $(TEST_HC_OPTS_INTERACTIVE) -lstdc++
-endif
 
 # Test 4: 
 #   package P
 
 # Test 4: 
 #   package P