Failing to run compiled Python extension

Bottom line: change compiler flags to link to libgcc and libstdc++ statically.
Or place them (with any transitive MinGW-specific dependencies) alongside the .pyd and distribute with your module.


I’ve diagnosed the problem with Process Monitor using the following code.
This is the most reliable way because you get to see what the system is actually searching for, and where, rather than look at metadata and make assumptions. Since Windows’ DLL search process is compilcated and very settings-dependent, the latter proved unreliable.

# FTP_* are secret variables for an FTP server that I run on my machine when needed
- curl -f -O ftp://$FTP_USER:$FTP_PASSWD@$FTP_SERVER/procmon.exe
- ./procmon //AcceptEula //Quiet //Minimized //BackingFile error.pml & (until test -f *.pml; do sleep 1; done)
- <command under test>
- ./procmon //Terminate
- gzip -v *.pml
- curl -T "$(perl -e 'print "{".join(",",@ARGV)."}"' *.gz)" ftp://$FTP_USER:$FTP_PASSWD@$FTP_SERVER/

The relevant part of the resulting .pml I got onto my machine for examination is:

"Time of Day","Process Name","PID","TID","Operation","Path","Result","Detail"
"22:46:05,0651278","python.exe","4852","3816","Load Image","C:\Users\travis\build\native-api\travis-windows-pybind\build\CPPMATH.cp38-win_amd64.pyd","SUCCESS","Image Base: 0x68cc0000, Image Size: 0x3d000"
"22:46:05,0652375","python.exe","4852","3816","CloseFile","C:\Users\travis\build\native-api\travis-windows-pybind\build\CPPMATH.cp38-win_amd64.pyd","SUCCESS",""
"22:46:05,0653272","python.exe","4852","3816","QueryOpen","C:\Users\travis\build\native-api\travis-windows-pybind\build\libgcc_s_seh-1.dll","NAME NOT FOUND",""
"22:46:05,0654100","python.exe","4852","3816","QueryOpen","C:\Python38\libgcc_s_seh-1.dll","NAME NOT FOUND",""
"22:46:05,0655351","python.exe","4852","3816","QueryOpen","C:\Windows\System32\libgcc_s_seh-1.dll","NAME NOT FOUND",""

So, the module it fails to find is libgcc_s_seh-1.dll, and for some reason, it only searches for it in a few locations rather than everything on PATHwhich does include C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin where this DLL is located.

Looking at the stacktrace of the events in Process Monitor shows that the .pyd is being loaded from python38.dll with LoadLibraryExW. Searching Python codebase (tag 3.8.2 as this is the version you are installing) for LoadLibraryEx and then looking for anything related to module loading in the results finds this peculiar code:

        /* bpo-36085: We use LoadLibraryEx with restricted search paths
           to avoid DLL preloading attacks and enable use of the
           AddDllDirectory function. We add SEARCH_DLL_LOAD_DIR to
           ensure DLLs adjacent to the PYD are preferred. */
        Py_BEGIN_ALLOW_THREADS
        hDLL = LoadLibraryExW(wpathname, NULL,
                              LOAD_LIBRARY_SEARCH_DEFAULT_DIRS |
                              LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);

which explains the restricted search path.

Since those libraries are not stocked with Windows or CPython but are rather specific to the MinGW toolchain, you need to link to them statically (or place, together with their dependencies, alongside the .pyd, a location which is searched).

The restricted search path is actually proving very useful in that you can detect such dependencies early!

1 Like