Skip to content

Instantly share code, notes, and snippets.

@JeanChristopheMorinPerso
Last active March 13, 2025 21:41
Show Gist options
  • Save JeanChristopheMorinPerso/62ee5feaa693622d2a2a4e0116bdacda to your computer and use it in GitHub Desktop.
Save JeanChristopheMorinPerso/62ee5feaa693622d2a2a4e0116bdacda to your computer and use it in GitHub Desktop.
Debugging missing symbol error when importing a python extension on Windows

I stumbled on a case where a user of https://pypi.org/project/OpenImageIO/'s wheels on Windows was reporting that they could not import the library in Nuke 14.1.

To debug the issue, I created an EC2 instance with a GPU using the NVidia WinServer 2022 AMI, installed Nuke and tried to import OpenImageIO. I was able to reproduce immediately.

Let's install OpenImageIO using Nuke's python interpreter:

& 'C:/Program Files/Nuke14.1v6/python.exe' -m pip install OpenImageIO==3.0.3.1 --prefix C:/Users/Administrator/asd

Inside Nuke's script editor:

import sys
sys.path.append("C:/Users/Administrator/asd/Lib/site-packages")
import OpenImageIO.OpenImageIO
# Result: Traceback (most recent call last):
  File "<string>", line 3, in <module>
  File "C:/Program Files/Nuke14.1v6/pythonextensions\site-packages\shiboken2\files.dir\shibokensupport\__feature__.py", line 142, in _import
    return original_import(name, *args, **kwargs)
  File "C:\Users/Administrator/asd/Lib/site-packages\OpenImageIO\__init__.py", line 33, in <module>
    from .OpenImageIO import * # type: ignore # noqa: F401, F403, E402
  File "C:/Program Files/Nuke14.1v6/pythonextensions\site-packages\shiboken2\files.dir\shibokensupport\__feature__.py", line 142, in _import
    return original_import(name, *args, **kwargs)
ImportError: DLL load failed while importing OpenImageIO: The specified procedure could not be found.

Now, that's weird, why is shiboken involved here? I don't know...

The first thing that came to mind to debug this was to try directly inside Nuke's python interpreter outside Nuke. This produced the same error. Which is good.

PS C:\Users\Administrator\source\repos> & 'C:/Program Files/Nuke14.1v6/python.exe'
Python 3.9.10 (remotes/origin/foundry/v3.9.10:fc80903a66, Jul 21 2023, 12:01:36) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path.append("C:/Users/Administrator/asd/Lib/site-packages")
>>> import OpenImageIO
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\Administrator\asd\Lib\site-packages\OpenImageIO\__init__.py", line 33, in <module>
    from .OpenImageIO import * # type: ignore # noqa: F401, F403, E402
ImportError: DLL load failed while importing OpenImageIO: The specified procedure could not be found.

So shiboken was a red herring. This is reassuring because debugging an issue with shiboken could have been painful.

Next, I needed to get a better view on the problem. I tried to attach the Visual Studio debugger to the python interpreter, but it didn't tell me anything. It doesn't even catch the problem.

So the next thing to try was to use https://www.dependencywalker.com/. Again, this was not successful. I wasn't able to see anything obvious (though I'm not very familiar with the tool, so maybe I just don't know where to look).

If that doesn't work, then we are left with doing some runtime analysis to find which DLL is missing symbols. dlltrace is very useful here. Its designed exactly to help solve issues with DLL loading in Python on Windows.

Note

Why chasing missing symbols? Because the error isn't about a missing library. It's about a missing "precedure", which is probably a symbol I think.

Here is how it can be used:

import sys
import dlltracer

sys.path.append("C:/Users/Administrator/asd/Lib/site-packages")

with dlltracer.Trace(out=sys.stdout):
    import OpenImageIO

Running this code led to this output:

LoadLibrary C:/Windows/System32/kernel.appcore.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/OpenImageIO.cp39-win_amd64.pyd
LoadLibrary C:/Program Files/Nuke14.1v6/msvcp140.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO_Util.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll
LoadLibrary C:/Program Files/Nuke14.1v6/vcruntime140_1.dll
LoadLibrary C:/Windows/System32/shell32.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Imath_v_3_1_10_OpenImageIO_v3_0.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/zlib1.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXR_v_3_2_4_OpenImageIO_v3_0.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXRCore_v_3_2_4_OpenImageIO_v3_0.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Iex_v_3_2_4_OpenImageIO_v3_0.dll
LoadLibrary C:/Program Files/Nuke14.1v6/tiff.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/IlmThread_v_3_2_4_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/zlib1.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/IlmThread_v_3_2_4_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXR_v_3_2_4_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXRCore_v_3_2_4_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Iex_v_3_2_4_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Program Files/Nuke14.1v6/tiff.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Imath_v_3_1_10_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Windows/System32/shell32.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO_Util.dll
Failed /Device/HarddiskVolume1/Program Files/Nuke14.1v6/msvcp140.dll
Failed /Device/HarddiskVolume1/Program Files/Nuke14.1v6/vcruntime140_1.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/OpenImageIO.cp39-win_amd64.pyd
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/__init__.py", line 33, in <module>
    from .OpenImageIO import * # type: ignore # noqa: F401, F403, E402
ImportError: DLL load failed while importing OpenImageIO: The specified procedure could not be found.

We are getting somewhere! That's very useful information right there. Although it's kind of hard to really pinpoint to the problematic library, we at least know which ones failed.

If we look at these paths more closely, we see libraries being loaded from C:/Program Files/Nuke14.1v6 even if the libraries are already provided by the installed OpenTimelineIO:

PS C:\Users\Administrator\source\repos> tree /f C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO
Folder PATH listing
Volume serial number is 004C005C 1CE9:5F0A
C:\USERS\ADMINISTRATOR\ASD\LIB\SITE-PACKAGES\OPENIMAGEIO
│   OpenImageIO.cp39-win_amd64.pyd
│   __init__.py
│
├───bin
│       Iex_v_3_2_4_OpenImageIO_v3_0.dll
│       IlmThread_v_3_2_4_OpenImageIO_v3_0.dll
│       Imath_v_3_1_10_OpenImageIO_v3_0.dll
│       maketx.exe
│       oiiotool.exe
│       OpenEXRCore_v_3_2_4_OpenImageIO_v3_0.dll
│       OpenEXRUtil_v_3_2_4_OpenImageIO_v3_0.dll
│       OpenEXR_v_3_2_4_OpenImageIO_v3_0.dll
│       OpenImageIO.dll
│       OpenImageIO_Util.dll
│       tiff.dll
│       zlib1.dll
│
├───lib
... more files

If I had to guess, the missing symbol(s) are problaly in tiff.dll or zlib1.dll since both are provided by Nuke and our OIIO wheel.

Note

At this point, I didn't realize yet that I could just try to use ctypes.cdll.LoadLibrary to load OIIO's own tiff.dll or zlib1.dll manually before importing OpenImageIO to check that if that would fix the issue... This would have saved me some time. Anyway, let's continue with my thought process. We'll come back to the LoadLibrary trick at the end).

So how can we find which symbol(s) is causing issues?

We can use our good old friend dumpbin or we could script our way into the problem. At this point I got nerd sniped and decided to write a script that would tell me exactly which symbol is missing and which library is trying to load it.

I came up with this script:

import os
import collections
import sys
sys.path.append("C:/Users/Administrator/asd/Lib/site-packages")
import lief

# List of DLLs to inspect.
# The list was generated from the paths provided by dlltracer.
# At least I started with that. I then ran the script and
# looked at the errors. For each "unknown" DLL, I searched where it was
# and added it in the list. Rinse and repeat until no more errors.
dlls = [
    "C:/Program Files/Nuke14.1v6/msvcp140.dll",
    "C:/Program Files/Nuke14.1v6/tiff.dll",
    "C:/Program Files/Nuke14.1v6/vcruntime140_1.dll",
    "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Iex_v_3_2_4_OpenImageIO_v3_0.dll",
    "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/IlmThread_v_3_2_4_OpenImageIO_v3_0.dll",
    "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Imath_v_3_1_10_OpenImageIO_v3_0.dll",
    "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXRCore_v_3_2_4_OpenImageIO_v3_0.dll",
    "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXR_v_3_2_4_OpenImageIO_v3_0.dll",
    "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll",
    "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO_Util.dll",
    "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/zlib1.dll",
    "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/OpenImageIO.cp39-win_amd64.pyd",
    "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/OpenImageIO.cp39-win_amd64.pyd",
    "C:/Windows/System32/kernel.appcore.dll",
    "C:/Windows/System32/shell32.dll",
    "C:/Program Files/Nuke14.1v6/python39.dll",
    "C:/Windows/System32/GDI32.dll",
    "C:/Windows/System32/KERNEL32.dll",
    "C:/Windows/System32/KERNELBASE.dll",
    "C:/Windows/System32/MSVCP140.dll",
    "C:/Windows/System32/SHELL32.dll",
    "C:/Windows/System32/USER32.dll",
    "C:/Windows/System32/VCRUNTIME140.dll",
    "C:/Windows/System32/VCRUNTIME140_1.dll",
    "C:/Windows/System32/msvcp_win.dll",
    "C:/Windows/System32/msvcrt.dll",
    "C:/Windows/System32/ntdll.dll",
    "C:/Windows/System32/ADVAPI32.dll",
    "C:/Windows/System32/win32u.dll",
    "C:/Windows/System32/VERSION.dll",
    "C:/Windows/System32/WS2_32.dll",
    "C:/Windows/System32/SECHOST.dll",
    "C:/Windows/System32/RPCRT4.dll",
    "C:/Windows/System32/bcrypt.dll"
]

# Map DLL names to full paths.
dlls = {os.path.basename(dll): dll for dll in dlls}

# Map of DLL names to symbols
symbols = collections.defaultdict(list)

# Get the exported symbol for each DLL
for name, dllpath in dlls.items():
    dll = lief.parse(dllpath)
    for export in dll.get_export().entries:
        symbols[name].append(export.name)


# Now go over each DLL and check if the imported symbols
# are provided by any of the DLLs in the list.
for name, dllpath in dlls.items():
    dll = lief.parse(dllpath)

    # Imports in PE are organized by DLLs. So loop through
    # each DLL.
    for imported_dll in dll.imports:
        # The api-ms-win-crt-runtime-l1-1-0.dll and co don't exist. I'm not too sure
        # how they work, but I know they are not real files. I think. So let's skip them.
        if imported_dll.name.startswith("api-"):
            continue

        if imported_dll.name not in symbols:
            raise RuntimeError(f"We have not yet processed symbols for {imported_dll.name!r}")

        # Now go over each symbol imported from that DLL
        for imported_symbol in imported_dll.entries:
            # For some reason, some symbols are empty... Skip them.
            if imported_symbol.name == "":
                continue

            # Check if the imported symbol is in the list of known symbols for the DLL.
            if imported_symbol.name not in symbols[imported_dll.name]:
                # We found a missing symbol!
                print(f"{dllpath} imports {imported_symbol.name!r} from {dlls[imported_dll.name]!r} but the symbol was not found")

I added comments to the script, hopefully that is enough to explain what it does. Before running it, we need to install lief:

& 'C:/Program Files/Nuke14.1v6/python.exe' -m pip install lief --prefix C:/Users/Administrator/asd

lief is a cross-platform library to parse, modify and abstract ELF, PE and MachO formats. PE here is what we want. This is the file format used on Windows for executables and shared libraries.

With that out of the way, I ran the script and it told me:

C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFOpenOptionsAlloc' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFOpenOptionsFree' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFOpenWExt' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFClientOpenExt' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFOpenOptionsSetWarningHandlerExtR' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFOpenOptionsSetErrorHandlerExtR' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found

Tada 🎉! The problem couldn't be more clear now. The problematic library is the tiff library provided by Nuke. The wheel's OpenImageIO/bin/OpenImageIO.dll library imports (uses) TIFFOpenOptionsAlloc, TIFFOpenOptionsFree, TIFFOpenWExt, TIFFClientOpenExt, TIFFOpenOptionsSetWarningHandlerExtR and TIFFOpenOptionsSetErrorHandlerExtR but Nuke's tiff library doesn't export or provide them.

These symbols were added in libtiff 4.5.0. Nuke 14.1 ships with 4.3.0. OIIO supports 4.0 and above but was compiled with 4.6.0. And this is why it uses symbols that don't exist in 4.3.0.

And why is python loading C:/Program Files/Nuke14.1v6/tiff.dll instead of C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/tiff.dll? And why isn't zlib1 loaded from Nuke's DLL? First of all, Nuke doesn't have zlib1.dll, so that why it's being loaded from OIIO as it should. As for tiff.dll, that's because Nuke does provide that DLL. When executing C:/Program Files/Nuke14.1v6/python.exe, because of the DLL search order in Windows, Windows will load C:/Program Files/Nuke14.1v6/tiff.dll first it's in the same folder as the python executable (which is the case).

So let's test to see if we are correct and that it's really the problem. For that, we can try to load tiff.dll that comes with OIIO's wheel before importing OpenImageIO. In Python, this can be achieved by using ctypes.cdll.LoadLibrary. So let's give that a try:

PS C:\Users\Administrator\source\repos> & 'C:/Program Files/Nuke14.1v6/python.exe'
Python 3.9.10 (remotes/origin/foundry/v3.9.10:fc80903a66, Jul 21 2023, 12:01:36) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path.append("C:/Users/Administrator/asd/Lib/site-packages")

>>> import ctypes
>>> ctypes.cdll.LoadLibrary("C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/tiff.dll")
<CDLL 'C:\Users\Administrator\asd\Lib\site-packages\OpenImageIO\bin\tiff.dll', handle 7ffc47d80000 at 0x178ede76910>

>>> import OpenImageIO
>>>

Bingo! This worked. We loaded the good tiff shared library first, and this makes importing OpenImageIO work!

Note

And like I said earlier, this is what I would have tried from the beginning to narrow down which library was causing issues.

We can then verify that C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/tiff.dll contains the missing symbols:

PS C:\Users\Administrator\source\repos> dumpbin /imports:tiff.dll C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll
Microsoft (R) COFF/PE Dumper Version 14.43.34808.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\Users\Administrator\asd\Lib\site-packages\OpenImageIO\bin\OpenImageIO.dll

File Type: DLL

  Section contains the following imports:

    tiff.dll
             1806A2C48 Import Address Table
             1808635E0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                          66 TIFFReadEXIFDirectory
                          4A TIFFIsTiled
                          47 TIFFIsByteSwapped
                          35 TIFFGetField
                          7D TIFFSetDirectory
                          73 TIFFReadScanline
                          6C TIFFReadRGBAImageOriented
                          6F TIFFReadRGBATile
                          5E TIFFRGBAImageOK
                          53 TIFFOpenOptionsAlloc
                          54 TIFFOpenOptionsFree
                           9 TIFFClose
                          59 TIFFOpenWExt
                           6 TIFFClientOpen
                          74 TIFFReadTile
                          67 TIFFReadEncodedStrip
                          71 TIFFReadRawStrip
                          72 TIFFReadRawTile
                          90 TIFFSwabArrayOfShort
                          8E TIFFSwabArrayOfLong
                          43 TIFFGetVersion
                           E TIFFCreateEXIFDirectory
                          80 TIFFSetField
                          AB TIFFWriteDirectory
                          AA TIFFWriteCustomDirectory
                           4 TIFFCheckpointDirectory
                          B0 TIFFWriteScanline
                           7 TIFFClientOpenExt
                          36 TIFFGetFieldDefaulted
                          B1 TIFFWriteTile
                          AE TIFFWriteRawStrip
                          57 TIFFOpenOptionsSetWarningHandlerExtR
                          AF TIFFWriteRawTile
                          22 TIFFFieldReadCount
                          21 TIFFFieldPassCount
                          1E TIFFFieldDataType
                          2C TIFFFindField
                          55 TIFFOpenOptionsSetErrorHandlerExtR

  Summary

      160000 .data
       3A000 .pdata
      1D1000 .rdata
        D000 .reloc
        1000 .rsrc
      6A0000 .text
        1000 _RDATA

Yep, they are all there.

Tip

Note how I used /imports:tiff.dll instead of a bare /imports. This instructs dumpbin to only list imports that are provided by the given DLL.

And that's it, that's one way to debug symbol issues on Windows.

@zachlewis
Copy link

This is absolutely brilliant. Thank you for going through all the effort of documenting your efforts here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment