Repository navigation
Conversation
The shared library exports the pm_* API for the FFI backend, and like any ELF shared object its internal calls to those exported functions go through the PLT, so the dynamic loader resolves them by searching the global scope before the library itself. When two copies of libprism are loaded in the same process and one of them was opened with RTLD_GLOBAL, the second copy's internal calls silently bind to the first copy. With different prism versions this mixes incompatible code and data layouts. Two copies are a realistic situation: the interpreter can ship one copy (as JRuby and TruffleRuby do for their bundled prism) while a newer prism gem builds another, and the proposal to expose the interpreter's parser as Ruby::Prism makes both being loaded at once a supported setup. -Wl,-Bsymbolic makes the linker bind the library's references to its own definitions at link time, so each copy is self-contained regardless of the flags it was loaded with. The symbols stay exported, so dlsym and FFI are unaffected. Mach-O's two-level namespace and PE imports already behave this way, so the flag is only added when SOEXT is "so". Verified on Linux by loading a prism 1.8.0 libprism.so with RTLD_GLOBAL and then this build: LD_DEBUG=bindings shows 25 cross-library pm_* bindings without the flag and none with it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
I think this is probably okay but to be honest I really don't have enough knowledge in this area. I'd love to have just one more set of eyes on this that understands it better. Maybe @flavorjones? @eregon Any idea why the tests are timing out for CRuby? |
|
Both AIs I asked about this suggested putting this variable into LDFLAGS. I don't actually know if that's good advice on here or not. Sorry I wish I was more knowledgeable about this. |
That feels like not the right thing, because FWIW, this PR seems fine to me. |
Using |
This could be useful as an extra safety measure for https://bugs.ruby-lang.org/issues/22379#note-8.
OTOH it doesn't seem strictly necessary as it seems best to always load libprism with
RTLD_LOCALto avoid this problem.A separate side effect would be slightly faster/more direct calls between exported functions in libprism.
The shared library exports the pm_* API for the FFI backend, and like any ELF shared object its internal calls to those exported functions go through the PLT, so the dynamic loader resolves them by searching the global scope before the library itself. When two copies of libprism are loaded in the same process and one of them was opened with RTLD_GLOBAL, the second copy's internal calls silently bind to the first copy. With different prism versions this mixes incompatible code and data layouts.
Two copies are a realistic situation: the interpreter can ship one copy (as JRuby and TruffleRuby do for their bundled prism) while a newer prism gem builds another, and the proposal to expose the interpreter's parser as Ruby::Prism makes both being loaded at once a supported setup.
-Wl,-Bsymbolic makes the linker bind the library's references to its own definitions at link time, so each copy is self-contained regardless of the flags it was loaded with. The symbols stay exported, so dlsym and FFI are unaffected. Mach-O's two-level namespace and PE imports already behave this way, so the flag is only added when SOEXT is "so".
Verified on Linux by loading a prism 1.8.0 libprism.so with RTLD_GLOBAL and then this build: LD_DEBUG=bindings shows 25 cross-library pm_* bindings without the flag and none with it.