Skip to content

ruby: add DTV-based TLS access for ruby_current_ec#29

Draft
dalehamel wants to merge 2 commits intomainfrom
ruby-dtv-ec-lookup
Draft

ruby: add DTV-based TLS access for ruby_current_ec#29
dalehamel wants to merge 2 commits intomainfrom
ruby-dtv-ec-lookup

Conversation

@dalehamel
Copy link
Member

When TLSDESC relocations are unavailable (e.g. some musl setups or dynamically allocated TLS), the Ruby execution context can still be found by traversing the Dynamic Thread Vector (DTV).

This commit:

  • Adds a shared dtv_read() helper in tsd.h (alongside existing tsd_read) that traverses the DTV using DTVInfo from PR Extract DTV info from __tls_get_addr, add to LibcInfo open-telemetry/opentelemetry-ebpf-profiler#929's libc introspection. This helper is available to all interpreters, not just Ruby.

  • Generalizes VisitTLSRelocations into VisitRelocations with a pluggable relocation type filter, enabling lookup of DTPMOD64 relocations to find the TLS module ID offset for libruby.so.

  • Adds DTVInfo, current_ec_tls_offset, and tls_module_id fields to RubyProcInfo so the eBPF unwinder can use DTV traversal.

  • Implements UpdateLibcInfo for Ruby to receive DTVInfo when the libc package provides it (may arrive from a separate DSO like ld-linux.so).

  • Adds a DTV fallback path in ruby_tracer.ebpf.c between the existing TLSDESC path and the ractor fallback.

@dalehamel
Copy link
Member Author

Coredump Test Plan

This PR needs a coredump test for a dynamically-linked Ruby on musl (Alpine Linux) to exercise the DTV-based ruby_current_ec lookup via dtv_read().

Test: ruby-3.4.x-dtv-loop (amd64 + arm64)

musl is the target because it uses DTV-based TLS (no TLSDESC), which is exactly the path this PR adds.

Setup (Alpine container):

docker run -it --privileged alpine:3.22 sh
apk add ruby gdb
echo 0x3f > /proc/self/coredump_filter

Verify musl uses DTPMOD64 (not TLSDESC):

readelf -r /usr/lib/libruby.so | grep DTPMOD
# Should show R_X86_64_DTPMOD64 or R_AARCH64_TLS_DTPMOD64
readelf -r /usr/lib/libruby.so | grep TLSDESC
# Should be EMPTY — musl doesn't use TLSDESC

Capture:

ruby /path/to/loop.rb &
PID=$!
sleep 2
./coredump new -pid $PID -name ruby-3.4.x-dtv-loop

What this exercises

  • VisitRelocations() with DTPMOD64 filter finds the TLS module ID offset in libruby.so
  • UpdateLibcInfo() receives DTVInfo extracted from musl's __tls_get_addr (via PR Extract DTV info from __tls_get_addr, add to LibcInfo open-telemetry/opentelemetry-ebpf-profiler#929's libc introspection)
  • dtv_read() shared eBPF helper traverses: tsd_base → DTV pointer (via DTVInfo.offset + optional indirection) → DTV[module_id * multiplier] → TLS block → ruby_current_ec
  • Expected frames should be identical in shape to existing ruby-3.4.x-loop tests

Important notes

  • Coredump filter must be 0x3f to capture anonymous pages containing TLS/DTV data
  • Both ld-musl-*.so and libruby.so must be included as modules in the coredump
  • musl DTV parameters: Multiplier=8, Indirect=1 (vs glibc Multiplier=16, Indirect=0 on x86)

Files to add

  • tools/coredump/testdata/amd64/ruby-3.4.x-dtv-loop.json
  • tools/coredump/testdata/arm64/ruby-3.4.x-dtv-loop.json

@dalehamel dalehamel force-pushed the ruby-dtv-ec-lookup branch from ca09227 to 06640ae Compare March 2, 2026 21:01
When TLSDESC relocations are unavailable (e.g. some musl setups or
dynamically allocated TLS), the Ruby execution context can still be
found by traversing the Dynamic Thread Vector (DTV).

This commit:

- Adds a shared dtv_read() helper in tsd.h (alongside existing tsd_read)
  that traverses the DTV using DTVInfo from PR open-telemetry#929's libc introspection.
  This helper is available to all interpreters, not just Ruby.

- Generalizes VisitTLSRelocations into VisitRelocations with a pluggable
  relocation type filter, enabling lookup of DTPMOD64 relocations to find
  the TLS module ID offset for libruby.so.

- Adds DTVInfo, current_ec_tls_offset, and tls_module_id fields to
  RubyProcInfo so the eBPF unwinder can use DTV traversal.

- Implements UpdateLibcInfo for Ruby to receive DTVInfo when the libc
  package provides it (may arrive from a separate DSO like ld-linux.so).

- Adds a DTV fallback path in ruby_tracer.ebpf.c between the existing
  TLSDESC path and the ractor fallback.
Rebuilt tracer.ebpf.amd64 and tracer.ebpf.arm64 with:
- dtv_read() helper in tsd.h
- DTV fallback path in ruby_tracer.ebpf.c
- New metricID_UnwindErrBadDTVRead metric
- Updated RubyProcInfo struct with DTVInfo fields
@dalehamel dalehamel force-pushed the ruby-dtv-ec-lookup branch from 06640ae to c5ef637 Compare March 3, 2026 15:10
@dalehamel dalehamel changed the base branch from libcinfo-tsd-tls-refactor to main March 3, 2026 15:10
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.

1 participant