I have an open-source game project that was mostly developed under Ubuntu. Recently I ported it to Windows, what consisted only in minor tweaks and then building it for Windows, since I only used cross-platform libraries and features.
To build it, initially I cross-compiled using MinGW-w64 from Ubuntu's 19.04 repositories, and it worked like a charm. This is what it reports as the version:
$ x86_64-w64-mingw32-g++-posix --version
x86_64-w64-mingw32-g++-posix (GCC) 9.2-posix 20191008
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
When I updated to Ubuntu 20.04, MinGW-w64 got a small bump in version number:
$ x86_64-w64-mingw32-g++-posix --version
x86_64-w64-mingw32-g++-posix (GCC) 9.3-posix 20200320
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
The update broke my build, as the executable generated by the new version of MinGW-w64 does not work. When executed on a Windows machine, it is unable to find the symbols from its DLL dependencies, and I get the following popup error:
Which should translate to English version of the error message as (filling the variables):
glome.exe - Entry Point Not Found
The procedure entry point ogg_page_bos could not be located in the dynamic link library «path to glome.exe».
The interesting points to notice here are:
This is the command line for the compilation of one of the files of the game (there are many files, but all are compiled in the same way):
cd /home/lucas/glome/ubuntu-20.04-win-build/src/sdl && /usr/bin/x86_64-w64-mingw32-g++-posix @CMakeFiles/glome.dir/includes_CXX.rsp -march=haswell -mtune=generic -Ofast -fno-fat-lto-objects -flto=12 -I/home/lucas/glome/windows-deps/opusfile/include/opus/ -I/home/lucas/glome/windows-deps/opus/include/opus/ -I/home/lucas/glome/windows-deps/libogg/include/ogg/ -I/home/lucas/glome/windows-deps/glew-2.1.0/include/ -I/home/lucas/glome/windows-deps/OpenAL-1.1-SDK/include -I/home/lucas/glome/windows-deps/libogg/include -I/home/lucas/glome/windows-deps/SDL2-2.0.12/include/ -I/home/lucas/glome/windows-deps/boost_1_72_0/ -g -std=gnu++17 -o CMakeFiles/glome.dir/input.cpp.obj -c /home/lucas/glome/src/src/sdl/input.cpp
where CMakeFiles/glome.dir/includes_CXX.rsp
contains only -I
directives:
-I/home/lucas/glome/src/src/common/. -I/home/lucas/glome/src/external/concurrentqueue -I/home/lucas/glome/ubuntu-20.04-win-build/src -I/home/lucas/glome/src/src/sdl
The linking command for the executable is:
/usr/bin/x86_64-w64-mingw32-g++-posix -march=haswell -mtune=generic -Ofast -fno-fat-lto-objects -flto=12 -I/home/lucas/glome/windows-deps/opusfile/include/opus/ -I/home/lucas/glome/windows-deps/opus/include/opus/ -I/home/lucas/glome/windows-deps/libogg/include/ogg/ -I/home/lucas/glome/windows-deps/glew-2.1.0/include/ -I/home/lucas/glome/windows-deps/OpenAL-1.1-SDK/include -I/home/lucas/glome/windows-deps/libogg/include -I/home/lucas/glome/windows-deps/SDL2-2.0.12/include/ -I/home/lucas/glome/windows-deps/boost_1_72_0/ -g -Wl,--whole-archive CMakeFiles/glome.dir/objects.a -Wl,--no-whole-archive -o glome.exe -Wl,--out-implib,libglome.dll.a -Wl,--major-image-version,0,--minor-image-version,0 @CMakeFiles/glome.dir/linklibs.rsp
where CMakeFiles/glome.dir/linklibs.rsp
contains:
../common/libcommon.a -lopengl32 -lglu32 -march=haswell -mtune=generic -Ofast -fno-fat-lto-objects -flto=12 -I/home/lucas/glome/windows-deps/opusfile/include/opus/ -I/home/lucas/glome/windows-deps/opus/include/opus/ -I/home/lucas/glome/windows-deps/libogg/include/ogg/ -I/home/lucas/glome/windows-deps/glew-2.1.0/include/ -I/home/lucas/glome/windows-deps/OpenAL-1.1-SDK/include -I/home/lucas/glome/windows-deps/libogg/include -I/home/lucas/glome/windows-deps/SDL2-2.0.12/include/ -I/home/lucas/glome/windows-deps/boost_1_72_0/ /home/lucas/glome/windows-deps/OpenAL-1.1-SDK/libs/Win64/OpenAL32.lib /home/lucas/glome/windows-deps/glew-2.1.0/lib/Release/x64/glew32.lib /home/lucas/glome/windows-deps/opusfile/lib/libopusfile.a /home/lucas/glome/windows-deps/opus/lib/libopus.dll.a /home/lucas/glome/windows-deps/libogg/lib/libogg.dll.a -L/home/lucas/glome/windows-deps/SDL2-2.0.12/lib/x64/ -lSDL2main -lSDL2 -static-libgcc -static-libstdc++ -Wl,-allow-multiple-definition -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32
Apart from the paths (ubuntu-20.04-win-build
vs ubuntu-19.10-win-build
), the compilation and linking commands are exactly the same, generated with the same parameters from the same CMakeLists.txt
.
The questions:
After hanging out in mingw-w64 IRC channel, people there suggested me to open the executable in dependency tracker, which highlighted a big difference between the working and broken binaries: it looks like symbol requirements "leaked" from one DLL into another, unrelated, DLL.
What led people there to take a closer look into the DLL import libraries. In particular, I was linking against some DLLs via their MSVC generated import libraries, namely: glew32.lib
, OpenAL32.lib
and SDL2.lib
.
It seems GNU ld
has trouble handling import libraries from MSVC, and the fix was simply linking against the DLL files directly, in which case the symbol loading code is generated by ld
itself (and this is actually a much better supported operation: always link against the .dll directly if possible, import libraries are not needed in GNU toolchain).
I don't know why it worked before, apparently there was a regression in GNU ld
across the versions I used.