Skip to content

Link libprism.so with -Bsymbolic on ELF platforms - #4246

Draft
eregon wants to merge 1 commit into
ruby:mainfrom
eregon:bsymbolic-libprism
Draft

eregon wants to merge 1 commit into
ruby:mainfrom
eregon:bsymbolic-libprism

Conversation

@eregon

@eregon eregon commented Oct 7, 2026

Copy link
Copy Markdown
Member

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_LOCAL to 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.

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>
@kddnewton

Copy link
Copy Markdown
Collaborator

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?

@kddnewton

Copy link
Copy Markdown
Collaborator

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.

@froydnj

froydnj commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

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.

That feels like not the right thing, because -Bsymbolic is specific to shared libraries. Maybe if this was building shared libraries only it would be the right tack, but I think Prism wants to build static and shared libraries?

FWIW, this PR seems fine to me.

@flavorjones

Copy link
Copy Markdown
Contributor

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?

Using -Bsymbolic looks right to me, and I also agree it should only be applied to the shared libraries (as written in 2c245c7). 👍 from me.

@kddnewton kddnewton left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks everyone! @eregon I think the CRuby failure was just a flaky apt-get. I think I'm fine rolling with this.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants