4

I am using embedded Python (3.9) in ubuntu 20.04 and trying to import ctypes which produces the error _ctypes.cpython-39-x86_64-linux-gnu.so: undefined symbol: PyFloat_Type.

I am compiling a shared object, which is loaded dynamically using dlopen().

CMake is used to build the shared object. I am stating Python3 dependency like so: find_package(Python3 REQUIRED COMPONENTS Development Development.Embed) and link using target_link_libraries(${target_name} Boost::filesystem Python3::Python)

If I understand correctly, this tells CMake to link directly with libpython3.9.so (I also tried to explicitly state linking to libpython3.9.so, but that did not solve the issue). I do see that libpython3.9.so exports PyFloat_Type and that _ctypes.cpython-39-x86_64-linux-gnu.so does not.

The import is simply done by the PyRun_SimpleString() function: PyRun_SimpleString("import ctypes").

I should state that I have seen on the web some solutions, but none of them worked (like exporting LD_FLAGS="-rdynamic", but does also did not help).

I should also point out that importing using the interpreter (python3.9) works well.

Here is the build command generated by CMake: /usr/bin/c++ -fPIC -g -Xlinker -export-dynamic -shared -Wl,-soname,mytest.python3.so -o mytest.python3.so CMakeFiles/mytest.python3.dir/[mydir]/[myobjects].o /usr/lib/x86_64-linux-gnu/libboost_filesystem.so.1.71.0 /usr/lib/x86_64-linux-gnu/libpython3.9.so /usr/lib/x86_64-linux-gnu/libpython3.9.so

Thanks for any help in advance!

1
  • 1
    I have resolved the issue by loading my shared object using RTLD_NOW|RTLD_GLOBAL in the dl_open Commented Jun 8, 2021 at 20:39

1 Answer 1

10

When a C-extension is imported in CPython on Linux, dlopen is used under the hood (and per default with RTLD_LOCAL-flag).

A C-extension usually needs functionality from the Python-library (libpythonX.Y.so), like for example PyFloat_Type. However, on Linux the C-extension isn't linked against the libpythonX.Y.so (the situation is different on Windows, see this or this for more details) - the missing function-definition/functionality will be provided by the python-executable.

In order to be able to do so, the executable must be linked with -Xlinker -export-dynamic, otherwise loader will not be able to use the symbols from the executable for shared objects loaded with dlopen.

Now, if the embedded python is not the executable, but a shared object, which loaded with dlopen itself, we need to ensure that its symbols are added to the dynamic table. Building this shared object with -Xlinker -export-dynamic doesn't make much sense (it is not an executable after all) but doesn't break anything - the important part, how dlopen is used.

In order to make symbols from text.python.so visible for shared objects loaded later with dlopen, it should be opened with flag RTLD_GLOBAL:

RTLD_GLOBAL The symbols defined by this shared object will be made available for symbol resolution of subsequently loaded shared objects.

i.e.

shared_lib = dlopen(path_to_so_file, RTLD_GLOBAL | RTLD_NOW);

Warning: RTLD_LAZY should not be used.

The issue with RTLD_LAZY is that C-extensions do not have dependency on the libpython (as can be seen with help of ldd), so once they are loaded and a symbol (e.g. PyFloat_Type) from libpython which is not yet resolved must be looked up, dynamic linker doesn't know that it has to look into the libpython.

On the other hand with RTLD_NOW, all symbols are resolved and are visible when a C-extension is loaded (it is the same situation as in the "usual" case when libpython is linked in during the linkage step with -Xlinker -export-dynamic) and thus there is no issue finding e.g. PyFloat_Type-symbol.


As long as the embedded python is loaded with dlopen, the main executable doesn't need to be built/linked with -Xlinker -export-dynamic.

However, if the main executable is linked against the embedded-python-shared-object, -Xlinker -export-dynamic is necessary, otherwise the python-symbols won't be visible when dlopen is used during the import of c-extension.


One might ask, why aren't C-extension linked against libpython in the first place?

Due to used RTLD_LOCAL, every C-extension would have its own (uninitialized) version of Python-interpreter (as the symbols from the libpython would not be interposed) and crash as soon as used.

To make it work, dlopen should be open with RTLD_GLOBAL-flag - but this is not a sane default option.

Sign up to request clarification or add additional context in comments.

4 Comments

Doesn't dlopen( ) require either RTLD_LAZY or RTLD_NOW? Which should be OR'd with RTLD_GLOBAL?
@Jiminion I have added the explicit call (with RTLD_GLOBAL) and information why RTLD_LAZY must not be used.
I still confused about why aren't C-extension linked against libpython in the first place? Is it by design?
@AnonymousX I don't know why this decision was made, but python-exe has all symbols from libpython already. So if the extension would be linked against libpython it would be quite a mess as all symbols (and singletons!) would be present twice.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.