Skip to content

Instantly share code, notes, and snippets.

@AlexanderSavochkin
Created October 25, 2025 15:14
Show Gist options
  • Select an option

  • Save AlexanderSavochkin/4ba5b67edd617e3833c4ea3645ef3ac7 to your computer and use it in GitHub Desktop.

Select an option

Save AlexanderSavochkin/4ba5b67edd617e3833c4ea3645ef3ac7 to your computer and use it in GitHub Desktop.
libX Python shim: prefer in-DSO symbols via RTLD_DEEPBIND
"""
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