Skip to content

ruby: support EC offset extraction for statically-linked Ruby#1227

Merged
fabled merged 3 commits intoopen-telemetry:mainfrom
Shopify:ruby-static-ec-tls-offset-upstream
Mar 19, 2026
Merged

ruby: support EC offset extraction for statically-linked Ruby#1227
fabled merged 3 commits intoopen-telemetry:mainfrom
Shopify:ruby-static-ec-tls-offset-upstream

Conversation

@dalehamel
Copy link
Copy Markdown
Contributor

What

Support profiling statically-linked Ruby binaries (bin/ruby with no libruby.so) by extracting the execution context (EC) TLS offset directly from the rb_current_ec_noinline function's disassembly.

Fixes #884
Related to #883 (handles static case, at least for ruby)

Why

The existing Ruby profiler only matches libruby*.so via regex and finds the EC via TLSDESC relocations. Statically-linked Ruby has no TLSDESC — instead rb_current_ec_noinline accesses EC directly via FS:offset (x86_64) or MRS tpidr_el0 (aarch64). Without this change, statically-linked Ruby processes produce no Ruby frames at all.

How

  • Adds binRubyRegex to match bin/ruby in addition to libruby*.so
  • Adds extractEcTLSOffset() in interpreter/ruby/ec.go that disassembles rb_current_ec_noinline using the existing asm/amd.ExtractTLSOffset and asm/arm.ExtractTLSOffset infrastructure from python: get the Thread State from a Thread-local #1109 (Python TLS)
  • Falls back to VisitSymbols when the symbol is LOCAL (not in .dynsym) — fixes a nil pointer panic in the GNU hash lookup path
  • Changes current_ec_tpbase_tls_offset from u64 to s64 since static TLS offsets are negative on x86_64 (e.g. FS:0xfffffffffffffff8 = -8)
  • Adds RUBY_DISABLE_GC env var support to the coredump test script loop.rb
  • Includes coredump tests for both arm64 and amd64

NOTE: This depends on coredump artifacts which need to be added to the CI modulestore, they've been uploaded to a shared drive that @florianl has access to. Please re-run CI after uploading these artifacts.

Copy link
Copy Markdown

@ianks ianks left a comment

Choose a reason for hiding this comment

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

In principle the changes make sense to me. Would recommend a Gopher take a closer look though since my Go knowledge is limited

For statically-linked Ruby binaries (bin/ruby rather than libruby.so),
TLS descriptors are not available. Instead, rb_current_ec_noinline
accesses the execution context directly via a TP-relative offset
(FS:offset on x86_64, MRS tpidr_el0 + ADD on aarch64).

This reuses the asm/amd.ExtractTLSOffset and asm/arm.ExtractTLSOffset
infrastructure from the Python TLS PR (open-telemetry#1109) to disassemble
rb_current_ec_noinline and extract the offset.

Also changes current_ec_tpbase_tls_offset from u64 to s64 since
static TLS offsets (local exec model) are negative on x86_64.

Also adds RUBY_DISABLE_GC env var support to the loop.rb test script
to allow capturing coredumps without GC interference.
Adds coredump tests for a statically-linked Ruby 3.4.7 binary on both
arm64 and amd64, exercising the binRubyRegex match and
extractEcTLSOffset() disassembly path for finding ruby_current_ec
via direct TP-relative offset.

Captured via gdb attach + breakpoint on rb_vm_exec to ensure a clean
snapshot deep in the Ruby VM stack.
@dalehamel dalehamel force-pushed the ruby-static-ec-tls-offset-upstream branch from a2ccaf1 to 8c2edc7 Compare March 17, 2026 20:24
Copy link
Copy Markdown
Contributor

@fabled fabled left a comment

Choose a reason for hiding this comment

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

lgtm

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Support static bin/ruby builds for ruby interpreter

4 participants