Isolate v8 symbols by lloeki · Pull Request #179 · rubyjs/mini_racer (original) (raw)
Hello!
We identified an issue happening when two extensions are built with different V8 versions, then loaded at the same time.
In an ideal world, all extensions would be built with -fvisibility=hidden
, only exposing relevant symbols and avoiding clases, unfortunately that is not the case.
Indeed the libv8 gem does not build its static library with -fvisibility=hidden
. The result is that when this static library is linked into a Ruby extension (IOW a dynamic library), V8 symbols are global, irrespective of passing -fvisibility=hidden
when building the extension.
Additionally, Ruby doesn't really make an effort to load its extensions in isolated ways, resulting in the default of globally available symbols.
As a result, the second extension being loaded will resolve some of its V8 symbols to the first ones that were loaded.
To combat this, within Sqreen's fork of MiniRacer, we created a small wrapper that takes in charge loading the extension more privately by:
- enforcing all symbols to be local (via
RTLD_LOCAL
), preventing other libs to pick its symbols up - attempting to enforce binding to local symbols first (via
RTLD_DEEPBIND
), preventing this lib to pick others symbols
Unfortunately, the latter is not always available, resulting in MiniRacer's V8 symbols being picked up by other libraries, even when they use RTLD_LOCAL
(which only controls things one way). In that case we are left with no option.
This PR attempts to make MiniRacer a good citizen in two ways:
- first commit: compile with
-fvisibility=hidden
to hide any irrelevantmini_racer_extension
symbols, and be ready if/whenlibv8_monolith.a
is compiled with it too. - second commit: hide V8 symbols from
libv8_monolith.a
that wasn't built with-fvisibility=hidden
.
Considered alternatives
- linker version scripts to hide symbols, but it's only supported by GCC's
ld
-exported_symbols_list
and-unexported_symbols_list
, but it needs exhaustive generation of symbols to hide and show. Possible, but calling intonm libv8_monolith.a
and parsing the output to generate the list could be unreliable.- leveraging gem dependencies to ensure the same
libv8
version and build: unfortunately this does not mean a given lib wasn't built with a different v8, unless all gems pin theirlibv8
versions exactly in theirgemspec
(this particular one stumped us for a long time)
Excerpt of mini_racer_extension.bundle
symbol table (T
means global, t
means local`):
$ nm -C lib/mini_racer_extension.bundle | grep ' T' | head
0000000000003ce0 T _Init_mini_racer_extension
00000000001544b0 T _v8_internal_Get_Object(void*)
0000000000154870 T _v8_internal_Node_Print(void*)
0000000000154550 T _v8_internal_Print_Code(void*)
00000000001544f0 T _v8_internal_Print_Object(void*)
00000000001547c0 T _v8_internal_Print_StackTrace()
0000000000154800 T _v8_internal_Print_TransitionTree(void*)
0000000000154730 T _v8_internal_Print_LayoutDescriptor(void*)
000000000000f260 T V8_Fatal(char const*, ...)
000000000000f3b0 T V8_Dcheck(char const*, int, char const*)
Excerpt of a stack trace where code jumps from one lib to the other within libv8:
-- C level backtrace information -------------------------------------------
/Users/lloeki/.rubies/ruby-2.7.1/bin/ruby(rb_vm_bugreport+0x96) [0x106ebf616]
/Users/lloeki/.rubies/ruby-2.7.1/bin/ruby(rb_bug_for_fatal_signal+0x1d0) [0x106cf9270]
/Users/lloeki/.rubies/ruby-2.7.1/bin/ruby(sigsegv+0x5b) [0x106e22c4b]
/usr/lib/system/libsystem_platform.dylib(_sigtramp+0x1d) [0x7fff7248c5fd]
/Users/lloeki/.gem/ruby/2.7.1/gems/mini_racer-0.3.1/lib/mini_racer_extension.bundle(_ZN2v88internal12_GLOBAL__N_123GetPageTableInitializerEv+0x25) [0x10bade405]
/Users/lloeki/.gem/ruby/2.7.1/gems/mini_racer-0.3.1/lib/mini_racer_extension.bundle(0x10badea39) [0x10badea39]
/Users/lloeki/.gem/ruby/2.7.1/gems/mini_racer-0.3.1/lib/mini_racer_extension.bundle(0x10b77b3b6) [0x10b77b3b6]
/Users/lloeki/.gem/ruby/2.7.1/gems/mini_racer-0.3.1/lib/mini_racer_extension.bundle(0x10b77bc5e) [0x10b77bc5e]
/Users/lloeki/Workspace/github.com/sqreen/mini_racer/lib/sq_mini_racer_extension.bundle(_ZN2v88internal9SemiSpace6CommitEv+0x7a) [0x10d81df5a]
/Users/lloeki/Workspace/github.com/sqreen/mini_racer/lib/sq_mini_racer_extension.bundle(0x10d81de76) [0x10d81de76]
/Users/lloeki/Workspace/github.com/sqreen/mini_racer/lib/sq_mini_racer_extension.bundle(0x10d7a35ef) [0x10d7a35ef]
/Users/lloeki/Workspace/github.com/sqreen/mini_racer/lib/sq_mini_racer_extension.bundle(0x10d74cf00) [0x10d74cf00]
/Users/lloeki/Workspace/github.com/sqreen/mini_racer/lib/sq_mini_racer_extension.bundle(0x10d74c9fd) [0x10d74c9fd]
/Users/lloeki/Workspace/github.com/sqreen/mini_racer/lib/sq_mini_racer_extension.bundle(0x10d5e78fb) [0x10d5e78fb]
/Users/lloeki/Workspace/github.com/sqreen/mini_racer/lib/sq_mini_racer_extension.bundle(0x10d5e79af) [0x10d5e79af]
/Users/lloeki/Workspace/github.com/sqreen/mini_racer/lib/sq_mini_racer_extension.bundle(_ZN11IsolateInfo4initEP12SnapshotInfo+0xc2) [0x10d3ba012]
/Users/lloeki/Workspace/github.com/sqreen/mini_racer/lib/sq_mini_racer_extension.bundle(_ZL22rb_context_init_unsafemmm+0x126) [0x10d3bc016]
/Users/lloeki/.rubies/ruby-2.7.1/bin/ruby(vm_call_cfunc+0x16c) [0x106eb0cdc]
/Users/lloeki/.rubies/ruby-2.7.1/bin/ruby(vm_exec_core+0x38b0) [0x106e97050]