I think the MinGW project is very useful, but I simply do not recommend using it.

My experience with it was trying to debug why a friend’s build was working on Windows and not Wine. I figured it out: it’s because MinGW implements something called pseudo relocations, which implement symbol-relative relocations inside the entrypoint of your executable. It’s needed because when compiling, you don’t know if an unresolved symbol will be external or not, so it gets compiled as if it’s not. But when linking, and you finally know that the symbol is external, it’s too late to change the code to refer to an import table indirection.

On ELF platforms, relocations can refer to symbols in other libraries during runtime linking. So external symbols can be treated very similarly to internal ones, just with linking done at runtime instead of compile-time.

On Windows, you have to use `__declspec(dllimport)` or some such to inform the compiler that the symbol will be external. It can then generate code for referring to the IAT.

MinGW wanted to support compiling UNIXy software that didn’t use this attribute, so it does not require it. Instead… the ELF-style pseudo-relocations are used. So at the entrypoint, your MinGW program or DLL loops through a table of pointers inside machine code, looks up their real address in the IAT, and performs fixups. It will even temporarily change the protection of the relevant pages to be writable. Check to see if your MinGW program has unexpected imports to kernel32.VirtualProtect! (IIRC this can also happen if you trigger executable stack by using GCC trampolines, another reason to not use GCC…)

(edit: Please read the replies below, but according to MinGW developers, this only happens in some edge cases; ordinarily, thunks will be used, which do not have this problem. In addition, MinGW binaries will always link to VritualProtect, because the pseudo-reloc code exists whether or not you use them. I never noticed it before, so that's interesting.)

There’s only one problem: this is extremely failure-prone. For example, if you are on AMD64, the typical CALL instruction contains a 32-bit RIP-relative offset. But in a 64-bit process space, two modules can happily be more than 2^32 bytes away in memory. I assume ELF platforms have a linker that’s aware of this, since the linker knows about these special relocations, and thus has to ensure the binaries are in range… but I don’t know much about ELF linking, so don’t take that as fact.

So why does this break on Wine and not Windows? Well, as far as I can tell, it doesn’t break on Windows because for some reason ASLR never puts any of the modules too far away. I haven’t figured out why. Wine doesn’t implement ASLR and loads modules at their preferred addresses as long as it’s available, and those addresses wind up being too far away. The pseudo reloc code does not output any errors when the address overflows; would probably be worth a pull request, but I am still puzzling with why Windows doesn’t ever seem to cause the problem.

Unless I am missing something crucial, it’s possible that a large amount of Windows x64 binaries compiled with MinGW work almost entirely by accident, and maybe not even every time.

Most symbols like this are function calls, so a significantly safer approach would be for MinGW to generate a thunk for those, and then just fail at linking when encountering a data import that isn’t decorated. Unfortunately, I don’t make the calls. :P

I don’t mean to disparage any specific persons. I just generally recommend against MinGW even in the face of the fact that it is virtually the only free software option to compile Windows binaries.