Get reason that LoadLibrary cannot load DLL

Viewed 3871

On Linux and Mac, when using dlopen() to load a shared library that links to another library, if linking fails because of a missing symbol, you can get the name of the missing symbol with dlerror(). It says something like

dlopen failed: cannot locate symbol "foo"

On Windows, when using LoadLibrary() to load a DLL with a missing symbol, you can only get an error code from GetLastError() which for this type of issue will always be 127. How can I figure out which symbol is missing, or a more verbose error message from LoadLibrary() that explains why the function failed?

2 Answers

I figured out a way using the MSYS2 terminal. Other methods might work with GUI software. A major caveat is that this can't be done in pure C/C++ and released for end users. It's for developers only, but it's better than nothing.

Install Debugging Tools for Windows by downloading the Windows SDK and unchecking everything except Debugging Tools. I could be wrong, but it seems that installing this software installs a hook into the Windows kernel to allow LoadLibrary() to write verbose information to stderr.

Open the MSYS2 Mingw64 terminal as an administrator and run

'/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/gflags.exe' -i main.exe +sls

This prints the following to the terminal to confirm that the registry has been changed.

Current Registry Settings for main.exe executable are: 00000002
    sls - Show Loader Snaps

Use -sls instead of +sls if you need to undo, since I believe that the change takes place for all programs called main.exe in Windows globally, not just for your file.

Then running main.exe should print debug information to stderr, but since I'm debugging an -mwindows application, it's not working for me.

But for some reason, running the binary with MSYS2's gdb allows this debug information to be printed to stderr. Install mingw-w64-x86_64-gdb with MSYS2 and run gdb ./main.exe and type run or r. Search for a section similar to the following.

warning: 1ec8:43a0 @ 764081125 - LdrpNameToOrdinal - WARNING: Procedure "foo" could not be located in DLL at base 0x000000006FC40000.
warning: 1ec8:43a0 @ 764081125 - LdrpReportError - ERROR: Locating export "foo" for DLL "C:\whatever\plugin.dll" failed with status: 0xc0000139.
warning: 1ec8:43a0 @ 764081125 - LdrpGenericExceptionFilter - ERROR: Function LdrpSnapModule raised exception 0xc0000139
    Exception record: .exr 00000000050BE5F0
    Context record: .cxr 00000000050BE100
warning: 1ec8:43a0 @ 764081125 - LdrpProcessWork - ERROR: Unable to load DLL: "C:\whatever\plugin.dll", Parent Module: "(null)", Status: 0xc0000139
warning: 1ec8:43a0 @ 764081171 - LdrpLoadDllInternal - RETURN: Status: 0xc0000139
warning: 1ec8:43a0 @ 764081171 - LdrLoadDll - RETURN: Status: 0xc0000139

Great! It says Procedure "foo" could not be located in DLL so we have our missing symbol, just like in POSIX/UNIX's dlopen().

While the answer from Remy Lebeau is technically correct, determining the missing symbol from GetLastError() is still possible on a Windows platform. To understand what exactly is missing, understanding the terminology is critical.

Symbol:

When a DLL is compiled, it's functions are referenced by symbols. These symbols directly relate to the functions name (the symbols are represented by visible and readable strings), its return type, and it's parameters. The symbols can actually be read directly through a text editor although difficult to find in large DLLs.DLL Symbols - C++ Forum

To have a missing symbol implies that a function within cannot be found. If this error occurs prior to using GetProcAddress(), then it's possible that any number of functions cannot be loaded due to missing prerequisites. This means it is possible that a library that you are attempting to load also requires a library that the first cannot load. These levels of dependency may go on for an unknown number of layers, but the only answer that GetLastError() can determine is that there was a missing symbol. One such method is by using Dependency Walker to determine the missing library the first library requires. Once all required libraries are available and can be found by that library (which can be its own can of worms), that library can be loaded via LoadLibrary().

Related