Skip to content

python: get the Thread State from a Thread-local#1109

Merged
christos68k merged 15 commits into
open-telemetry:mainfrom
florianl:python-tls
Jan 30, 2026
Merged

python: get the Thread State from a Thread-local#1109
christos68k merged 15 commits into
open-telemetry:mainfrom
florianl:python-tls

Conversation

@florianl
Copy link
Copy Markdown
Member

While looking into #1054 I noticed that python unwinding fails starting for python 3.13 on ARM64.

Starting from Python 3.13, internals changed with python/cpython#103323.

@florianl florianl requested review from a team as code owners January 23, 2026 10:41
@florianl florianl marked this pull request as draft January 23, 2026 10:44
This change originates from python/cpython#103323.

Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
@florianl florianl marked this pull request as ready for review January 23, 2026 11:03
@florianl florianl added the bug Something isn't working label Jan 23, 2026
Comment thread interpreter/python/amd64_decode.go Outdated
Comment thread interpreter/python/amd64_decode.go Outdated
Comment on lines +67 to +69
// extractTLSOffsetFromCodeAMD64 extracts the TLS offset by analyzing x86_64 assembly code.
// It looks for MOV instructions with FS segment prefix (e.g., MOV rax, FS:[offset]).
func extractTLSOffsetFromCodeAMD64(code []byte, baseAddr uint64) (int64, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This function looks like a partial copy of

func extractTLSGOffset(f *pfelf.File) (int32, error) {

I think it would make sense to add the common portion of finding FS:xxx and resolving the xxx to asm/amd as a helper function. The other function also supports RIP relative stuff, so using that as reference would likely be better.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have added asm/amd/ExtractFSOffsetFromCode() with d08582d.

While I knew about the duplication with golabels, I wasn't sure how my approach will be received and the difference to resolving the memory reference (Pattern 3). Let me know if this deduplication works for you.

Comment thread support/ebpf/python_tracer.ebpf.c Outdated
Comment thread support/ebpf/types.h Outdated
Comment thread interpreter/python/arm64_decode.go Outdated
Comment on lines +107 to +109
// extractTLSOffsetFromCodeARM64 extracts the TLS offset by analyzing ARM64 assembly code.
// It looks for the pattern: MRS Xn, TPIDR_EL0 followed by ADD Xn, Xn, #offset or LDR [Xn, #offset].
func extractTLSOffsetFromCodeARM64(code []byte, baseAddr uint64, visited map[uint64]bool, depth int, ef *pfelf.File) (int64, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Immediately looking, this probably is generic code and could also live in asm/arm as helper?

Copy link
Copy Markdown
Member

@christos68k christos68k Jan 27, 2026

Choose a reason for hiding this comment

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

Also if we stick with a recursive function instead of iteration, we can hide it as either an inner function or a separate recursive function that's called by the wrapper (which shouldn't take a map argument). Otherwise, it's sort of ugly to leak internal implementation details (allocate and pass a map to carry state across recursive calls) to the caller.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Moved and refactored the code with 72de6e3.
Hope this works for you.

florianl and others added 9 commits January 27, 2026 10:17
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
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.

looks pretty good to me. some minor clean up comments added

Comment thread asm/arm/tls.go Outdated
Comment thread asm/amd/tls.go Outdated
Comment thread interpreter/python/amd64_decode.go Outdated
florianl and others added 3 commits January 29, 2026 15:54
Co-authored-by: Timo Teräs <timo.teras@iki.fi>
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
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. thanks!

Comment thread asm/amd/tls.go Outdated
Comment thread asm/amd/tls.go Outdated
Comment thread asm/arm/tls.go Outdated
Comment thread asm/arm/tls.go Outdated
Comment thread asm/arm/tls.go Outdated
Comment thread interpreter/python/python.go Outdated
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
@christos68k christos68k merged commit 619e4c1 into open-telemetry:main Jan 30, 2026
32 checks passed
florianl added a commit that referenced this pull request Jan 30, 2026
#1109 introduced a new asm/arm package.
Move functionality of armhelpers into this new package for consistency.

Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
@bobrik
Copy link
Copy Markdown
Contributor

bobrik commented Feb 4, 2026

I just saw this on x86_64:

Feb 04 02:43:59 748m17 ebpf-profiler[1492724]: time=2026-02-04T02:43:59.480Z level=WARN msg="Failed to extract TLS offset: could not extract TLS offset from _PyThreadState_GetCurrent: could not find FS-relative MOV instruction with valid TLS offset"

It seems related to this PR.

dalehamel added a commit to Shopify/opentelemetry-ebpf-profiler that referenced this pull request Feb 4, 2026
PR open-telemetry#1109 already handles Python 3.13+ TLS access via staticTLSOffset,
making these checks unnecessary. The eBPF code checks tls_offset directly
and uses it for Python 3.13+ without requiring TSDInfo.
dalehamel added a commit to Shopify/opentelemetry-ebpf-profiler that referenced this pull request Feb 4, 2026
When LibcInfo is collected from multiple DSOs (e.g., libc.so and ld-linux.so),
UpdateLibcInfo may be called multiple times. For Python versions that don't
have a static TLS offset (< 3.13 or when extraction fails), we need TSDInfo
to access thread state. Wait until TSDInfo is available before inserting
proc data, and prevent duplicate inserts.

This is a simplified version that checks staticTLSOffset directly rather
than version numbers, since PR open-telemetry#1109 already extracts the TLS offset for
Python 3.13+ when available.
dalehamel added a commit to Shopify/opentelemetry-ebpf-profiler that referenced this pull request Feb 20, 2026
When LibcInfo is collected from multiple DSOs (e.g., libc.so and ld-linux.so),
UpdateLibcInfo may be called multiple times. For Python versions that don't
have a static TLS offset (< 3.13 or when extraction fails), we need TSDInfo
to access thread state. Wait until TSDInfo is available before inserting
proc data, and prevent duplicate inserts.

This is a simplified version that checks staticTLSOffset directly rather
than version numbers, since PR open-telemetry#1109 already extracts the TLS offset for
Python 3.13+ when available.
dalehamel added a commit to Shopify/opentelemetry-ebpf-profiler that referenced this pull request Mar 2, 2026
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.
dalehamel added a commit to Shopify/opentelemetry-ebpf-profiler that referenced this pull request Mar 3, 2026
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.
dalehamel added a commit to Shopify/opentelemetry-ebpf-profiler that referenced this pull request Mar 3, 2026
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.
dalehamel added a commit to Shopify/opentelemetry-ebpf-profiler that referenced this pull request Mar 4, 2026
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.
dalehamel added a commit to Shopify/opentelemetry-ebpf-profiler that referenced this pull request Mar 17, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working interpreter/python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants