How to catch delay-import dll errors (missing dll or symbol) in MinGW(-w64)?

1.5k views Asked by At

Using dlltool -y it is possible to create delay-import libraries for existing .dll or .def files. This seems to work just fine up to the point where the dll is needed on systems that do not have the corresponding dll (as expected for a delay-imported/loaded dll). However, I could not find any information on how to catch errors (missing module or missing function) generated during delayed loading.

On MSVC you would use __try {} __except (...) {} SEH exception handling, however, this is not available on MinGW (also I do not know what kind of exception mechanism dlltool uses).

Regular try {} catch(...) {} does not work either (application crashes in the same manner as without any exception handling).

The GDB output is also not particularly helpful:

gdb: unknown target exception 0xc06d007e at 0x7fefccfaaad

Program received signal ?, Unknown signal.
0x000007fefccfaaad in RaiseException ()
   from C:\Windows\system32\KernelBase.dll

That the unknown exception occurs in RaiseException would seem to indicate an SEH exception if I am not mistaken.

Therefore the question, has anyone successfully handled delay loading in MinGW-w64 and how?

EDIT: After a bit of experimentation I came up with the following solution:

extern "C" __declspec(dllexport) void foo(int);

#include <windows.h>
#include <csetjmp>
#include <stdexcept>
#include <memory>
#include <cstdio>

thread_local auto do_handler = true;
thread_local jmp_buf env;
LONG CALLBACK handler(PEXCEPTION_POINTERS e)
{
    if(do_handler)
    {
        // this flag is necessary to prevent a recursive call to handler
        do_handler = false;
        longjmp(env, 1);
    }
    else
    {
        return EXCEPTION_CONTINUE_SEARCH;
    }
}

struct veh_remover
{
    void operator() (void * veh) const
    {
        RemoveVectoredExceptionHandler(veh);
        do_handler = true;
    }
};

int main(int, char**)
{
    #define CHECKED_DELAY(fn, ...) \
        do { \
            auto h = std::unique_ptr<void, veh_remover>(AddVectoredExceptionHandler(1, handler)); \
            if(!setjmp(env)) fn(__VA_ARGS__); \
            else throw std::runtime_error(#fn " not available"); \
        } while(0);

    try { CHECKED_DELAY(foo, 0); }
    catch(std::exception & e) { printf("%s\n", e.what()); }
}

However, I am not sure if the behavior of this code is well defined (I am longjumping out of the handler after all). Also it doesn't seem particularly clean.

EDIT 2: I tried a different approach, setting __pfnDliFailureHook2:

extern "C" __declspec(dllimport) void foo(int);

#include <windows.h>
#include <csetjmp>
#include <stdexcept>
#include <memory>
#include <cstdio>
#include <cassert>

#include <delayimp.h>

FARPROC WINAPI delayHook(unsigned dliNotify, PDelayLoadInfo)
{
    switch(dliNotify)
    {
        case dliFailLoadLib: throw std::runtime_error("module could not be loaded");
        case dliFailGetProc: throw std::runtime_error("could not find procedure in module");
        default: return 0;
    };
}

int main(int, char**)
{
    __pfnDliFailureHook2 = &delayHook;
    try
    {
        foo(0);
    }
    catch(std::exception & e)
    {
        printf("%s\n", e.what());
    }
}

This approach fails because the exception isn't properly propagated and results in a 0x20474343 SEH exception. There seems to be a related GCC bug which should be fixed, but at least using MinGW-w64 g++ 4.9.2 this still fails (and that is the most current version available)

0

There are 0 answers