diff options
| author | Tamar Christina <tamar@zhox.com> | 2015-11-07 03:51:43 -0500 |
|---|---|---|
| committer | Tamar Christina <tamar@zhox.com> | 2015-11-07 14:00:04 +0100 |
| commit | 6e6438e15f33cb94ad6338e950e693f59d046385 (patch) | |
| tree | 445b9881c599f6977d6ad812462d9bf84c2570af /rts/Linker.c | |
| parent | ce1f1607ed7f8fedd2f63c8610cafefd59baaf32 (diff) | |
| download | haskell-6e6438e15f33cb94ad6338e950e693f59d046385.tar.gz | |
Allow the GHCi Linker to resolve related dependencies when loading DLLs
Summary:
GHCi does not correctly tell the Windows Loader how to handle dependencies to DLL's
that are not on the standard Windows load path:
1. The directory from which the application loaded.
2. The current directory.
3. The system directory. Use the GetSystemDirectory function to get the path of this directory.
4. The 16-bit system directory. There is no function that obtains the path of this directory,
but it is searched.
5. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
6. The directories that are listed in the PATH environment variable.
Note that this does not include the per-application path specified by the
AppPaths registry key. The App Paths key is not used when computing the DLL search path.
So what this means is given two DLLs `A` and `B` and `B` depending on `A`.
If we put both DLLs into a new folder bin and then call GHC with:
`ghc -L$(PWD)/bin -lB`
the loading will fail as the Windows loader will try to load the dependency of `B` and fail
since it cannot find `A`.
*IMPORTANT* this patch drops XP Support.
The APIs being used were natively added to Windows 8+ and backported to Windows 7 and Vista
via a mandatory security patch (in 2011). This means that there is a chance that KB2533623 has
not been installed on certain machines. For those machines I display a warning and
temporarily expand the `PATH` to allow it to load.
This patch will make sure that paths provided by the user with `-L` *and* the folder in which a
DLL is found are added to the search path. It does so using one of two methods depending upon how
new of a Windows version we are running on:
- If the APIs are available it will use `addDllDirectory` and `removeDllDirectory`.
The order of which these directories are searched is nondeterministic.
- If the APIs are not available it means that we're running on a pretty old unpatched machine.
But if it's being used in an environment with no internet access it may be the case.
So if the APIs are not available we temporarily extend the `PATH` with the directories.
A warning is also displayed to the user informing them that the linking may fail,
and if it does, install the needed patch. The `PATH` variable has limitations.
Test Plan:
./validate
Added two new test T10955 and T10955dyn
Reviewers: erikd, bgamari, thomie, hvr, austin
Reviewed By: erikd, thomie
Subscribers: #ghc_windows_task_force
Differential Revision: https://phabricator.haskell.org/D1340
GHC Trac Issues: #10955
Diffstat (limited to 'rts/Linker.c')
| -rw-r--r-- | rts/Linker.c | 199 |
1 files changed, 182 insertions, 17 deletions
diff --git a/rts/Linker.c b/rts/Linker.c index 0507c9c268..35227c866b 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -104,6 +104,7 @@ # include <windows.h> # include <shfolder.h> /* SHGetFolderPathW */ # include <math.h> +# include <wchar.h> #elif defined(darwin_HOST_OS) # define OBJFORMAT_MACHO # include <regex.h> @@ -246,6 +247,12 @@ static void machoInitSymbolsWithoutUnderscore( void ); #endif #endif +#if defined(OBJFORMAT_PEi386) +// MingW-w64 is missing these from the implementation. So we have to look them up +typedef DLL_DIRECTORY_COOKIE(*LPAddDLLDirectory)(PCWSTR NewDirectory); +typedef WINBOOL(*LPRemoveDLLDirectory)(DLL_DIRECTORY_COOKIE Cookie); +#endif + static void freeProddableBlocks (ObjectCode *oc); #if USE_MMAP @@ -832,7 +839,7 @@ addDLL( pathchar *dll_name ) OpenedDLL* o_dll; HINSTANCE instance; - /* debugBelch("\naddDLL; dll_name = `%s'\n", dll_name); */ + IF_DEBUG(linker, debugBelch("\naddDLL; 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) { @@ -852,23 +859,46 @@ addDLL( pathchar *dll_name ) size_t bufsize = pathlen(dll_name) + 10; buf = stgMallocBytes(bufsize * sizeof(wchar_t), "addDLL"); - snwprintf(buf, bufsize, L"%s.DLL", dll_name); - instance = LoadLibraryW(buf); - if (instance == NULL) { - if (GetLastError() != ERROR_MOD_NOT_FOUND) goto error; - // KAA: allow loading of drivers (like winspool.drv) - snwprintf(buf, bufsize, L"%s.DRV", dll_name); - instance = LoadLibraryW(buf); - if (instance == NULL) { - if (GetLastError() != ERROR_MOD_NOT_FOUND) goto error; - // #1883: allow loading of unix-style libfoo.dll DLLs - snwprintf(buf, bufsize, L"lib%s.DLL", dll_name); - instance = LoadLibraryW(buf); - if (instance == NULL) { - goto error; + + /* These are ordered by probability of success and order we'd like them */ + const wchar_t *formats[] = { L"%s.DLL", L"%s.DRV", L"lib%s.DLL", L"%s" }; + const DWORD flags[] = { LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, 0 }; + + int cFormat; + int 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. } } } + + // Check if we managed to load the DLL + if (instance == NULL) { + goto error; + } + stgFree(buf); addDLLHandle(dll_name, instance); @@ -877,7 +907,7 @@ addDLL( pathchar *dll_name ) error: stgFree(buf); - sysErrorBelch("%" PATH_FMT, dll_name); + sysErrorBelch("addDLL: %" PATH_FMT " (Win32 error %lu)", dll_name, GetLastError()); /* LoadLibrary failed; return a ptr to the error msg. */ return "addDLL: could not load DLL"; @@ -887,6 +917,142 @@ error: # endif } + +/* ----------------------------------------------------------------------------- +* Emits a warning determining that the system is missing a required security +* update that we need to get access to the proper APIs +*/ +void warnMissingKBLibraryPaths( void ) +{ + static HsBool missing_update_warn = HS_BOOL_FALSE; + if (!missing_update_warn) { + debugBelch("Warning: If linking fails, consider installing KB2533623.\n"); + missing_update_warn = HS_BOOL_TRUE; + } +} + +/* ----------------------------------------------------------------------------- +* appends a directory to the process DLL Load path so LoadLibrary can find it +* +* Returns: NULL on failure, or pointer to be passed to removeLibrarySearchPath to +* restore the search path to what it was before this call. +*/ +HsPtr addLibrarySearchPath(pathchar* dll_path) +{ + IF_DEBUG(linker, debugBelch("\naddLibrarySearchPath: dll_path = `%" PATH_FMT "'\n", dll_path)); + +#if defined(OBJFORMAT_PEi386) + HINSTANCE hDLL = LoadLibraryW(L"Kernel32.DLL"); + LPAddDLLDirectory AddDllDirectory = (LPAddDLLDirectory)GetProcAddress((HMODULE)hDLL, "AddDllDirectory"); + + HsPtr result = NULL; + + const unsigned int init_buf_size = 4096; + int bufsize = init_buf_size; + + // Make sure the path is an absolute path + WCHAR* abs_path = malloc(sizeof(WCHAR) * init_buf_size); + DWORD wResult = GetFullPathNameW(dll_path, bufsize, abs_path, NULL); + if (!wResult){ + sysErrorBelch("addLibrarySearchPath[GetFullPathNameW]: %" PATH_FMT " (Win32 error %lu)", dll_path, GetLastError()); + } + else if (wResult > init_buf_size) { + abs_path = realloc(abs_path, sizeof(WCHAR) * wResult); + if (!GetFullPathNameW(dll_path, bufsize, abs_path, NULL)) { + sysErrorBelch("addLibrarySearchPath[GetFullPathNameW]: %" PATH_FMT " (Win32 error %lu)", dll_path, GetLastError()); + } + } + + if (AddDllDirectory) { + result = AddDllDirectory(abs_path); + } + else + { + warnMissingKBLibraryPaths(); + WCHAR* str = malloc(sizeof(WCHAR) * init_buf_size); + wResult = GetEnvironmentVariableW(L"PATH", str, bufsize); + + if (wResult > init_buf_size) { + str = realloc(str, sizeof(WCHAR) * wResult); + bufsize = wResult; + wResult = GetEnvironmentVariableW(L"PATH", str, bufsize); + if (!wResult) { + sysErrorBelch("addLibrarySearchPath[GetEnvironmentVariableW]: %" PATH_FMT " (Win32 error %lu)", dll_path, GetLastError()); + } + } + + bufsize = wResult + 2 + pathlen(abs_path); + wchar_t* newPath = malloc(sizeof(wchar_t) * bufsize); + + wcscpy(newPath, abs_path); + wcscat(newPath, L";"); + wcscat(newPath, str); + if (!SetEnvironmentVariableW(L"PATH", (LPCWSTR)newPath)) { + sysErrorBelch("addLibrarySearchPath[SetEnvironmentVariableW]: %" PATH_FMT " (Win32 error %lu)", abs_path, GetLastError()); + } + + free(newPath); + free(abs_path); + + return str; + } + + if (!result) { + sysErrorBelch("addLibrarySearchPath: %" PATH_FMT " (Win32 error %lu)", abs_path, GetLastError()); + free(abs_path); + return NULL; + } + + free(abs_path); + return result; +#else + (void)(dll_path); // Function not implemented for other platforms. + return NULL; +#endif +} + +/* ----------------------------------------------------------------------------- +* removes a directory from the process DLL Load path +* +* Returns: HS_BOOL_TRUE on success, otherwise HS_BOOL_FALSE +*/ +HsBool removeLibrarySearchPath(HsPtr dll_path_index) +{ + IF_DEBUG(linker, debugBelch("\nremoveLibrarySearchPath: ptr = `%p'\n", dll_path_index)); + +#if defined(OBJFORMAT_PEi386) + HsBool result = 0; + + if (dll_path_index != NULL) { + HINSTANCE hDLL = LoadLibraryW(L"Kernel32.DLL"); + LPRemoveDLLDirectory RemoveDllDirectory = (LPRemoveDLLDirectory)GetProcAddress((HMODULE)hDLL, "RemoveDllDirectory"); + + if (RemoveDllDirectory) { + result = RemoveDllDirectory(dll_path_index); + // dll_path_index is now invalid, do not use it after this point. + } + else + { + warnMissingKBLibraryPaths(); + + result = SetEnvironmentVariableW(L"PATH", (LPCWSTR)dll_path_index); + + free(dll_path_index); + } + + if (!result) { + sysErrorBelch("removeLibrarySearchPath: (Win32 error %lu)", GetLastError()); + return HS_BOOL_FALSE; + } + } + + return result == 0 ? HS_BOOL_TRUE : HS_BOOL_FALSE; +#else + (void)(dll_path_index); // Function not implemented for other platforms. + return HS_BOOL_FALSE; +#endif +} + /* ----------------------------------------------------------------------------- * insert a symbol in the hash table * @@ -2806,7 +2972,6 @@ typedef #define sizeof_COFF_reloc 10 - /* From PE spec doc, section 3.3.2 */ /* Note use of MYIMAGE_* since IMAGE_* are already defined in windows.h -- for the same purpose, but I want to know what I'm |
