-
-
Notifications
You must be signed in to change notification settings - Fork 30.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
extension built with a shared python cannot be loaded with a static python #65735
Comments
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) |
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. |
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:
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
|
bpo-1429775 is another example of RTLD_LOCAL usage, see: |
I 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). |
With an additonal change on SOABI (I will open a separated issue for that), PR 12946 allows to load lxml built in release mode in a Python built in debug mode! That's *very* useful for debugging: see my gdb example below. --- I just modified the ABI of debug build so release and debug build now have the same ABI: bpo-36465. I wrote a patch to use the same sys.implementation.cache_tag (SOABI) in release and debug mode: diff --git a/configure b/configure
index b02d17c053..38eb7f1bd6 100755
--- a/configure
+++ b/configure
@@ -6325,7 +6325,6 @@ $as_echo "#define Py_DEBUG 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; };
Py_DEBUG='true'
- ABIFLAGS="${ABIFLAGS}d"
else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }; Py_DEBUG='false'
fi
diff --git a/configure.ac b/configure.ac
index 65d3f8e691..1b2cd3076c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1223,7 +1223,6 @@ then
[Define if you want to build an interpreter with many run-time checks.])
AC_MSG_RESULT(yes);
Py_DEBUG='true'
- ABIFLAGS="${ABIFLAGS}d"
else AC_MSG_RESULT(no); Py_DEBUG='false'
fi],
[AC_MSG_RESULT(no)]) (That's a temporary patch, I will design a better solution later.) Using this patch + PR 12946, it becomes possible to load a C extension compiled in release mode in a debug Python! --- Example building lxml in release mode and then load it in debug mode. Install Python in *release* mode into /opt/py38release (shared libpython): git clean -fdx; ./configure --enable-shared --prefix /opt/py38release && make && make install Install Python in *debug* mode into /opt/py38debug (shared libpython): git clean -fdx; ./configure CFLAGS="-O0" --enable-shared --prefix /opt/py38debug --with-pydebug && make && make install Build lxml in release mode: LD_LIBRARY_PATH=/opt/py38release/lib/ /opt/py38release/bin/python3.8 -m pip install lxml By default, the debug Python doesn't have lxml: $ LD_LIBRARY_PATH=/opt/py38debug/lib/ /opt/py38debug/bin/python3.8 -c 'import lxml'
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'lxml' Give access to C extensions compiled in release mode to the debug Python: $ LD_LIBRARY_PATH=/opt/py38debug/lib/ PYTHONPATH=/opt/py38release/lib/python3.8/site-packages /opt/py38debug/bin/python3.8
Python 3.8.0a3+ (heads/omit_libpython-dirty:8a03782387, Apr 25 2019, 02:52:01)
>>> import lxml
>>> import lxml.etree
>>> lxml.etree
<module 'lxml.etree' from '/opt/py38release/lib/python3.8/site-packages/lxml/etree.cpython-38-x86_64-linux-gnu.so'>
>>> import sys
>>> sys.gettotalrefcount()
108304 It works! Explanation:
--- Now let's have a look at the gdb experience of release vs debug Python build. I put a breakpoint on lxml.etree.iterparse('example.xml'): the C function is called __pyx_tp_new_4lxml_5etree_iterparse. Using the release build, gdb fails to read many C local variables: $ LD_LIBRARY_PATH=/opt/py38release/lib/ gdb -args /opt/py38release/bin/python3.8 parse.py (gdb) source /home/vstinner/prog/python/master/python-gdb.py Breakpoint 1, __pyx_tp_new_4lxml_5etree_iterparse (t=0x7fffea724900 <__pyx_type_4lxml_5etree_iterparse>, a=('example.xml',), k=0x0) at src/lxml/etree.c:218930 (gdb) py-bt
Traceback (most recent call first):
File "parse.py", line 4, in func
context = etree.iterparse('example.xml')
File "parse.py", line 12, in <module>
func("arg") (gdb) frame 4 The basic function "py-bt" works as expected, but inspecting Python internals doesn't work: most local C variables are "optimized out" :-( New attempt using a debug build: $ LD_LIBRARY_PATH=/opt/py38debug/lib/ PYTHONPATH=/opt/py38release/lib/python3.8/site-packages gdb -args /opt/py38debug/bin/python3.8 parse.py ... same commands to load python-gdb.py and put a breakpoint ... Breakpoint 1, __pyx_tp_new_4lxml_5etree_iterparse (t=0x7fffea606900 <__pyx_type_4lxml_5etree_iterparse>, a=('example.xml',), k=0x0) at src/lxml/etree.c:218930 (gdb) py-bt
Traceback (most recent call first):
File "parse.py", line 4, in func
context = etree.iterparse('example.xml')
File "parse.py", line 12, in <module>
func("arg") (gdb) frame 4 The debugging experience is *much* better: it's possible to inspect Python internals! |
If an application is linked to libpython to embed Python, the application build requires flags to link to libpython. Currently, there are: $ python3-config --libs
-lpython3.7m -lcrypt -lpthread -ldl -lutil -lm
$ pkg-config --libs python-3.7
-lpython3.7m My PR doens't change Misc/python.pc. Discussion on python-dev: |
I created bpo-36721 "Add pkg-config python-3.8-embed" to discuss this issue. |
Since this change is causing subtle issues like bpo-36721 "Add pkg-config python-3.8-embed", I don't think that it would be a good idea to backport this change to Python 2.7 or 3.7. I close the issue. See also bpo-36722 "In debug build, load also C extensions compiled in release mode or compiled using the stable ABI". |
Please revert the change in Makefile.pre.in made by changeset 8c3ecc6 as it breaks builds made out of the source tree (OST). The error message is:
When the source tree has been already built at least once and 'make clean' is run in the source tree (as required for building OST), the OST build does not fail but incorrectly uses the stale python-config.sh from the source tree as 'make clean' does not remove Misc/python-config.sh. |
Oh. I tried to "fix" the Makefile but it seems like I misunderstood how Misc/python-config.sh is handled. This file is generated from Misc/python-config.sh.in. I wrote 12971 to revert the change but also add a comment to explain the "magic" behind this file. |
I tested manually and I confirm that my latest change fix the compilation out of the source tree. I close again the issue. Sorry for the regression, it's now fixed. |
Thanks for fixing the regression Victor. Here is another potential problem. On Android API 24 built with NDK r19, symbol resolution fails in the _socket shared library after changeset 8c3ecc6 when python is built with '--enable-shared': generic_x86_64:/data/local/tmp/python $ python -c "import _socket"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ImportError: dlopen failed: cannot locate symbol "PyByteArray_Type" referenced by "/data/local/tmp/python/lib/python3.8/lib-dynload/_socket.cpython-38d.so"... This does not happen when the build is configured without '--enable-shared'. An NDK issue [1] reports the same problem and dimitry in this issue explains that on Android >= 23 the shared library must be linked against the shared library where the symbol is defined. Dimitry says that the Android loader was fixed in M (i.e. API 23) and it must refer to the changes listed in the "RTLD_LOCAL (Available in API level >= 23)" section of "Android changes for NDK developers" [2]. [1] android/ndk#201 |
I reopen the issue for the Android case. I am fine with having a different linking policy per platform. For example, Windows has a different policy than Linux. We can continue to link C extensions to libpython on Android. The ability to load release C extension in debug Python is a feature more for debugging. I am not sure that Android is the best platform for debugging anyway. The debug can be done on a desktop Linux for example, no? |
Xavier: would you be interested to work on a fix for Android? |
PR 12989 fixes the Android issue and has been checked on the Android emulator at API 24. Python is cross-compiled with '--enable-shared' and the python-config scripts give the expected result: generic_x86_64:/data/local/tmp/python/bin $ python -c "from sysconfig import get_config_var; print(get_config_var('Py_ENABLE_SHARED'))" |
As explained in bpo-34814, this change not only breaks RTLD_LOCAL of libpython, but it breaks it in fact system-wide. |
I vaguely recall seeing some discussion about this on python-dev or elsewhere and wish I had chimed in sooner, as I didn't realize action was going to be taken on this so soon. 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. I'm not actually sure this use case can even be achieved on these platforms. I tried several hacks but nothing works. I'll open a follow-up issue... |
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. |
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:
So rebuilt libpython3.9.so with -Wl,-z,global
[0] https://gcc.gcc.gnu.narkive.com/ceGm0WCb/android-the-reason-why-bsymbolic-is-turned-on-by-default |
libpython.so loads all dependent libraries lazily. This does not work for us, so by using this dummy wrapper, we force the loader to resolve all lib dependencies at load time. eg, see: python/cpython#65735 (comment) Signed-off-by: Anastassios Nanos <ananos@nubificus.co.uk>
libpython.so loads all dependent libraries lazily. This does not work for us, so by using this dummy wrapper, we force the loader to resolve all lib dependencies at load time. eg, see: python/cpython#65735 (comment) Signed-off-by: Anastassios Nanos <ananos@nubificus.co.uk>
libpython.so loads all dependent libraries lazily. This does not work for us, so by using this dummy wrapper, we force the loader to resolve all lib dependencies at load time. eg, see: python/cpython#65735 (comment) Signed-off-by: Anastassios Nanos <ananos@nubificus.co.uk>
libpython.so loads all dependent libraries lazily. This does not work for us, so by using this dummy wrapper, we force the loader to resolve all lib dependencies at load time. eg, see: python/cpython#65735 (comment) Signed-off-by: Anastassios Nanos <ananos@nubificus.co.uk>
libpython.so loads all dependent libraries lazily. This does not work for us, so by using this dummy wrapper, we force the loader to resolve all lib dependencies at load time. eg, see: python/cpython#65735 (comment) Signed-off-by: Anastassios Nanos <ananos@nubificus.co.uk>
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:
The text was updated successfully, but these errors were encountered: