Created
October 25, 2025 15:14
-
-
Save AlexanderSavochkin/4ba5b67edd617e3833c4ea3645ef3ac7 to your computer and use it in GitHub Desktop.
libX Python shim: prefer in-DSO symbols via RTLD_DEEPBIND
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Self-binding loader shim for libX (RTLD_DEEPBIND) | |
| Purpose | |
| ------- | |
| Ensure that libX’s **own relocations** bind to the symbols embedded inside libX | |
| (e.g., its statically linked LLVM) rather than to similarly named symbols that | |
| may already exist in the process (e.g., ROCm/Torch’s libLLVM). This removes any | |
| fragile “import order” requirement in Python. | |
| What this is (and isn’t) | |
| ------------------------ | |
| - This is a **loader-time** tweak: we set dlopen(3) flags so that when Python | |
| imports the compiled libX extension, its relocations **prefer in-DSO symbols**. | |
| - It does **not** relink or modify libX. It just controls how the dynamic | |
| loader resolves symbols for this one load. | |
| Flags used | |
| ---------- | |
| - RTLD_NOW : resolve all relocations immediately (fail fast if anything is missing). | |
| - RTLD_LOCAL : do not export libX’s symbols to satisfy symbols in subsequently loaded DSOs. | |
| - RTLD_DEEPBIND (glibc only) : | |
| When resolving relocations *inside* libX, prefer symbols found in libX itself | |
| over those already present in the global namespace. This is the runtime analog | |
| of preferring “self” similar to linking with -Bsymbolic. | |
| Why this helps | |
| -------------- | |
| If the process already loaded another DSO with overlapping C++ symbols (e.g., ROCm’s | |
| libLLVM), a plain import of libX might bind libX’s vtables/RTTI/calls to that other | |
| copy, causing ODR violations and crashes. DEEPBIND flips the preference so libX binds | |
| to its **own** copy. | |
| Risks / caveats | |
| --------------- | |
| - **Interposer bypass**: DEEPBIND can bypass LD_PRELOAD hooks and some sanitizer/ | |
| profiler interceptors (e.g., custom malloc, ASan/TSan interceptors) for calls | |
| made from inside libX. | |
| - **Portability**: RTLD_DEEPBIND is **GNU/glibc-only**. It’s ignored on musl and | |
| does not exist on macOS/Windows. The shim falls back to RTLD_NOW|RTLD_LOCAL. | |
| - **Scope**: Affects only how *this* import of libX is relocated. It does not | |
| isolate other DSOs from each other; for stronger isolation consider dlmopen() | |
| into a separate link-map namespace. | |
| Quick start | |
| ----------- | |
| Place this at package import before pulling in the compiled extension: | |
| import os, sys | |
| old = getattr(sys, "getdlopenflags", lambda: None)() | |
| flags = 0 | |
| flags |= getattr(os, "RTLD_NOW", 0) | |
| flags |= getattr(os, "RTLD_LOCAL", 0) | |
| flags |= getattr(os, "RTLD_DEEPBIND", 0) # 0 on non-glibc → safe no-op | |
| if old is not None and flags: | |
| sys.setdlopenflags(flags) | |
| try: | |
| from . import _libx # the compiled extension (triggers dlopen with flags) | |
| finally: | |
| if old is not None: | |
| sys.setdlopenflags(old) | |
| Troubleshooting | |
| --------------- | |
| - Verify stable bindings regardless of import order: | |
| $ LD_DEBUG=bindings,libs python -c "import yourpkg; import torch" | |
| You should no longer see libX’s LLVM symbols binding to ROCm’s libLLVM. | |
| - Ensure libX does not DT_NEEDED a shared libLLVM if you expect a private/static one: | |
| $ readelf -dW libX.so | grep NEEDED | |
| Terminology | |
| ----------- | |
| DSO = Dynamic Shared Object (a shared library, e.g., libfoo.so) loaded by the | |
| ELF dynamic loader at runtime. | |
| Reference | |
| --------- | |
| dlopen(3), dlsym(3); GNU glibc extension RTLD_DEEPBIND; linker-time analog: | |
| -Wl,-Bsymbolic (build-time, not used here). | |
| """ | |
| flags = 0 | |
| flags |= getattr(os, "RTLD_NOW", 0) | |
| flags |= getattr(os, "RTLD_LOCAL", 0) | |
| flags |= getattr(os, "RTLD_DEEPBIND", 0) # 0 on non-glibc → safe no-op | |
| try: | |
| old = sys.getdlopenflags() | |
| sys.setdlopenflags(flags or old) | |
| from . import _libx | |
| finally: | |
| if 'old' in locals(): | |
| sys.setdlopenflags(old) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment