diff --git a/interpreter/instancestubs.go b/interpreter/instancestubs.go index 6b503280e..75af5fb35 100644 --- a/interpreter/instancestubs.go +++ b/interpreter/instancestubs.go @@ -5,11 +5,11 @@ package interpreter // import "go.opentelemetry.io/ebpf-profiler/interpreter" import ( "go.opentelemetry.io/ebpf-profiler/host" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/metrics" "go.opentelemetry.io/ebpf-profiler/process" "go.opentelemetry.io/ebpf-profiler/reporter" - "go.opentelemetry.io/ebpf-profiler/tpbase" ) // InstanceStubs provides empty implementations of Instance hooks that are @@ -22,7 +22,7 @@ func (is *InstanceStubs) SynchronizeMappings(EbpfHandler, reporter.ExecutableRep return nil } -func (is *InstanceStubs) UpdateTSDInfo(EbpfHandler, libpf.PID, tpbase.TSDInfo) error { +func (is *InstanceStubs) UpdateLibcInfo(EbpfHandler, libpf.PID, libc.LibcInfo) error { return nil } diff --git a/interpreter/multi.go b/interpreter/multi.go index 34f426758..b6372fc1e 100644 --- a/interpreter/multi.go +++ b/interpreter/multi.go @@ -8,12 +8,12 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/internal/log" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/metrics" "go.opentelemetry.io/ebpf-profiler/process" "go.opentelemetry.io/ebpf-profiler/remotememory" "go.opentelemetry.io/ebpf-profiler/reporter" - "go.opentelemetry.io/ebpf-profiler/tpbase" ) // MultiData implements the Data interface for multiple interpreters. @@ -104,11 +104,11 @@ func (m *MultiInstance) SynchronizeMappings(ebpf EbpfHandler, return errors.Join(errs...) } -// UpdateTSDInfo updates TSD info for all interpreter instances. -func (m *MultiInstance) UpdateTSDInfo(ebpf EbpfHandler, pid libpf.PID, info tpbase.TSDInfo) error { +// UpdateLibcInfo updates libc info for all interpreter instances. +func (m *MultiInstance) UpdateLibcInfo(ebpf EbpfHandler, pid libpf.PID, info libc.LibcInfo) error { var errs []error for _, instance := range m.instances { - if err := instance.UpdateTSDInfo(ebpf, pid, info); err != nil { + if err := instance.UpdateLibcInfo(ebpf, pid, info); err != nil { errs = append(errs, err) } } diff --git a/interpreter/perl/instance.go b/interpreter/perl/instance.go index 026d7dbfa..d3fda9fa0 100644 --- a/interpreter/perl/instance.go +++ b/interpreter/perl/instance.go @@ -15,6 +15,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/interpreter" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfunsafe" "go.opentelemetry.io/ebpf-profiler/metrics" @@ -22,7 +23,6 @@ import ( "go.opentelemetry.io/ebpf-profiler/remotememory" "go.opentelemetry.io/ebpf-profiler/successfailurecounter" "go.opentelemetry.io/ebpf-profiler/support" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -73,9 +73,8 @@ func hashCOPKey(k copKey) uint32 { return uint32(h ^ xxh3.HashString128(k.funcName.String()).Lo) } -func (i *perlInstance) UpdateTSDInfo(ebpf interpreter.EbpfHandler, pid libpf.PID, - tsdInfo tpbase.TSDInfo, -) error { +func (i *perlInstance) UpdateLibcInfo(ebpf interpreter.EbpfHandler, pid libpf.PID, + libcInfo libc.LibcInfo) error { d := i.d stateInTSD := uint8(0) if d.stateInTSD { @@ -88,9 +87,9 @@ func (i *perlInstance) UpdateTSDInfo(ebpf interpreter.EbpfHandler, pid libpf.PID StateInTSD: stateInTSD, TsdInfo: support.TSDInfo{ - Offset: tsdInfo.Offset, - Multiplier: tsdInfo.Multiplier, - Indirect: tsdInfo.Indirect, + Offset: libcInfo.TSDInfo.Offset, + Multiplier: libcInfo.TSDInfo.Multiplier, + Indirect: libcInfo.TSDInfo.Indirect, }, Interpreter_curcop: uint16(vms.interpreter.curcop), diff --git a/interpreter/python/python.go b/interpreter/python/python.go index a440de2c4..23ca245c1 100644 --- a/interpreter/python/python.go +++ b/interpreter/python/python.go @@ -26,6 +26,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/interpreter" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" "go.opentelemetry.io/ebpf-profiler/metrics" @@ -33,7 +34,6 @@ import ( "go.opentelemetry.io/ebpf-profiler/remotememory" "go.opentelemetry.io/ebpf-profiler/successfailurecounter" "go.opentelemetry.io/ebpf-profiler/support" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -371,9 +371,8 @@ func (p *pythonInstance) GetAndResetMetrics() ([]metrics.Metric, error) { }, nil } -func (p *pythonInstance) UpdateTSDInfo(ebpf interpreter.EbpfHandler, pid libpf.PID, - tsdInfo tpbase.TSDInfo, -) error { +func (p *pythonInstance) UpdateLibcInfo(ebpf interpreter.EbpfHandler, pid libpf.PID, + libcInfo libc.LibcInfo) error { d := p.d vm := &d.vmStructs cdata := support.PyProcInfo{ @@ -381,9 +380,9 @@ func (p *pythonInstance) UpdateTSDInfo(ebpf interpreter.EbpfHandler, pid libpf.P Version: d.version, TsdInfo: support.TSDInfo{ - Offset: tsdInfo.Offset, - Multiplier: tsdInfo.Multiplier, - Indirect: tsdInfo.Indirect, + Offset: libcInfo.TSDInfo.Offset, + Multiplier: libcInfo.TSDInfo.Multiplier, + Indirect: libcInfo.TSDInfo.Indirect, }, PyThreadState_frame: uint8(vm.PyThreadState.Frame), diff --git a/interpreter/ruby/ruby.go b/interpreter/ruby/ruby.go index 7ecc54940..840e94525 100644 --- a/interpreter/ruby/ruby.go +++ b/interpreter/ruby/ruby.go @@ -4,6 +4,7 @@ package ruby // import "go.opentelemetry.io/ebpf-profiler/interpreter/ruby" import ( + "debug/elf" "encoding/binary" "errors" "fmt" @@ -92,6 +93,12 @@ type rubyData struct { // Address to the ruby_current_ec variable in TLS, as an offset from tpbase currentEcTpBaseTlsOffset libpf.Address + // Offset of the current EC within TLS for this module + currentEcTlsOffset libpf.Address + + // TLS Module ID offset in ELF, to read mod ID after loaded and written by linker + tlsModuleIdOffset libpf.Address + // version of the currently used Ruby interpreter. // major*0x10000 + minor*0x100 + release (e.g. 3.0.1 -> 0x30001) version uint32 @@ -192,11 +199,19 @@ func (r *rubyData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libp tlsOffset = rm.Uint64(bias + r.currentEcTpBaseTlsOffset + 8) } + var modId uint64 + if r.tlsModuleIdOffset != 0 { + modId = rm.Uint64(bias + r.tlsModuleIdOffset) + log.Debugf("Read TLS module as %d", modId) + } + cdata := support.RubyProcInfo{ Version: r.version, Current_ctx_ptr: uint64(r.currentCtxPtr + bias), Current_ec_tpbase_tls_offset: tlsOffset, + Current_ec_tls_offset: uint64(r.currentEcTlsOffset), + Tls_module_id: modId, Vm_stack: r.vmStructs.execution_context_struct.vm_stack, Vm_stack_size: r.vmStructs.execution_context_struct.vm_stack_size, @@ -853,11 +868,28 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr log.Warnf("failed to locate TLS descriptor: %v", err) } + var tlsModuleIdOffset libpf.Address + if err = ef.VisitRelocations(func(r pfelf.ElfReloc, symName string) bool { + // We've already verified that the relocation is DTPMOD type + // so if we get here, we should be able to read the module ID from this offset + log.Debugf("Found module id at %x", r.Off) + tlsModuleIdOffset = libpf.Address(r.Off) + return false + }, func(rela pfelf.ElfReloc) bool { + ty := rela.Info & 0xffff + return (ef.Machine == elf.EM_AARCH64 && elf.R_AARCH64(ty) == elf.R_AARCH64_TLS_DTPMOD64) || + (ef.Machine == elf.EM_X86_64 && elf.R_X86_64(ty) == elf.R_X86_64_DTPMOD64) + }); err != nil { + log.Warnf("failed to mod offset: %v", err) + } + log.Debugf("Discovered EC tls tpbase offset %x, fallback ctx %x, interp ranges: %v", currentEcTpBaseTlsOffset, currentCtxPtr, interpRanges) rid := &rubyData{ version: version, currentEcTpBaseTlsOffset: libpf.Address(currentEcTpBaseTlsOffset), + tlsModuleIdOffset: tlsModuleIdOffset, + currentEcTlsOffset: libpf.Address(currentEcSymbolAddress), currentCtxPtr: libpf.Address(currentCtxPtr), } diff --git a/interpreter/types.go b/interpreter/types.go index a619c865b..2577fa1f0 100644 --- a/interpreter/types.go +++ b/interpreter/types.go @@ -8,13 +8,13 @@ import ( "unsafe" "go.opentelemetry.io/ebpf-profiler/host" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/lpm" "go.opentelemetry.io/ebpf-profiler/metrics" "go.opentelemetry.io/ebpf-profiler/process" "go.opentelemetry.io/ebpf-profiler/remotememory" "go.opentelemetry.io/ebpf-profiler/reporter" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -143,9 +143,9 @@ type Instance interface { SynchronizeMappings(ebpf EbpfHandler, exeReporter reporter.ExecutableReporter, pr process.Process, mappings []process.Mapping) error - // UpdateTSDInfo is called when the process C-library Thread Specific Data related + // UpdateLibcInfo is called when the process C-library related // introspection data has been updated. - UpdateTSDInfo(ebpf EbpfHandler, pid libpf.PID, info tpbase.TSDInfo) error + UpdateLibcInfo(ebpf EbpfHandler, pid libpf.PID, info libc.LibcInfo) error // Symbolize converts one ebpf frame to one or more (if inlining was expanded) libpf.Frame. // The resulting libpf.Frame values are appended to frames. diff --git a/tpbase/assembly_decode_aarch64.go b/libc/assembly_decode_aarch64.go similarity index 97% rename from tpbase/assembly_decode_aarch64.go rename to libc/assembly_decode_aarch64.go index 31de50e97..50bb706a6 100644 --- a/tpbase/assembly_decode_aarch64.go +++ b/libc/assembly_decode_aarch64.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "errors" diff --git a/tpbase/assembly_decode_test.go b/libc/assembly_decode_test.go similarity index 99% rename from tpbase/assembly_decode_test.go rename to libc/assembly_decode_test.go index 426bbada4..de62cf847 100644 --- a/tpbase/assembly_decode_test.go +++ b/libc/assembly_decode_test.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase +package libc import ( "debug/elf" diff --git a/tpbase/assembly_decode_x86.go b/libc/assembly_decode_x86.go similarity index 97% rename from tpbase/assembly_decode_x86.go rename to libc/assembly_decode_x86.go index 6742aa504..d2cca06fc 100644 --- a/tpbase/assembly_decode_x86.go +++ b/libc/assembly_decode_x86.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "bytes" diff --git a/tpbase/libc.go b/libc/libc.go similarity index 73% rename from tpbase/libc.go rename to libc/libc.go index 7e9b2a7a1..33481d9a2 100644 --- a/tpbase/libc.go +++ b/libc/libc.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "debug/elf" @@ -11,6 +11,17 @@ import ( "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" ) +type LibcInfo struct { + DTVInfo *DTVInfo + TSDInfo *TSDInfo +} + +type DTVInfo struct { + Offset int32 // Offset of DTV from FS base (or from thread pointer) + EntryWidth uint32 // Width of each DTV entry in bytes + Indirect uint8 // 0 if DTV is at FS+offset, 1 if at [FS+0]+offset +} + // TSDInfo contains information to access C-library's Thread Specific Data from eBPF type TSDInfo struct { // Offset is the pointer difference from "tpbase" pointer to the C-library @@ -84,6 +95,23 @@ func IsPotentialTSDDSO(filename string) bool { return libcRegex.MatchString(filename) } +func ExtractLibcInfo(ef *pfelf.File) (*LibcInfo, error) { + tsdinfo, err := ExtractTSDInfo(ef) + if err != nil { + return nil, err + } + + dtvinfo, err := extractDTVInfo(ef) + if err != nil { + return nil, err + } + + return &LibcInfo{ + TSDInfo: tsdinfo, + DTVInfo: dtvinfo, + }, nil +} + // ExtractTSDInfo extracts the introspection data for pthread thread specific data. func ExtractTSDInfo(ef *pfelf.File) (*TSDInfo, error) { _, code, err := ef.SymbolData("__pthread_getspecific", 2048) @@ -111,3 +139,28 @@ func ExtractTSDInfo(ef *pfelf.File) (*TSDInfo, error) { } return &info, nil } + +// extractDTVInfo extracts the introspection data for the DTV to access TLS vars +func extractDTVInfo(ef *pfelf.File) (*DTVInfo, error) { + _, code, err := ef.SymbolData("__tls_get_addr", 2048) + if err != nil { + return nil, fmt.Errorf("unable to read '__tls_get_addr': %s", err) + } + if len(code) < 8 { + return nil, fmt.Errorf("__tls_get_addr function size is %d", len(code)) + } + + var info DTVInfo + switch ef.Machine { + case elf.EM_AARCH64: + info, err = extractDTVInfoARM(code) + case elf.EM_X86_64: + info, err = extractDTVInfoX86(code) + default: + return nil, fmt.Errorf("unsupported arch %s", ef.Machine.String()) + } + if err != nil { + return nil, fmt.Errorf("failed to extract DTV data: %s", err) + } + return &info, nil +} diff --git a/tpbase/libc_aarch64.go b/libc/libc_aarch64.go similarity index 82% rename from tpbase/libc_aarch64.go rename to libc/libc_aarch64.go index 585879126..651ae85d6 100644 --- a/tpbase/libc_aarch64.go +++ b/libc/libc_aarch64.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "errors" @@ -268,3 +268,72 @@ func extractTSDInfoARM(code []byte) (TSDInfo, error) { Indirect: indirect, }, nil } + +func extractDTVInfoARM(code []byte) (DTVInfo, error) { + dtvOffset := int32(0) + entryWidth := uint32(0) + foundThreadPtr := false + foundDTVLoad := false + + // Scan entire function + for offs := 0; offs < len(code); offs += 4 { + if offs+4 > len(code) { + break + } + + inst, err := aa.Decode(code[offs:]) + if err != nil { + return DTVInfo{}, err + } + + switch inst.Op { + + case aa.MRS: + foundThreadPtr = true + + case aa.LDUR: + if len(inst.Args) >= 2 { + if m, ok := inst.Args[1].(aa.MemImmediate); ok { + imm, ok := ah.DecodeImmediate(m) + if ok { + dtvOffset = int32(imm) + foundDTVLoad = true + } + } + } + + case aa.LDR: + if len(inst.Args) >= 2 { + // Check what type of LDR this is + switch m := inst.Args[1].(type) { + case aa.MemImmediate: + if foundThreadPtr && !foundDTVLoad { + imm, ok := ah.DecodeImmediate(m) + if ok { + dtvOffset = int32(imm) + foundDTVLoad = true + } + } + + case aa.MemExtend: + if m.Amount > 0 { + entryWidth = uint32(1 << m.Amount) + } + } + } + + case aa.LSL: + if len(inst.Args) >= 3 { + if imm, ok := inst.Args[2].(aa.Imm); ok { + entryWidth = uint32(1 << imm.Imm) + } + } + } + } + + return DTVInfo{ + Offset: dtvOffset, + EntryWidth: entryWidth, + Indirect: 1, + }, nil +} diff --git a/libc/libc_test.go b/libc/libc_test.go new file mode 100644 index 000000000..fa30e71c3 --- /dev/null +++ b/libc/libc_test.go @@ -0,0 +1,555 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" + +import ( + "debug/elf" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExtractDTVOffset(t *testing.T) { + testCases := map[string]struct { + machine elf.Machine + code []byte + info DTVInfo + }{ + "glibc 2.36 / debian 12 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // mov %fs:0x8,%rdx + // mov 0x1fc48(%rip),%rax # 0x7ffff7ffe0b8 <_rtld_global+4248> + // cmp %rax,(%rdx) + // jne 0x7ffff7fde48b <__tls_get_addr+43> + // mov (%rdi),%rax + // shl $0x4,%rax + // mov (%rdx,%rax,1),%rax + // cmp $0xffffffffffffffff,%rax + // je 0x7ffff7fde48b <__tls_get_addr+43> + // add 0x8(%rdi),%rax + // ret + // push %rbp + // mov %rsp,%rbp + // and $0xfffffffffffffff0,%rsp + // call 0x7ffff7fdbd40 <__tls_get_addr_slow> + // mov %rbp,%rsp + // pop %rbp + // ret + 0x64, 0x48, 0x8b, 0x14, 0x25, 0x08, 0x00, 0x00, 0x00, + 0x48, 0x8b, 0x05, 0x48, 0xfc, 0x01, 0x00, + 0x48, 0x39, 0x02, + 0x75, 0x16, + 0x48, 0x8b, 0x07, + 0x48, 0xc1, 0xe0, 0x04, + 0x48, 0x8b, 0x04, 0x02, + 0x48, 0x83, 0xf8, 0xff, + 0x74, 0x05, + 0x48, 0x03, 0x47, 0x08, + 0xc3, + 0x55, + 0x48, 0x89, 0xe5, + 0x48, 0x83, 0xe4, 0xf0, + 0xe8, 0xa8, 0xd8, 0xff, 0xff, + 0x48, 0x89, 0xec, + 0x5d, + 0xc3, + }, + info: DTVInfo{ + Offset: 8, + EntryWidth: 16, + Indirect: 0, + }, + }, + "glibc 2.32 / Fedora 33 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // endbr64 + // mov %fs:0x8,%rdx + // mov 0x1394c(%rip),%rax # 0x7f48d90c2ff0 <_rtld_local+4080> + // cmp %rax,(%rdx) + // jne 0x7f48d90af6bf <__tls_get_addr+47> + // mov (%rdi),%rax + // shl $0x4,%rax + // mov (%rdx,%rax,1),%rax + // cmp $0xffffffffffffffff,%rax + // je 0x7f48d90af6bf <__tls_get_addr+47> + // add 0x8(%rdi),%rax + // ret + // push %rbp + // mov %rsp,%rbp + // and $0xfffffffffffffff0,%rsp + // call 0x7f48d90a9ed0 <__tls_get_addr_slow> + // mov %rbp,%rsp + // pop %rbp + // ret + 0xf3, 0x0f, 0x1e, 0xfa, + 0x64, 0x48, 0x8b, 0x14, 0x25, 0x08, 0x00, 0x00, 0x00, + 0x48, 0x8b, 0x05, 0x4c, 0x39, 0x01, 0x00, + 0x48, 0x39, 0x02, + 0x75, 0x16, + 0x48, 0x8b, 0x07, + 0x48, 0xc1, 0xe0, 0x04, + 0x48, 0x8b, 0x04, 0x02, + 0x48, 0x83, 0xf8, 0xff, + 0x74, 0x05, + 0x48, 0x03, 0x47, 0x08, + 0xc3, + 0x55, + 0x48, 0x89, 0xe5, + 0x48, 0x83, 0xe4, 0xf0, + 0xe8, 0x04, 0xa8, 0xff, 0xff, + 0x48, 0x89, 0xec, + 0x5d, + 0xc3, + }, + info: DTVInfo{ + Offset: 8, + EntryWidth: 16, + Indirect: 0, + }, + }, + "musl 1.2.5 / alpine 3.22.2 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // mov %fs:0x0,%rax + // mov (%rdi),%rcx + // mov 0x8(%rax),%rdx + // mov 0x8(%rdi),%rax + // add (%rdx,%rcx,8),%rax + // ret + 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x8b, 0x0f, + 0x48, 0x8b, 0x50, 0x08, + 0x48, 0x8b, 0x47, 0x08, + 0x48, 0x03, 0x04, 0xca, + 0xc3, + }, + info: DTVInfo{ + Offset: 8, + EntryWidth: 8, + Indirect: 1, + }, + }, + "musl 1.1.5 / alpine 3.1 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // mov %fs:0x0,%rax + // mov 0x8(%rax),%rax + // mov (%rdi),%rdx + // cmp %rdx,(%rax) + // jae 0x7f824da49ef3 <__tls_get_addr+26> + // jmpq 0x7f824da191d5 + // mov (%rax,%rdx,8),%rax + // add 0x8(%rdi),%rax + // retq + 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x8b, 0x40, 0x08, + 0x48, 0x8b, 0x17, + 0x48, 0x39, 0x10, + 0x73, 0x05, + 0xe9, 0xe2, 0xf2, 0xfc, 0xff, + 0x48, 0x8b, 0x04, 0xd0, + 0x48, 0x03, 0x47, 0x08, + 0xc3, + }, + info: DTVInfo{ + Offset: 8, + EntryWidth: 8, + Indirect: 1, + }, + }, + "glibc 2.39 / ubuntu 24.04 / aarch64": { + machine: elf.EM_AARCH64, + code: []byte{ + 0x41, 0xd0, 0x3b, 0xd5, // mrs x1, tpidr_el0 + 0xfd, 0x7b, 0xbf, 0xa9, // stp x29, x30, [sp, #-16]! + 0x83, 0x01, 0x00, 0xb0, // adrp x3, 0xfffff7fff000 <_rtld_global+4096> + 0xfd, 0x03, 0x00, 0x91, // mov x29, sp + 0x63, 0x00, 0x05, 0x91, // add x3, x3, #0x140 + 0x21, 0x00, 0x40, 0xf9, // ldr x1, [x1] + 0x64, 0x00, 0x40, 0xf9, // ldr x4, [x3] + 0x25, 0x00, 0x40, 0xf9, // ldr x5, [x1] + 0xbf, 0x00, 0x04, 0xeb, // cmp x5, x4 + 0x41, 0x01, 0x00, 0x54, // b.ne 0xfffff7fce2bc <__GI___tls_get_addr+76> // b.any + 0x03, 0x00, 0x40, 0xf9, // ldr x3, [x0] + 0x63, 0xec, 0x7c, 0xd3, // lsl x3, x3, #4 + 0x23, 0x68, 0x63, 0xf8, // ldr x3, [x1, x3] + 0x7f, 0x04, 0x00, 0xb1, // cmn x3, #0x1 + 0x00, 0x01, 0x00, 0x54, // b.eq 0xfffff7fce2c8 <__GI___tls_get_addr+88> // b.none + 0x00, 0x04, 0x40, 0xf9, // ldr x0, [x0, #8] + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0x60, 0x00, 0x00, 0x8b, // add x0, x3, x0 + 0xc0, 0x03, 0x5f, 0xd6, // ret + 0x61, 0xfc, 0xdf, 0xc8, // ldar x1, [x3] + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0xd3, 0xff, 0xff, 0x17, // b 0xfffff7fce210 + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0x02, 0x00, 0x80, 0xd2, // mov x2, #0x0 // #0 + 0xa9, 0xfc, 0xff, 0x17, // b 0xfffff7fcd574 + + }, + info: DTVInfo{ + Offset: 0, + EntryWidth: 16, + Indirect: 1, + }, + }, + "glibc / Fedora 39 / aarch64": { + machine: elf.EM_AARCH64, + code: []byte{ + 0x5f, 0x24, 0x03, 0xd5, // bti c + 0x41, 0xd0, 0x3b, 0xd5, // mrs x1, tpidr_el0 + 0x3f, 0x23, 0x03, 0xd5, // paciasp + 0xfd, 0x7b, 0xbf, 0xa9, // stp x29, x30, [sp, #-16]! + 0x83, 0x01, 0x00, 0xb0, // adrp x3, 0xeebc52855000 <_rtld_local+4096> + 0x63, 0x40, 0x05, 0x91, // add x3, x3, #0x150 + 0xfd, 0x03, 0x00, 0x91, // mov x29, sp + 0x21, 0x00, 0x40, 0xf9, // ldr x1, [x1] + 0x63, 0x00, 0x40, 0xf9, // ldr x3, [x3] + 0x24, 0x00, 0x40, 0xf9, // ldr x4, [x1] + 0x9f, 0x00, 0x03, 0xeb, // cmp x4, x3 + 0x61, 0x01, 0x00, 0x54, // b.ne 0xeebc52824278 <__GI___tls_get_addr+88> // b.any + 0x03, 0x00, 0x40, 0xf9, // ldr x3, [x0] + 0x63, 0xec, 0x7c, 0xd3, // lsl x3, x3, #4 + 0x23, 0x68, 0x63, 0xf8, // ldr x3, [x1, x3] + 0x7f, 0x04, 0x00, 0xb1, // cmn x3, #0x1 + 0x20, 0x01, 0x00, 0x54, // b.eq 0xeebc52824284 <__GI___tls_get_addr+100> // b.none + 0x00, 0x04, 0x40, 0xf9, // ldr x0, [x0, #8] + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0xbf, 0x23, 0x03, 0xd5, // autiasp + 0x60, 0x00, 0x00, 0x8b, // add x0, x3, x0 + 0xc0, 0x03, 0x5f, 0xd6, // ret + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0xbf, 0x23, 0x03, 0xd5, // autiasp + 0xcc, 0xff, 0xff, 0x17, // b 0xeebc528241b0 + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0xbf, 0x23, 0x03, 0xd5, // autiasp + 0x02, 0x00, 0x80, 0xd2, // mov x2, #0x0 // #0 + 0x70, 0xfc, 0xff, 0x17, // b 0xeebc52823450 + }, + info: DTVInfo{ + Offset: 0, + EntryWidth: 16, + Indirect: 1, + }, + }, + "glibc / Fedora 33 / aarch64": { + machine: elf.EM_AARCH64, + code: []byte{ + 0x5f, 0x24, 0x03, 0xd5, //bti c + 0x41, 0xd0, 0x3b, 0xd5, //mrs x1, tpidr_el0 + 0x62, 0x01, 0x00, 0xf0, //adrp x2, 0xf58e5fcc1000 <_rtld_local+4072> + 0x43, 0x60, 0x40, 0xf9, //ldr x3, [x2, #192] + 0x21, 0x00, 0x40, 0xf9, //ldr x1, [x1] + 0x24, 0x00, 0x40, 0xf9, //ldr x4, [x1] + 0x9f, 0x00, 0x03, 0xeb, //cmp x4, x3 + 0x21, 0x01, 0x00, 0x54, //b.ne 0xf58e5fc921e0 <__GI___tls_get_addr+64> // b.any + 0x03, 0x00, 0x40, 0xf9, //ldr x3, [x0] + 0x63, 0xec, 0x7c, 0xd3, //lsl x3, x3, #4 + 0x23, 0x68, 0x63, 0xf8, //ldr x3, [x1, x3] + 0x7f, 0x04, 0x00, 0xb1, //cmn x3, #0x1 + 0xa0, 0x00, 0x00, 0x54, //b.eq 0xf58e5fc921e4 <__GI___tls_get_addr+68> // b.none + 0x00, 0x04, 0x40, 0xf9, //ldr x0, [x0, #8] + 0x60, 0x00, 0x00, 0x8b, //add x0, x3, x0 + 0xc0, 0x03, 0x5f, 0xd6, //ret + 0xd4, 0xff, 0xff, 0x17, //b 0xf58e5fc92130 + 0x02, 0x00, 0x80, 0xd2, //mov x2, #0x0 // #0 + 0x7e, 0xfc, 0xff, 0x17, //b 0xf58e5fc913e0 + }, + info: DTVInfo{ + Offset: 192, + EntryWidth: 16, + Indirect: 1, + }, + }, + + "musl 1.2.5 / alpine 3.22 / aarch64": { // same as alpine 3.13, oldest on dockerhub + machine: elf.EM_AARCH64, + code: []byte{ + 0x02, 0x00, 0x40, 0xa9, // ldp x2, x0, [x0] + 0x41, 0xd0, 0x3b, 0xd5, // mrs x1, tpidr_el0 + 0x21, 0x80, 0x5f, 0xf8, // ldur x1, [x1, #-8] + 0x21, 0x78, 0x62, 0xf8, // ldr x1, [x1, x2, lsl #3] + 0x20, 0x00, 0x00, 0x8b, // add x0, x1, x0 + 0xc0, 0x03, 0x5f, 0xd6, // ret + }, + info: DTVInfo{ + Offset: -8, + EntryWidth: 8, + Indirect: 1, + }, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + var info DTVInfo + var err error + switch test.machine { + case elf.EM_X86_64: + info, err = extractDTVInfoX86(test.code) + case elf.EM_AARCH64: + info, err = extractDTVInfoARM(test.code) + } + if assert.NoError(t, err) { + assert.Equal(t, test.info, info) + } + }) + } +} + +func TestExtractTSDInfo(t *testing.T) { + testCases := map[string]struct { + machine elf.Machine + code []byte + info TSDInfo + }{ + "musl 1.2.3 / Alpine 3.16 / arm64": { + machine: elf.EM_AARCH64, + code: []byte{ + 0x41, 0xd0, 0x3b, 0xd5, // mrs x1, tpidr_el0 + 0x21, 0x80, 0x5a, 0xf8, // ldur x1, [x1, #-88] + 0x20, 0x58, 0x60, 0xf8, // ldr x0, [x1, w0, uxtw #3] + 0xc0, 0x03, 0x5f, 0xd6, // ret + }, + info: TSDInfo{ + Offset: -88, + Multiplier: 8, + Indirect: 1, + }, + }, + "glibc 2.35 / Fedora 36 / arm64": { + machine: elf.EM_AARCH64, + code: []byte{ + 0x5f, 0x24, 0x03, 0xd5, // bti c + 0xe1, 0x03, 0x00, 0x2a, // mov w1, w0 + 0x1f, 0x7c, 0x00, 0x71, // cmp w0, #0x1f + 0x48, 0x02, 0x00, 0x54, // b.hi 85bb4 <__pthread_getspecific+0x54> + 0x20, 0x7c, 0x7c, 0xd3, // ubfiz x0, x1, #4, #32 + 0x42, 0xd0, 0x3b, 0xd5, // mrs x2, tpidr_el0 + 0x00, 0xc0, 0x1a, 0xd1, // sub x0, x0, #0x6b0 + 0x42, 0x00, 0x00, 0x8b, // add x2, x2, x0 + 0x40, 0x04, 0x40, 0xf9, // ldr x0, [x2, #8] + 0x40, 0x01, 0x00, 0xb4, // cbz x0, 85bac <__pthread_getspecific+0x4c> + 0x21, 0x7c, 0x7c, 0xd3, // ubfiz x1, x1, #4, #32 + 0xe3, 0x08, 0x00, 0xd0, // adrp x3, 1a3000 + 0x63, 0x40, 0x0a, 0x91, // add x3, x3, #0x290 + 0x44, 0x00, 0x40, 0xf9, // ldr x4, [x2] + 0x61, 0x68, 0x61, 0xf8, // ldr x1, [x3, x1] + 0x3f, 0x00, 0x04, 0xeb, // cmp x1, x4 + 0x41, 0x00, 0x00, 0x54, // b.ne 85ba8 <__pthread_getspecific+0x48> + 0xc0, 0x03, 0x5f, 0xd6, // ret + // code skipped handling keys >0x1f + }, + info: TSDInfo{ + Offset: -0x6b0 + 8, + Multiplier: 0x10, + }, + }, + "glibc 2.33 / Fedora 34 / arm64": { + machine: elf.EM_AARCH64, + code: []byte{ + 0x5f, 0x24, 0x03, 0xd5, // bti c + 0xe1, 0x03, 0x00, 0x2a, // mov w1, w0 + 0x1f, 0x7c, 0x00, 0x71, // cmp w0, #0x1f + 0x48, 0x02, 0x00, 0x54, // b.hi fb94 <__pthread_getspecific+0x54> // b.pmore + 0x40, 0xd0, 0x3b, 0xd5, // mrs x0, tpidr_el0 + 0x22, 0x44, 0x00, 0x11, // add w2, w1, #0x11 + 0x00, 0x40, 0x1e, 0xd1, // sub x0, x0, #0x790 + 0x02, 0x10, 0x02, 0x8b, // add x2, x0, x2, lsl #4 + 0x40, 0x04, 0x40, 0xf9, // ldr x0, [x2, #8] + 0x00, 0x01, 0x00, 0xb4, // cbz x0, fb84 <__pthread_getspecific+0x44> + 0x21, 0x7c, 0x7c, 0xd3, // ubfiz x1, x1, #4, #32 + 0x03, 0x01, 0x00, 0xb0, // adrp x3, 30000 <__nptl_nthreads> + 0x63, 0x80, 0x01, 0x91, // add x3, x3, #0x60 + 0x44, 0x00, 0x40, 0xf9, // ldr x4, [x2] + 0x61, 0x68, 0x61, 0xf8, // ldr x1, [x3, x1] + 0x3f, 0x00, 0x04, 0xeb, // cmp x1, x4 + 0x41, 0x00, 0x00, 0x54, // b.ne fb88 <__pthread_getspecific+0x48> // b.any + 0xc0, 0x03, 0x5f, 0xd6, // ret + // code skipped handling keys >0x1f + }, + info: TSDInfo{ + Offset: -0x790 + (0x11 << 4) + 8, + Multiplier: 0x10, + }, + }, + "musl 1.2.3 / Alpine 3.16 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // mov %fs:0x0,%rax + // mov 0x80(%rax),%rax + // mov %edi,%edi + // mov (%rax,%rdi,8),%rax + // ret + 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x48, 0x8b, 0x80, 0x80, 0x00, 0x00, 0x00, + 0x89, 0xff, 0x48, 0x8b, 0x04, 0xf8, 0xc3, + }, + info: TSDInfo{ + Offset: 0x80, + Multiplier: 0x8, + Indirect: 1, + }, + }, + "musl 1.1.24 / Alpine 3.12 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // mov %fs:0x0,%rax + // mov 0x88(%rax),%rax + // mov %edi,%edi + // mov (%rax,%rdi,8),%rax + // ret + 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x48, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, + 0x89, 0xff, 0x48, 0x8b, 0x04, 0xf8, 0xc3, + }, + info: TSDInfo{ + Offset: 0x88, + Multiplier: 0x8, + Indirect: 1, + }, + }, + "glibc 2.32 / Fedora 33 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // endbr64 + // cmp $0x1f,%edi + // ja 10bf0 <__pthread_getspecific+0x40> + // lea 0x31(%rdi),%eax + // shl $0x4,%rax # <- 0x31<<4 = 0x310, <<4 = *0x10 + // mov %fs:0x10,%rdx + // add %rdx,%rax + // mov 0x8(%rax),%r8 # <- +8 + // test %r8,%r8 + // je 10beb <__pthread_getspecific+0x3b> + // mov %edi,%edi + // lea 0xc4c2(%rip),%rdx # 1d0a0 <__GI___pthread_keys> + // mov (%rax),%rsi + // shl $0x4,%rdi + // cmp %rsi,(%rdx,%rdi,1) + // jne 10c20 <__pthread_getspecific+0x70> + // mov %r8,%rax + // retq + // code skipped for handling keys >0x1f + 0xf3, 0x0f, 0x1e, 0xfa, 0x83, 0xff, 0x1f, 0x77, + 0x37, 0x8d, 0x47, 0x31, 0x48, 0xc1, 0xe0, 0x04, + 0x64, 0x48, 0x8b, 0x14, 0x25, 0x10, 0x00, 0x00, + 0x00, 0x48, 0x01, 0xd0, 0x4c, 0x8b, 0x40, 0x08, + 0x4d, 0x85, 0xc0, 0x74, 0x16, 0x89, 0xff, 0x48, + 0x8d, 0x15, 0xc2, 0xc4, 0x00, 0x00, 0x48, 0x8b, + 0x30, 0x48, 0xc1, 0xe7, 0x04, 0x48, 0x39, 0x34, + 0x3a, 0x75, 0x35, 0x4c, 0x89, 0xc0, 0xc3, + }, + info: TSDInfo{ + Offset: 0x310 + 8, + Multiplier: 0x10, + }, + }, + "glibc 2.35 / Fedora 36 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // endbr64 + // cmp $0x1f,%edi + // ja 92a40 <__pthread_getspecific@GLIBC_2.2.5+0x40> + // mov %edi,%eax + // add $0x31,%rax + // shl $0x4,%rax + // add %fs:0x10,%rax + // mov 0x8(%rax),%rdx + // test %rdx,%rdx + // je 92a78 <__pthread_getspecific@GLIBC_2.2.5+0x78> + // mov %edi,%edi + // lea 0x167b92(%rip),%rcx + // mov (%rax),%rsi + // shl $0x4,%rdi + // cmp %rsi,(%rcx,%rdi,1) + // jne 92a70 <__pthread_getspecific@GLIBC_2.2.5+0x70> + // mov %rdx,%rax + // ret + // code skipped for handling keys >0x1f + 0xf3, 0x0f, 0x1e, 0xfa, 0x83, 0xff, 0x1f, 0x77, + 0x37, 0x89, 0xf8, 0x48, 0x83, 0xc0, 0x31, 0x48, + 0xc1, 0xe0, 0x04, 0x64, 0x48, 0x03, 0x04, 0x25, + 0x10, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x50, 0x08, + 0x48, 0x85, 0xd2, 0x74, 0x53, 0x89, 0xff, 0x48, + 0x8d, 0x0d, 0x92, 0x7b, 0x16, 0x00, 0x48, 0x8b, + 0x30, 0x48, 0xc1, 0xe7, 0x04, 0x48, 0x39, 0x34, + 0x39, 0x75, 0x35, 0x48, 0x89, 0xd0, 0xc3, + }, + info: TSDInfo{ + Offset: 0x310 + 8, + Multiplier: 0x10, + }, + }, + "glibc 2.38 / Fedora 39 / arm64": { + machine: elf.EM_AARCH64, + code: []byte{ + 0x3f, 0x23, 0x03, 0xd5, // paciasp + 0xfd, 0x7b, 0xbf, 0xa9, // stp x29, x30, [sp, #-16]! + 0xe1, 0x03, 0x00, 0x2a, // mov w1, w0 + 0xfd, 0x03, 0x00, 0x91, // mov x29, sp + 0x1f, 0x7c, 0x00, 0x71, // cmp w0, #0x1f + // b.hi 91d98 <__pthread_getspecific@GLIBC_2.17+0x58> // b.pmore + 0x28, 0x02, 0x00, 0x54, + // mov x0, #0xfffffffffffff9d0 // #-1584 + 0xe0, 0xc5, 0x80, 0x92, + 0x42, 0xd0, 0x3b, 0xd5, // mrs x2, tpidr_el0 + 0x00, 0x50, 0x21, 0x8b, // add x0, x0, w1, uxtw #4 + 0x42, 0x00, 0x00, 0x8b, // add x2, x2, x0 + 0x40, 0x04, 0x40, 0xf9, // ldr x0, [x2, #8] + // cbz x0, 91dcc <__pthread_getspecific@GLIBC_2.17+0x8c> + 0x00, 0x03, 0x00, 0xb4, + 0x21, 0x7c, 0x7c, 0xd3, // ubfiz x1, x1, #4, #32 + 0x83, 0x09, 0x00, 0xb0, // adrp x3, 1c2000 + 0x63, 0x40, 0x17, 0x91, // add x3, x3, #0x5d0 + 0x44, 0x00, 0x40, 0xf9, // ldr x4, [x2] + 0x61, 0x68, 0x61, 0xf8, // ldr x1, [x3, x1] + 0x3f, 0x00, 0x04, 0xeb, // cmp x1, x4 + // b.ne 91dc8 <__pthread_getspecific@GLIBC_2.17+0x88> // b.any + 0x01, 0x02, 0x00, 0x54, + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0xbf, 0x23, 0x03, 0xd5, // autiasp + 0xc0, 0x03, 0x5f, 0xd6, // ret + // code skipped handling keys >0x1f + }, + info: TSDInfo{ + Offset: -1584 + 8, + Multiplier: 16, + }, + }, + "booking coredump glibc": { + machine: elf.EM_X86_64, + code: []byte{ + 0x83, 0xff, 0x1f, 0x77, 0x49, 0x89, 0xf8, 0x48, 0x83, 0xc0, 0x30, 0x48, + 0xc1, 0xe0, 0x04, 0x64, 0x48, 0x8b, 0x14, 0x25, 0x10, 0x00, 0x00, 0x00, + 0x48, 0x8d, 0x54, 0x02, 0x10, 0x48, 0x8b, 0x42, 0x08, 0x48, 0x85, 0xc0, + 0x74, 0x1a, 0x89, 0xff, 0x48, 0x8d, 0x0d, 0x61, 0xaa, 0x20, 0x00, 0x48, + 0xc1, 0xe7, 0x04, 0x48, 0x8b, 0x34, 0x39, 0x48, 0x39, 0x32, 0x75, 0x07, + 0xf3, 0xc3, + }, + info: TSDInfo{ + Offset: 0x310 + 8, + Multiplier: 0x10, + }, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + var info TSDInfo + var err error + switch test.machine { + case elf.EM_X86_64: + info, err = extractTSDInfoX86(test.code) + case elf.EM_AARCH64: + info, err = extractTSDInfoARM(test.code) + } + if assert.NoError(t, err) { + assert.Equal(t, test.info, info, "Wrong TSD info extraction") + } + }) + } +} diff --git a/libc/libc_x86.go b/libc/libc_x86.go new file mode 100644 index 000000000..c95e1472d --- /dev/null +++ b/libc/libc_x86.go @@ -0,0 +1,230 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" + +import ( + "errors" + + "go.opentelemetry.io/ebpf-profiler/asm/amd" + e "go.opentelemetry.io/ebpf-profiler/asm/expression" + "golang.org/x/arch/x86/x86asm" +) + +// extractDTVInfoX86 analyzes __tls_get_addr to find the DTV offset from FS base +func extractDTVInfoX86(code []byte) (DTVInfo, error) { + it := amd.NewInterpreterWithCode(code) + + // __tls_get_addr takes a tls_index struct in RDI + tls_index := it.Regs.Get(amd.RDI) + module_id := e.Mem8(tls_index) + tls_offset := e.Mem8(e.Add(tls_index, e.Imm(8))) + + // Execute until RET + _, err := it.LoopWithBreak(func(op x86asm.Inst) bool { + return op.Op == x86asm.RET + }) + if err != nil { + return DTVInfo{}, err + } + + result := it.Regs.Get(amd.RAX) + + // Capture variables + var ( + dtvOffset = e.NewImmediateCapture("dtvOffset") + entryWidth = e.NewImmediateCapture("entryWidth") + ) + + // Pattern 1: glibc - Direct DTV access + expected := e.Add( + e.Mem8( + e.Add( + e.MemWithSegment8(x86asm.FS, dtvOffset), + e.Multiply(module_id, entryWidth), + ), + ), + tls_offset, + ) + + if result.Match(expected) { + return DTVInfo{ + Offset: int32(dtvOffset.CapturedValue()), + EntryWidth: uint32(entryWidth.CapturedValue()), + Indirect: 0, + }, nil + } + + // Pattern 2: musl - The thread pointer itself might be represented differently + // Since FS:0 is the thread pointer, and DTV is at offset from it + thread_ptr := e.MemWithSegment8(x86asm.FS, e.Imm(0)) + dtv_ptr := e.Mem8(e.Add(thread_ptr, dtvOffset)) + + expected = e.Add( + tls_offset, + e.Mem8( + e.Add( + dtv_ptr, + e.Multiply(module_id, entryWidth), + ), + ), + ) + + if result.Match(expected) { + return DTVInfo{ + Offset: int32(dtvOffset.CapturedValue()), + EntryWidth: uint32(entryWidth.CapturedValue()), + Indirect: 1, + }, nil + } + + // Pattern 3: Reverse addition order + expected = e.Add( + e.Mem8( + e.Add( + dtv_ptr, + e.Multiply(module_id, entryWidth), + ), + ), + tls_offset, + ) + + if result.Match(expected) { + return DTVInfo{ + Offset: int32(dtvOffset.CapturedValue()), + EntryWidth: uint32(entryWidth.CapturedValue()), + Indirect: 1, + }, nil + } + + // Pattern 4: Maybe the scale is encoded in the memory operand differently + // Try without explicit multiply + expected = e.Add( + tls_offset, + e.Mem8( + e.Add( + e.Mem8(e.Add(thread_ptr, dtvOffset)), + e.Multiply(e.ZeroExtend32(module_id), entryWidth), + ), + ), + ) + + if result.Match(expected) { + return DTVInfo{ + Offset: int32(dtvOffset.CapturedValue()), + EntryWidth: uint32(entryWidth.CapturedValue()), + Indirect: 1, + }, nil + } + + // Pattern 5: Try hardcoded offset 8 for musl + expected = e.Add( + tls_offset, + e.Mem8( + e.Add( + e.Mem8(e.Add(thread_ptr, e.Imm(8))), + e.Multiply(module_id, entryWidth), + ), + ), + ) + + if result.Match(expected) { + return DTVInfo{ + Offset: 8, + EntryWidth: uint32(entryWidth.CapturedValue()), + Indirect: 1, + }, nil + } + + // Pattern 6: Try with scale 8 hardcoded + expected = e.Add( + tls_offset, + e.Mem8( + e.Add( + e.Mem8(e.Add(thread_ptr, dtvOffset)), + e.Multiply(module_id, e.Imm(8)), + ), + ), + ) + + if result.Match(expected) { + return DTVInfo{ + Offset: int32(dtvOffset.CapturedValue()), + EntryWidth: 8, + Indirect: 1, + }, nil + } + + return DTVInfo{}, errors.New("could not extract DTV info: no matching pattern found") +} + +func extractTSDInfoX86(code []byte) (TSDInfo, error) { + it := amd.NewInterpreterWithCode(code) + key := it.Regs.Get(amd.RDI) + _, err := it.LoopWithBreak(func(op x86asm.Inst) bool { + return op.Op == x86asm.RET + }) + if err != nil { + return TSDInfo{}, err + } + res := it.Regs.Get(amd.RAX) + var ( + multiplier = e.NewImmediateCapture("multiplier") + multiplier2 = e.NewImmediateCapture("multiplier2") + offset = e.NewImmediateCapture("offset") + ) + + expected := e.Mem8( + e.Add( + e.Mem8( + e.Add( + e.MemWithSegment8(x86asm.FS, e.Imm(0)), + offset, + ), + ), + e.Multiply( + e.ZeroExtend32(key), + multiplier), + ), + ) + if res.Match(expected) { + return TSDInfo{ + Offset: int16(offset.CapturedValue()), + Multiplier: uint8(multiplier.CapturedValue()), + Indirect: 1, + }, nil + } + expected = e.Mem8( + e.Add( + e.MemWithSegment8(x86asm.FS, e.Imm(0x10)), + e.Multiply(e.ZeroExtend32(key), multiplier), + offset, + ), + ) + if res.Match(expected) { + return TSDInfo{ + Offset: int16(offset.CapturedValue()), + Multiplier: uint8(multiplier.CapturedValue()), + Indirect: 0, + }, nil + } + expected = e.Mem8( + e.Add( + e.MemWithSegment8(x86asm.FS, e.Imm(0x10)), + e.Multiply( + e.ZeroExtend32(e.Add(key, multiplier2)), + multiplier, + ), + offset, + ), + ) + if res.Match(expected) { + return TSDInfo{ + Offset: int16(multiplier.CapturedValue()*multiplier2.CapturedValue() + + offset.CapturedValue()), + Multiplier: uint8(multiplier.CapturedValue()), + Indirect: 0, + }, nil + } + return TSDInfo{}, errors.New("could not extract tsdInfo amd") +} diff --git a/tpbase/tpbase.go b/libc/tpbase.go similarity index 93% rename from tpbase/tpbase.go rename to libc/tpbase.go index 11aa1c035..6baf23abe 100644 --- a/tpbase/tpbase.go +++ b/libc/tpbase.go @@ -7,7 +7,7 @@ // relative to the 'struct task_struct'. This is needed to support Thread Local // Storage access in eBPF. -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "fmt" diff --git a/libpf/pfelf/file.go b/libpf/pfelf/file.go index 1753947da..c72f30b9a 100644 --- a/libpf/pfelf/file.go +++ b/libpf/pfelf/file.go @@ -639,6 +639,17 @@ type ElfReloc *elf.Rela64 // for the TLS symbol, as well as a best-effort string for the symbol's name // it continues until the visitor returns false func (f *File) VisitTLSRelocations(visitor func(ElfReloc, string) bool) error { + checkFunc := func(rela ElfReloc) bool { + ty := rela.Info & 0xffff + return (f.Machine == elf.EM_AARCH64 && elf.R_AARCH64(ty) == elf.R_AARCH64_TLSDESC) || + (f.Machine == elf.EM_X86_64 && elf.R_X86_64(ty) == elf.R_X86_64_TLSDESC) + } + return f.VisitRelocations(visitor, checkFunc) +} + +// VisitRelocations visits all Relocations and provides the relocation to +// a visitor function, as well as checks its type using a chuck function +func (f *File) VisitRelocations(visitor func(ElfReloc, string) bool, checkFunc func(ElfReloc) bool) error { var err error if err = f.LoadSections(); err != nil { return err @@ -648,7 +659,7 @@ func (f *File) VisitTLSRelocations(visitor func(ElfReloc, string) bool) error { section := &f.Sections[i] // NOTE: SHT_REL is not relevant for the archs that we care about if section.Type == elf.SHT_RELA { - cont, err := f.visitTLSDescriptorsForSection(visitor, section) + cont, err := f.visitRelocationsForSection(visitor, checkFunc, section) if err != nil { return err } @@ -661,9 +672,9 @@ func (f *File) VisitTLSRelocations(visitor func(ElfReloc, string) bool) error { return nil } -func (f *File) visitTLSDescriptorsForSection(visitor func(ElfReloc, string) bool, - relaSection *Section, -) (bool, error) { +func (f *File) visitRelocationsForSection(visitor func(ElfReloc, string) bool, + checkRelocation func(ElfReloc) bool, + relaSection *Section) (bool, error) { if relaSection.Link > uint32(len(f.Sections)) { return false, errors.New("rela section link is out-of-bounds") } @@ -704,9 +715,7 @@ func (f *File) visitTLSDescriptorsForSection(visitor func(ElfReloc, string) bool for i := 0; i < len(relaData); i += relaSz { rela := (*elf.Rela64)(unsafe.Pointer(&relaData[i])) - ty := rela.Info & 0xffff - if !(f.Machine == elf.EM_AARCH64 && elf.R_AARCH64(ty) == elf.R_AARCH64_TLSDESC) && - !(f.Machine == elf.EM_X86_64 && elf.R_X86_64(ty) == elf.R_X86_64_TLSDESC) { + if !checkRelocation(rela) { continue } diff --git a/processmanager/execinfomanager/manager.go b/processmanager/execinfomanager/manager.go index e3952c238..39d45bcd9 100644 --- a/processmanager/execinfomanager/manager.go +++ b/processmanager/execinfomanager/manager.go @@ -32,7 +32,7 @@ import ( sdtypes "go.opentelemetry.io/ebpf-profiler/nativeunwind/stackdeltatypes" pmebpf "go.opentelemetry.io/ebpf-profiler/processmanager/ebpfapi" "go.opentelemetry.io/ebpf-profiler/support" - "go.opentelemetry.io/ebpf-profiler/tpbase" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/tracer/types" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -61,8 +61,8 @@ type ExecutableInfo struct { // instance belongs to was previously identified as an interpreter. Otherwise, // this field is nil. Data interpreter.Data - // TSDInfo stores TSD information if the executable is libc, otherwise nil. - TSDInfo *tpbase.TSDInfo + // LibcInfo stores libc information if the executable is libc, otherwise nil. + LibcInfo *libc.LibcInfo } // ExecutableInfoManager manages all per-executable (FileID) information that we require to @@ -164,7 +164,7 @@ func (mgr *ExecutableInfoManager) AddOrIncRef(fileID host.FileID, } var ( intervalData sdtypes.IntervalData - tsdInfo *tpbase.TSDInfo + libcInfo *libc.LibcInfo ref mapRef gaps []util.Range err error @@ -198,9 +198,9 @@ func (mgr *ExecutableInfoManager) AddOrIncRef(fileID host.FileID, } // Also gather TSD info if applicable. - if tpbase.IsPotentialTSDDSO(elfRef.FileName()) { + if libc.IsPotentialTSDDSO(elfRef.FileName()) { if ef, errx := elfRef.GetELF(); errx == nil { - tsdInfo, _ = tpbase.ExtractTSDInfo(ef) + libcInfo, _ = libc.ExtractLibcInfo(ef) } } @@ -227,7 +227,7 @@ func (mgr *ExecutableInfoManager) AddOrIncRef(fileID host.FileID, info = &entry{ ExecutableInfo: ExecutableInfo{ Data: state.detectAndLoadInterpData(loaderInfo), - TSDInfo: tsdInfo, + LibcInfo: libcInfo, }, mapRef: ref, rc: 1, diff --git a/processmanager/processinfo.go b/processmanager/processinfo.go index 836c4c9f5..016e2d5a3 100644 --- a/processmanager/processinfo.go +++ b/processmanager/processinfo.go @@ -24,6 +24,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/interpreter" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" "go.opentelemetry.io/ebpf-profiler/lpm" @@ -31,7 +32,6 @@ import ( eim "go.opentelemetry.io/ebpf-profiler/processmanager/execinfomanager" "go.opentelemetry.io/ebpf-profiler/reporter" "go.opentelemetry.io/ebpf-profiler/times" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -69,38 +69,38 @@ func isPIDLive(pid libpf.PID) (bool, error) { return true, err } -// assignTSDInfo updates the TSDInfo for the Interpreters on given PID. +// assignLibcInfo updates the LibcInfo for the Interpreters on given PID. // Caller must hold pm.mu write lock. -func (pm *ProcessManager) assignTSDInfo(pid libpf.PID, tsdInfo *tpbase.TSDInfo) { - if tsdInfo == nil { +func (pm *ProcessManager) assignLibcInfo(pid libpf.PID, libcInfo *libc.LibcInfo) { + if libcInfo == nil { return } info, ok := pm.pidToProcessInfo[pid] if !ok { - // This is guaranteed not to happen since assignTSDInfo is always called after + // This is guaranteed not to happen since assignLibcInfo is always called after // pm.updatePidInformation - but to avoid a possible panic we just return here. return - } else if info.tsdInfo != nil { + } else if info.libcInfo != nil { return } - info.tsdInfo = tsdInfo + info.libcInfo = libcInfo // Update the tsdInfo to interpreters that are already attached for _, instance := range pm.interpreters[pid] { - if err := instance.UpdateTSDInfo(pm.ebpf, pid, *tsdInfo); err != nil { - log.Errorf("Failed to update PID %v TSDInfo: %v", + if err := instance.UpdateLibcInfo(pm.ebpf, pid, *libcInfo); err != nil { + log.Errorf("Failed to update PID %v LibcInfo: %v", pid, err) } } } -// getTSDInfo retrieves the TSDInfo of given PID +// getLibcInfo retrieves the LibcInfo of given PID // Caller must hold pm.mu read lock. -func (pm *ProcessManager) getTSDInfo(pid libpf.PID) *tpbase.TSDInfo { +func (pm *ProcessManager) getLibcInfo(pid libpf.PID) *libc.LibcInfo { if info, ok := pm.pidToProcessInfo[pid]; ok { - return info.tsdInfo + return info.libcInfo } return nil } @@ -121,7 +121,7 @@ func (pm *ProcessManager) updatePidInformation(pr process.Process, m *Mapping) ( meta: pr.GetProcessMeta(process.MetaConfig{IncludeEnvVars: pm.includeEnvVars}), mappings: make(map[libpf.Address]*Mapping), mappingsByFileID: make(map[host.FileID]map[libpf.Address]*Mapping), - tsdInfo: nil, + libcInfo: nil, } pm.pidToProcessInfo[pid] = info @@ -238,10 +238,10 @@ func (pm *ProcessManager) handleNewInterpreter(pr process.Process, m *Mapping, log.Debugf("Attached to %v interpreter in PID %v", ei.Data, pid) pm.assignInterpreter(pid, key, instance) - if tsdInfo := pm.getTSDInfo(pid); tsdInfo != nil { - err = instance.UpdateTSDInfo(pm.ebpf, pid, *tsdInfo) + if libcInfo := pm.getLibcInfo(pid); libcInfo != nil { + err = instance.UpdateLibcInfo(pm.ebpf, pid, *libcInfo) if err != nil { - log.Errorf("Failed to update PID %v TSDInfo: %v", pid, err) + log.Errorf("Failed to update PID %v LibcInfo: %v", pid, err) } } @@ -270,7 +270,7 @@ func (pm *ProcessManager) handleNewMapping(pr process.Process, m *Mapping, return err } - pm.assignTSDInfo(pr.PID(), ei.TSDInfo) + pm.assignLibcInfo(pr.PID(), ei.LibcInfo) if ei.Data != nil { return pm.handleNewInterpreter(pr, m, &ei) diff --git a/processmanager/types.go b/processmanager/types.go index d445d02da..4a739e127 100644 --- a/processmanager/types.go +++ b/processmanager/types.go @@ -11,6 +11,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/interpreter" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" "go.opentelemetry.io/ebpf-profiler/metrics" @@ -19,7 +20,6 @@ import ( eim "go.opentelemetry.io/ebpf-profiler/processmanager/execinfomanager" "go.opentelemetry.io/ebpf-profiler/reporter" "go.opentelemetry.io/ebpf-profiler/times" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -168,7 +168,7 @@ type processInfo struct { // executable mappings keyed by host file ID. mappingsByFileID map[host.FileID]map[libpf.Address]*Mapping // C-library Thread Specific Data information - tsdInfo *tpbase.TSDInfo + libcInfo *libc.LibcInfo } // addMapping adds a mapping to the internal indices. diff --git a/support/ebpf/ruby_tracer.ebpf.c b/support/ebpf/ruby_tracer.ebpf.c index bdbeae972..b04dc81e0 100644 --- a/support/ebpf/ruby_tracer.ebpf.c +++ b/support/ebpf/ruby_tracer.ebpf.c @@ -226,6 +226,48 @@ static EBPF_INLINE ErrorCode walk_ruby_stack( return ERR_OK; } +static EBPF_INLINE u64 read_tls_addr_from_dtv(u64 symbol, u32 module_id, u32 dtv_step) +{ + int err; + u64 addr; + + u64 tsd_base; + if (tsd_get_base((void **)&tsd_base) != 0) { + DEBUG_PRINT("ruby: failed to get TSD base for TLS symbol lookup"); + return 0; + } + + u64 dtv_addr; + // On x86-64, the FS register points to the TCB + // The DTV is typically at offset 0 or 8 from the TCB + // You may need to adjust this offset based on your glibc version + + // Try offset 8 first (common in modern glibc) + // https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86_64/nptl/tls.h;h=683f8bfdfcad45734c4cc1aeea844582a5528640;hb=HEAD#l46 + if ((err = bpf_probe_read_user(&dtv_addr, sizeof(void *), (void *)(tsd_base + 8)))) { + DEBUG_PRINT("ruby: failed to read TLS DTV addr: %d", err); + return 0; + } + + // DTV layout is the same across architectures: + // DTV[0] = generation counter + // DTV[1] = module 1's TLS block + // DTV[2] = module 2's TLS block + // ... + u64 dtv_offset = module_id * dtv_step; + + if ((err = bpf_probe_read_user(&addr, sizeof(void *), (void *)(dtv_addr + dtv_offset)))) { + DEBUG_PRINT( + "ruby: failed to read TLS block addr for module %d at DTV offset %llu: %d", + module_id, + dtv_offset, + err); + return 0; + } + addr += symbol; + return addr; +} + // unwind_ruby is the tail call destination for PROG_UNWIND_RUBY. static EBPF_INLINE int unwind_ruby(struct pt_regs *ctx) { @@ -274,6 +316,19 @@ static EBPF_INLINE int unwind_ruby(struct pt_regs *ctx) } DEBUG_PRINT("ruby: EC from TLS: 0x%llx", (u64)current_ctx_addr); + } else if (rubyinfo->tls_module_id != 0) { + u64 tls_current_ec_addr = + read_tls_addr_from_dtv(rubyinfo->current_ec_tls_offset, rubyinfo->tls_module_id, 16); + if (tls_current_ec_addr == 0) { + DEBUG_PRINT("ruby: failed failed to read EC from DTV"); + goto exit; + } + if (bpf_probe_read_user( + ¤t_ctx_addr, sizeof(current_ctx_addr), (void *)(tls_current_ec_addr))) { + goto exit; + } + + DEBUG_PRINT("ruby: EC from TLS via DTV: 0x%llx", (u64)current_ctx_addr); } else if (rubyinfo->version >= 0x30000) { void *single_main_ractor = NULL; if (bpf_probe_read_user( diff --git a/support/ebpf/types.h b/support/ebpf/types.h index 3317ff17d..33b99f5e2 100644 --- a/support/ebpf/types.h +++ b/support/ebpf/types.h @@ -466,6 +466,12 @@ typedef struct RubyProcInfo { // tls_offset holds TLS base + ruby_current_ec tls symbol, as an offset from tpbase u64 current_ec_tpbase_tls_offset; + // current_ec_tls_offset is the offset of the current EC within the TLS + u64 current_ec_tls_offset; + + // tls_module_id is the module ID for libruby.so for reading the EC from TLS via DTV + u64 tls_module_id; + // current_ctx_ptr holds the address of the symbol ruby_current_execution_context_ptr. u64 current_ctx_ptr; diff --git a/support/types.go b/support/types.go index 7541b5b89..af566c3da 100644 --- a/support/types.go +++ b/support/types.go @@ -269,6 +269,8 @@ type PyProcInfo struct { type RubyProcInfo struct { Version uint32 Current_ec_tpbase_tls_offset uint64 + Current_ec_tls_offset uint64 + Tls_module_id uint64 Current_ctx_ptr uint64 Vm_stack uint8 Vm_stack_size uint8 @@ -316,7 +318,7 @@ const ( sizeof_ApmIntProcInfo = 0x8 sizeof_DotnetProcInfo = 0x4 sizeof_PHPProcInfo = 0x18 - sizeof_RubyProcInfo = 0x28 + sizeof_RubyProcInfo = 0x38 ) const ( diff --git a/tpbase/libc_test.go b/tpbase/libc_test.go deleted file mode 100644 index 8f5f48be7..000000000 --- a/tpbase/libc_test.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package tpbase - -import ( - "debug/elf" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestExtractTSDInfo(t *testing.T) { - testCases := map[string]struct { - machine elf.Machine - code []byte - info TSDInfo - }{ - "musl 1.2.3 / Alpine 3.16 / arm64": { - machine: elf.EM_AARCH64, - code: []byte{ - 0x41, 0xd0, 0x3b, 0xd5, // mrs x1, tpidr_el0 - 0x21, 0x80, 0x5a, 0xf8, // ldur x1, [x1, #-88] - 0x20, 0x58, 0x60, 0xf8, // ldr x0, [x1, w0, uxtw #3] - 0xc0, 0x03, 0x5f, 0xd6, // ret - }, - info: TSDInfo{ - Offset: -88, - Multiplier: 8, - Indirect: 1, - }, - }, - "glibc 2.35 / Fedora 36 / arm64": { - machine: elf.EM_AARCH64, - code: []byte{ - 0x5f, 0x24, 0x03, 0xd5, // bti c - 0xe1, 0x03, 0x00, 0x2a, // mov w1, w0 - 0x1f, 0x7c, 0x00, 0x71, // cmp w0, #0x1f - 0x48, 0x02, 0x00, 0x54, // b.hi 85bb4 <__pthread_getspecific+0x54> - 0x20, 0x7c, 0x7c, 0xd3, // ubfiz x0, x1, #4, #32 - 0x42, 0xd0, 0x3b, 0xd5, // mrs x2, tpidr_el0 - 0x00, 0xc0, 0x1a, 0xd1, // sub x0, x0, #0x6b0 - 0x42, 0x00, 0x00, 0x8b, // add x2, x2, x0 - 0x40, 0x04, 0x40, 0xf9, // ldr x0, [x2, #8] - 0x40, 0x01, 0x00, 0xb4, // cbz x0, 85bac <__pthread_getspecific+0x4c> - 0x21, 0x7c, 0x7c, 0xd3, // ubfiz x1, x1, #4, #32 - 0xe3, 0x08, 0x00, 0xd0, // adrp x3, 1a3000 - 0x63, 0x40, 0x0a, 0x91, // add x3, x3, #0x290 - 0x44, 0x00, 0x40, 0xf9, // ldr x4, [x2] - 0x61, 0x68, 0x61, 0xf8, // ldr x1, [x3, x1] - 0x3f, 0x00, 0x04, 0xeb, // cmp x1, x4 - 0x41, 0x00, 0x00, 0x54, // b.ne 85ba8 <__pthread_getspecific+0x48> - 0xc0, 0x03, 0x5f, 0xd6, // ret - // code skipped handling keys >0x1f - }, - info: TSDInfo{ - Offset: -0x6b0 + 8, - Multiplier: 0x10, - }, - }, - "glibc 2.33 / Fedora 34 / arm64": { - machine: elf.EM_AARCH64, - code: []byte{ - 0x5f, 0x24, 0x03, 0xd5, // bti c - 0xe1, 0x03, 0x00, 0x2a, // mov w1, w0 - 0x1f, 0x7c, 0x00, 0x71, // cmp w0, #0x1f - 0x48, 0x02, 0x00, 0x54, // b.hi fb94 <__pthread_getspecific+0x54> // b.pmore - 0x40, 0xd0, 0x3b, 0xd5, // mrs x0, tpidr_el0 - 0x22, 0x44, 0x00, 0x11, // add w2, w1, #0x11 - 0x00, 0x40, 0x1e, 0xd1, // sub x0, x0, #0x790 - 0x02, 0x10, 0x02, 0x8b, // add x2, x0, x2, lsl #4 - 0x40, 0x04, 0x40, 0xf9, // ldr x0, [x2, #8] - 0x00, 0x01, 0x00, 0xb4, // cbz x0, fb84 <__pthread_getspecific+0x44> - 0x21, 0x7c, 0x7c, 0xd3, // ubfiz x1, x1, #4, #32 - 0x03, 0x01, 0x00, 0xb0, // adrp x3, 30000 <__nptl_nthreads> - 0x63, 0x80, 0x01, 0x91, // add x3, x3, #0x60 - 0x44, 0x00, 0x40, 0xf9, // ldr x4, [x2] - 0x61, 0x68, 0x61, 0xf8, // ldr x1, [x3, x1] - 0x3f, 0x00, 0x04, 0xeb, // cmp x1, x4 - 0x41, 0x00, 0x00, 0x54, // b.ne fb88 <__pthread_getspecific+0x48> // b.any - 0xc0, 0x03, 0x5f, 0xd6, // ret - // code skipped handling keys >0x1f - }, - info: TSDInfo{ - Offset: -0x790 + (0x11 << 4) + 8, - Multiplier: 0x10, - }, - }, - "musl 1.2.3 / Alpine 3.16 / x86_64": { - machine: elf.EM_X86_64, - code: []byte{ - // mov %fs:0x0,%rax - // mov 0x80(%rax),%rax - // mov %edi,%edi - // mov (%rax,%rdi,8),%rax - // ret - 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00, - 0x00, 0x48, 0x8b, 0x80, 0x80, 0x00, 0x00, 0x00, - 0x89, 0xff, 0x48, 0x8b, 0x04, 0xf8, 0xc3, - }, - info: TSDInfo{ - Offset: 0x80, - Multiplier: 0x8, - Indirect: 1, - }, - }, - "musl 1.1.24 / Alpine 3.12 / x86_64": { - machine: elf.EM_X86_64, - code: []byte{ - // mov %fs:0x0,%rax - // mov 0x88(%rax),%rax - // mov %edi,%edi - // mov (%rax,%rdi,8),%rax - // ret - 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00, - 0x00, 0x48, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, - 0x89, 0xff, 0x48, 0x8b, 0x04, 0xf8, 0xc3, - }, - info: TSDInfo{ - Offset: 0x88, - Multiplier: 0x8, - Indirect: 1, - }, - }, - "glibc 2.32 / Fedora 33 / x86_64": { - machine: elf.EM_X86_64, - code: []byte{ - // endbr64 - // cmp $0x1f,%edi - // ja 10bf0 <__pthread_getspecific+0x40> - // lea 0x31(%rdi),%eax - // shl $0x4,%rax # <- 0x31<<4 = 0x310, <<4 = *0x10 - // mov %fs:0x10,%rdx - // add %rdx,%rax - // mov 0x8(%rax),%r8 # <- +8 - // test %r8,%r8 - // je 10beb <__pthread_getspecific+0x3b> - // mov %edi,%edi - // lea 0xc4c2(%rip),%rdx # 1d0a0 <__GI___pthread_keys> - // mov (%rax),%rsi - // shl $0x4,%rdi - // cmp %rsi,(%rdx,%rdi,1) - // jne 10c20 <__pthread_getspecific+0x70> - // mov %r8,%rax - // retq - // code skipped for handling keys >0x1f - 0xf3, 0x0f, 0x1e, 0xfa, 0x83, 0xff, 0x1f, 0x77, - 0x37, 0x8d, 0x47, 0x31, 0x48, 0xc1, 0xe0, 0x04, - 0x64, 0x48, 0x8b, 0x14, 0x25, 0x10, 0x00, 0x00, - 0x00, 0x48, 0x01, 0xd0, 0x4c, 0x8b, 0x40, 0x08, - 0x4d, 0x85, 0xc0, 0x74, 0x16, 0x89, 0xff, 0x48, - 0x8d, 0x15, 0xc2, 0xc4, 0x00, 0x00, 0x48, 0x8b, - 0x30, 0x48, 0xc1, 0xe7, 0x04, 0x48, 0x39, 0x34, - 0x3a, 0x75, 0x35, 0x4c, 0x89, 0xc0, 0xc3, - }, - info: TSDInfo{ - Offset: 0x310 + 8, - Multiplier: 0x10, - }, - }, - "glibc 2.35 / Fedora 36 / x86_64": { - machine: elf.EM_X86_64, - code: []byte{ - // endbr64 - // cmp $0x1f,%edi - // ja 92a40 <__pthread_getspecific@GLIBC_2.2.5+0x40> - // mov %edi,%eax - // add $0x31,%rax - // shl $0x4,%rax - // add %fs:0x10,%rax - // mov 0x8(%rax),%rdx - // test %rdx,%rdx - // je 92a78 <__pthread_getspecific@GLIBC_2.2.5+0x78> - // mov %edi,%edi - // lea 0x167b92(%rip),%rcx - // mov (%rax),%rsi - // shl $0x4,%rdi - // cmp %rsi,(%rcx,%rdi,1) - // jne 92a70 <__pthread_getspecific@GLIBC_2.2.5+0x70> - // mov %rdx,%rax - // ret - // code skipped for handling keys >0x1f - 0xf3, 0x0f, 0x1e, 0xfa, 0x83, 0xff, 0x1f, 0x77, - 0x37, 0x89, 0xf8, 0x48, 0x83, 0xc0, 0x31, 0x48, - 0xc1, 0xe0, 0x04, 0x64, 0x48, 0x03, 0x04, 0x25, - 0x10, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x50, 0x08, - 0x48, 0x85, 0xd2, 0x74, 0x53, 0x89, 0xff, 0x48, - 0x8d, 0x0d, 0x92, 0x7b, 0x16, 0x00, 0x48, 0x8b, - 0x30, 0x48, 0xc1, 0xe7, 0x04, 0x48, 0x39, 0x34, - 0x39, 0x75, 0x35, 0x48, 0x89, 0xd0, 0xc3, - }, - info: TSDInfo{ - Offset: 0x310 + 8, - Multiplier: 0x10, - }, - }, - "glibc 2.38 / Fedora 39 / arm64": { - machine: elf.EM_AARCH64, - code: []byte{ - 0x3f, 0x23, 0x03, 0xd5, // paciasp - 0xfd, 0x7b, 0xbf, 0xa9, // stp x29, x30, [sp, #-16]! - 0xe1, 0x03, 0x00, 0x2a, // mov w1, w0 - 0xfd, 0x03, 0x00, 0x91, // mov x29, sp - 0x1f, 0x7c, 0x00, 0x71, // cmp w0, #0x1f - // b.hi 91d98 <__pthread_getspecific@GLIBC_2.17+0x58> // b.pmore - 0x28, 0x02, 0x00, 0x54, - // mov x0, #0xfffffffffffff9d0 // #-1584 - 0xe0, 0xc5, 0x80, 0x92, - 0x42, 0xd0, 0x3b, 0xd5, // mrs x2, tpidr_el0 - 0x00, 0x50, 0x21, 0x8b, // add x0, x0, w1, uxtw #4 - 0x42, 0x00, 0x00, 0x8b, // add x2, x2, x0 - 0x40, 0x04, 0x40, 0xf9, // ldr x0, [x2, #8] - // cbz x0, 91dcc <__pthread_getspecific@GLIBC_2.17+0x8c> - 0x00, 0x03, 0x00, 0xb4, - 0x21, 0x7c, 0x7c, 0xd3, // ubfiz x1, x1, #4, #32 - 0x83, 0x09, 0x00, 0xb0, // adrp x3, 1c2000 - 0x63, 0x40, 0x17, 0x91, // add x3, x3, #0x5d0 - 0x44, 0x00, 0x40, 0xf9, // ldr x4, [x2] - 0x61, 0x68, 0x61, 0xf8, // ldr x1, [x3, x1] - 0x3f, 0x00, 0x04, 0xeb, // cmp x1, x4 - // b.ne 91dc8 <__pthread_getspecific@GLIBC_2.17+0x88> // b.any - 0x01, 0x02, 0x00, 0x54, - 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 - 0xbf, 0x23, 0x03, 0xd5, // autiasp - 0xc0, 0x03, 0x5f, 0xd6, // ret - // code skipped handling keys >0x1f - }, - info: TSDInfo{ - Offset: -1584 + 8, - Multiplier: 16, - }, - }, - "booking coredump glibc": { - machine: elf.EM_X86_64, - code: []byte{ - 0x83, 0xff, 0x1f, 0x77, 0x49, 0x89, 0xf8, 0x48, 0x83, 0xc0, 0x30, 0x48, - 0xc1, 0xe0, 0x04, 0x64, 0x48, 0x8b, 0x14, 0x25, 0x10, 0x00, 0x00, 0x00, - 0x48, 0x8d, 0x54, 0x02, 0x10, 0x48, 0x8b, 0x42, 0x08, 0x48, 0x85, 0xc0, - 0x74, 0x1a, 0x89, 0xff, 0x48, 0x8d, 0x0d, 0x61, 0xaa, 0x20, 0x00, 0x48, - 0xc1, 0xe7, 0x04, 0x48, 0x8b, 0x34, 0x39, 0x48, 0x39, 0x32, 0x75, 0x07, - 0xf3, 0xc3, - }, - info: TSDInfo{ - Offset: 0x310 + 8, - Multiplier: 0x10, - }, - }, - } - - for name, test := range testCases { - t.Run(name, func(t *testing.T) { - var info TSDInfo - var err error - switch test.machine { - case elf.EM_X86_64: - info, err = extractTSDInfoX86(test.code) - case elf.EM_AARCH64: - info, err = extractTSDInfoARM(test.code) - } - if assert.NoError(t, err) { - assert.Equal(t, test.info, info, "Wrong TSD info extraction") - } - }) - } -} diff --git a/tpbase/libc_x86.go b/tpbase/libc_x86.go deleted file mode 100644 index 8d1c839f8..000000000 --- a/tpbase/libc_x86.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" -import ( - "errors" - - "go.opentelemetry.io/ebpf-profiler/asm/amd" - e "go.opentelemetry.io/ebpf-profiler/asm/expression" - "golang.org/x/arch/x86/x86asm" -) - -func extractTSDInfoX86(code []byte) (TSDInfo, error) { - it := amd.NewInterpreterWithCode(code) - key := it.Regs.Get(amd.RDI) - _, err := it.LoopWithBreak(func(op x86asm.Inst) bool { - return op.Op == x86asm.RET - }) - if err != nil { - return TSDInfo{}, err - } - res := it.Regs.Get(amd.RAX) - var ( - multiplier = e.NewImmediateCapture("multiplier") - multiplier2 = e.NewImmediateCapture("multiplier2") - offset = e.NewImmediateCapture("offset") - ) - - expected := e.Mem8( - e.Add( - e.Mem8( - e.Add( - e.MemWithSegment8(x86asm.FS, e.Imm(0)), - offset, - ), - ), - e.Multiply( - e.ZeroExtend32(key), - multiplier), - ), - ) - if res.Match(expected) { - return TSDInfo{ - Offset: int16(offset.CapturedValue()), - Multiplier: uint8(multiplier.CapturedValue()), - Indirect: 1, - }, nil - } - expected = e.Mem8( - e.Add( - e.MemWithSegment8(x86asm.FS, e.Imm(0x10)), - e.Multiply(e.ZeroExtend32(key), multiplier), - offset, - ), - ) - if res.Match(expected) { - return TSDInfo{ - Offset: int16(offset.CapturedValue()), - Multiplier: uint8(multiplier.CapturedValue()), - Indirect: 0, - }, nil - } - expected = e.Mem8( - e.Add( - e.MemWithSegment8(x86asm.FS, e.Imm(0x10)), - e.Multiply( - e.ZeroExtend32(e.Add(key, multiplier2)), - multiplier, - ), - offset, - ), - ) - if res.Match(expected) { - return TSDInfo{ - Offset: int16(multiplier.CapturedValue()*multiplier2.CapturedValue() + - offset.CapturedValue()), - Multiplier: uint8(multiplier.CapturedValue()), - Indirect: 0, - }, nil - } - return TSDInfo{}, errors.New("could not extract tsdInfo amd") -}