Repository navigation
extension built with a shared python cannot be loaded with a static python #65735
Description
Activity
When a C extension is built (using distutils) with a shared library Python, it cannot be loaded with an otherwise identical statically linked Python. The other way round works fine. Trivial example using the _ssl module:
>>> import sys >>> sys.path.insert(0, '/home/antoine/cpython/shared/build/lib.linux-x86_64-3.5/') >>> import _ssl Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: libpython3.5m.so.1.0: cannot open shared object file: No such file or directory
This is probably because of an additional -L flag that is passed when linking a C extension with a shared library Python. I don't think the flag is useful under Linux (or perhaps under any other OS at all), since the relevant symbols are already loaded when the interpreter tries to load the C extension.
(AFAIK, systems notorious for providing shared library Pythons are RedHat-alike systems, while Debian/Ubuntu provide statically linked Pythons)
- addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
on May 19, 2014 Actually, it's not a -L flag but a -l flag. Removing the "-lpython3.5m" flag from the linker line works fine under Linux, and allows the resulting extension to be loaded with both a shared libary Python and a statically-linked Python.
I think you are right. It would IMO be useful to research a few comparable systems. E.g. Apache modules don't link a shared library, but still refer to apr_ functions as undefined symbols - but then, there isn't an APR shared library in the first place (at least not on Debian - how about Redhat?)
PHP might be close to our case: Debian includes a libphp5.so (in /usr/lib/php5), yet neither /usr/bin/php5 nor the Apache libphp5.so link against it, and all the PHP modules (in /usr/lib/php5/20100525+lfs/) don't link with the shared library - on Debian. I wonder how it is on systems that actually use the PHP shared library.
PHP might be close to our case: Debian includes a libphp5.so
(in /usr/lib/php5), yet neither /usr/bin/php5 nor the Apache libphp5.so
link against it, and all the PHP modules (in
/usr/lib/php5/20100525+lfs/) don't link with the shared library -
on Debian. I wonder how it is on systems that actually use the PHP
shared library.Regardless of libphp5.so, the modules in /usr/lib/php5/20100525 don't link against /usr/lib/apache2/modules/libphp5.so, though the latter is obviously able to load those modules.
Hmm, apparently the -l flag was added in bpo-832799, for a rather complicated case where the interpreter is linked with a library dlopened by an embedding application (I suppose for some kind of plugin system). The OP there also mentions RTLD_GLOBAL as a workaround (or perhaps the right way of achieving the desired effect).
(also, the OP didn't mention why he used a shared library build, instead of linking Python statically with the dlopened library)
Martin, what do you think about the aforementioned use case?
See also bpo-34814.
Antoine:
Hmm, apparently the -l flag was added in bpo-832799, for a rather complicated case where the interpreter is linked with a library dlopened by an embedding application (I suppose for some kind of plugin system). The OP there also mentions RTLD_GLOBAL as a workaround (or perhaps the right way of achieving the desired effect).
(also, the OP didn't mention why he used a shared library build, instead of linking Python statically with the dlopened library)bpo-34814 is linked to this use case: https://bugzilla.redhat.com/show_bug.cgi?id=1585201 is an example of Python embedded in C using dlopen("libpython2.7.so.1.0", RTLD_LOCAL | RTLD_NOW). Problem: some C extensions of the standard library cannot be loaded in this case, like _struct.
On Fedora and RHEL, some C extensions like _struct are built by the "*shared*" section of Modules/Setup. In this case, these C extensions are not explicitly linked to libpython.
IHMO it's a bad usage of dlopen(): libpython must always be loaded with RTLD_GLOBAL.
bpo-832799 has been fixed by the following commit which modify distutils to link C extensions to libpython:
commit 10acfd0
Author: Martin v. Löwis <martin@v.loewis.de>
Date: Mon Apr 10 12:39:36 2006 +0000Patch bpo-1429775: Link Python modules to libpython on linux if --enable-shared. Fixes bpo-832799.bpo-34814 is linked to this use case: https://bugzilla.redhat.com/show_bug.cgi?id=1585201 is an example of Python embedded in C using dlopen("libpython2.7.so.1.0", RTLD_LOCAL | RTLD_NOW). Problem: some C extensions of the standard library cannot be loaded in this case, like _struct.
bpo-1429775 is another example of RTLD_LOCAL usage, see:
https://mail.python.org/pipermail/python-dev/2006-February/060533.htmlI wrote PR 12946: "On Unix, C extensions are no longer linked to libpython".
Using PR 12946, the use case described in the initial message (msg218806) now works as expected. I can load a C extension built by a shared library Python with a statically linked Python:
$ LD_LIBRARY_PATH=/opt/py38shared/lib /opt/py38notshared/bin/python3.8 Python 3.8.0a3+ (heads/master:be0099719c, Apr 25 2019, 02:10:57) >>> import sys; sys.path.insert(0, '/opt/py38shared/lib/python3.8/lib-dynload') >>> import _ssl >>> _ssl <module '_ssl' from '/opt/py38shared/lib/python3.8/lib-dynload/_ssl.cpython-38-x86_64-linux-gnu.so'>
/opt/py38notshared/bin/python3.8 is statically linked, whereas /opt/py38shared/lib/python3.8/lib-dynload/_ssl.cpython-38-x86_64-linux-gnu.so comes from a shared library Python.
Install shared libray Python into /opt/py38shared:
git clean -fdx; ./configure CFLAGS="-O0" --enable-shared --prefix /opt/py38shared && make && make install
Install statically linked Python into /opt/py38notshared:
git clean -fdx; ./configure CFLAGS="-O0" --prefix /opt/py38notshared && make && make install
--
As Antoine said, the opposite already works. Just in case, I also tested and I confirm that it still works:
$ LD_LIBRARY_PATH=/opt/py38shared/lib /opt/py38shared/bin/python3.8 Python 3.8.0a3+ (heads/master:be0099719c, Apr 25 2019, 02:09:02) >>> import sys; sys.path.insert(0, '/opt/py38notshared/lib/python3.8/lib-dynload') >>> import _ssl >>> _ssl <module '_ssl' from '/opt/py38notshared/lib/python3.8/lib-dynload/_ssl.cpython-38-x86_64-linux-gnu.so'>
_ssl comes from statically linked Python (/opt/py38notshared) and is loaded in shared library Python (/opt/py38shared).
22 remaining items
This completely breaks building extension modules on Windows-based platforms like Cygwin and MinGW, where it is necessary when building even shared libraries for all global symbols to resolvable at link time.
C extensions are always linked to libpython on Android. I'm open to do something similar for Cygwin and/or MinGW if you can come up with a PR :-) See LIBPYTHON variable in configure.ac.
You can use Xavier's change as an example: commit 254b309, PR 12989.
I merged E. M. Bray's PR to get in Python 3.8 beta 1. If anything goes wrong, we can fix it later ;-)
FWIW I checked the Android build after those last changes.
FWIW I checked the Android build after those last changes.
Thank you! I was overconfident when I merged the Cygwin which looked good to me, whereas it broke Android :-( Hopefully it was quickly fixed. I hope that everything will be alright for the the beta1 to start from a good milestone!
Thanks everyone. And FWIW I agree the original change is positive overall, if a bit presumptuous about different linkers' behaviors :)
Just ran into this issue while cross-compiling python 3.9.2 for Android API 32 and managed to get the shared extensions working. Made only a few small modifications to the Makefile.
First added -Wl,-Bsymbolic to all shared libraries in the Android build as the Debian package has a patch to add this to the Linux build (actually -Wl,-Bsymbolic-functions) and i seemed like a good idea to copy that. Mainly because some time ago it was a de-facto standard to add this flag to every native library build [0]. Probably this is unrelated and not required [1].
Then found an interesting section in the man page of ld:
global This option is only meaningful when building a shared object. It makes the symbols defined by this shared object available for symbol resolution of subsequently loaded libraries.So rebuilt libpython3.9.so with -Wl,-z,global
And voila, it rocks! Virtually this marks the shared library to be treated as if it had been loaded with RTLD_GLOBAL, moving all its symbols to the global namespace.
The parameter just turns on a flag in the ELF header, so it could be possible to try its effect with an ELF editor much faster than recompiling.0x000000000000001e (FLAGS) SYMBOLIC BIND_NOW 0x000000006ffffffb (FLAGS_1) Flags: NOW GLOBAL[0] https://gcc.gcc.gnu.narkive.com/ceGm0WCb/android-the-reason-why-bsymbolic-is-turned-on-by-default
[1] https://www.technovelty.org/c/what-exactly-does-bsymblic-do.html- added a commit that references this issue
on Aug 14, 2026
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields: