Skip to content

Commit 63d24dc

Browse files
dalehamelfabled
andauthored
Extract DTV info from __tls_get_addr, add to LibcInfo (open-telemetry#929)
Co-authored-by: Timo Teräs <timo.teras@iki.fi>
1 parent ebad20f commit 63d24dc

12 files changed

Lines changed: 993 additions & 26 deletions

File tree

interpreter/perl/instance.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,21 @@ func hashCOPKey(k copKey) uint32 {
7474

7575
func (i *perlInstance) UpdateLibcInfo(ebpf interpreter.EbpfHandler, pid libpf.PID,
7676
libcInfo libc.LibcInfo) error {
77+
// Perl requires TSDInfo to access thread state. If stateInTSD is true,
78+
// we need valid TSDInfo to proceed. If it's false, we can proceed without it.
79+
// Since UpdateLibcInfo may be called multiple times as LibcInfo is collected
80+
// from multiple DSOs, we should only insert proc data when we have what we need.
7781
d := i.d
82+
if d.stateInTSD && !libcInfo.HasTSDInfo() {
83+
// We need TSDInfo but don't have it yet, wait for another call
84+
return nil
85+
}
86+
87+
// If we've already inserted proc info, don't do it again
88+
if i.procInfoInserted {
89+
return nil
90+
}
91+
7892
stateInTSD := uint8(0)
7993
if d.stateInTSD {
8094
stateInTSD = 1

interpreter/python/python.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,20 @@ func (p *pythonInstance) GetAndResetMetrics() ([]metrics.Metric, error) {
377377
func (p *pythonInstance) UpdateLibcInfo(ebpf interpreter.EbpfHandler, pid libpf.PID,
378378
libcInfo libc.LibcInfo) error {
379379
d := p.d
380+
381+
// If we don't have a static TLS offset (Python < 3.13 or extraction failed),
382+
// we need TSDInfo to access thread state via pthread_getspecific.
383+
// Since UpdateLibcInfo may be called multiple times as LibcInfo is collected
384+
// from multiple DSOs, wait until we have TSDInfo before inserting proc data.
385+
if d.staticTLSOffset == 0 && !libcInfo.HasTSDInfo() {
386+
return nil
387+
}
388+
389+
// Prevent duplicate inserts
390+
if p.procInfoInserted {
391+
return nil
392+
}
393+
380394
vm := &d.vmStructs
381395

382396
cdata := support.PyProcInfo{

libc/libc.go

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,73 @@ import (
1313
)
1414

1515
type TSDInfo = support.TSDInfo
16+
type DTVInfo = support.DTVInfo
1617

1718
// LibcInfo contains introspection information extracted from the C-library
1819
type LibcInfo struct {
1920
// TSDInfo is the TSDInfo extracted for this C-library
2021
TSDInfo TSDInfo
22+
// DTVInfo contains DTV (Dynamic Thread Vector) introspection data for accessing
23+
// TLS variables when TLS descriptors are not available
24+
DTVInfo DTVInfo
25+
}
26+
27+
// IsEqual checks if two LibcInfo instances are equal
28+
func (l LibcInfo) IsEqual(other LibcInfo) bool {
29+
return l.TSDInfo == other.TSDInfo && l.DTVInfo == other.DTVInfo
30+
}
31+
32+
// Merge fills in empty fields of the receiver with corresponding values from other.
33+
// Fields already populated in the receiver are not overwritten.
34+
func (l *LibcInfo) Merge(other LibcInfo) {
35+
// If other has TSDInfo and this instance does not, take it
36+
if l.TSDInfo == (TSDInfo{}) {
37+
l.TSDInfo = other.TSDInfo
38+
}
39+
40+
// If other has DTVInfo and this instance does not, take it
41+
if l.DTVInfo == (DTVInfo{}) {
42+
l.DTVInfo = other.DTVInfo
43+
}
44+
}
45+
46+
// HasTSDInfo returns true if the LibcInfo contains valid TSD information.
47+
// TSDInfo is considered valid when the Multiplier field is non-zero.
48+
func (l LibcInfo) HasTSDInfo() bool {
49+
return l.TSDInfo.Multiplier != 0
50+
}
51+
52+
// HasDTVInfo returns true if the LibcInfo contains valid DTV information.
53+
// DTVInfo is considered valid when the Multiplier field is non-zero.
54+
func (l LibcInfo) HasDTVInfo() bool {
55+
return l.DTVInfo.Multiplier != 0
56+
}
57+
58+
var (
59+
// regex for the libc
60+
libcRegex = regexp.MustCompile(`.*/(ld-musl|ld-linux|libc|libpthread)([-.].*)?\.so`)
61+
)
62+
63+
// IsPotentialLibcDSO determines if the DSO filename potentially contains libc code
64+
func IsPotentialLibcDSO(filename string) bool {
65+
return libcRegex.MatchString(filename)
66+
}
67+
68+
func ExtractLibcInfo(ef *pfelf.File) (*LibcInfo, error) {
69+
tsdinfo, err := extractTSDInfo(ef)
70+
if err != nil {
71+
return nil, err
72+
}
73+
74+
dtvinfo, err := extractDTVInfo(ef)
75+
if err != nil {
76+
return &LibcInfo{}, err
77+
}
78+
79+
return &LibcInfo{
80+
TSDInfo: tsdinfo,
81+
DTVInfo: dtvinfo,
82+
}, nil
2183
}
2284

2385
// This code analyzes the C-library provided POSIX defined function which is used
@@ -65,27 +127,6 @@ type LibcInfo struct {
65127
//
66128
// Reading the value is basically "return self->specific_1stblock[key].data;"
67129

68-
var (
69-
// regex for the libc
70-
libcRegex = regexp.MustCompile(`.*/(ld-musl|libc|libpthread)([-.].*)?\.so`)
71-
)
72-
73-
// IsPotentialTSDDSO determines if the DSO filename potentially contains pthread code
74-
func IsPotentialTSDDSO(filename string) bool {
75-
return libcRegex.MatchString(filename)
76-
}
77-
78-
func ExtractLibcInfo(ef *pfelf.File) (*LibcInfo, error) {
79-
tsdinfo, err := extractTSDInfo(ef)
80-
if err != nil {
81-
return nil, err
82-
}
83-
84-
return &LibcInfo{
85-
TSDInfo: tsdinfo,
86-
}, nil
87-
}
88-
89130
// extractTSDInfo extracts the introspection data for pthread thread specific data.
90131
func extractTSDInfo(ef *pfelf.File) (TSDInfo, error) {
91132
_, code, err := ef.SymbolData("__pthread_getspecific", 2048)
@@ -113,3 +154,31 @@ func extractTSDInfo(ef *pfelf.File) (TSDInfo, error) {
113154
}
114155
return info, nil
115156
}
157+
158+
// extractDTVInfo extracts the introspection data for the DTV to access TLS vars
159+
func extractDTVInfo(ef *pfelf.File) (DTVInfo, error) {
160+
var info DTVInfo
161+
_, code, err := ef.SymbolData("__tls_get_addr", 2048)
162+
if err != nil {
163+
// If the symbol is not exported, this is not a critical error.
164+
// Callers can check HasDTVInfo() to determine if DTV data is available.
165+
return info, nil
166+
}
167+
168+
if len(code) < 8 {
169+
return info, fmt.Errorf("__tls_get_addr function size is %d", len(code))
170+
}
171+
172+
switch ef.Machine {
173+
case elf.EM_AARCH64:
174+
info, err = extractDTVInfoARM(code)
175+
case elf.EM_X86_64:
176+
info, err = extractDTVInfoX86(code)
177+
default:
178+
return info, fmt.Errorf("unsupported arch %s", ef.Machine.String())
179+
}
180+
if err != nil {
181+
return info, fmt.Errorf("failed to extract DTV data: %s", err)
182+
}
183+
return info, nil
184+
}

libc/libc_aarch64.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,146 @@ func extractTSDInfoARM(code []byte) (TSDInfo, error) {
268268
Indirect: indirect,
269269
}, nil
270270
}
271+
272+
func extractDTVInfoARM(code []byte) (DTVInfo, error) {
273+
// Track register states similar to extractTSDInfoARM
274+
var regs [32]regState
275+
276+
dtvOffset := int16(0)
277+
entryWidth := uint32(0)
278+
resetReg := int(-1)
279+
280+
// Scan entire function
281+
for offs := 0; offs < len(code); offs += 4 {
282+
if offs+4 > len(code) {
283+
break
284+
}
285+
286+
if resetReg >= 0 {
287+
// Reset register state if something unsupported happens on it
288+
regs[resetReg] = regState{status: Unspec}
289+
}
290+
291+
inst, err := aa.Decode(code[offs:])
292+
if err != nil {
293+
continue
294+
}
295+
if inst.Op == aa.RET {
296+
break
297+
}
298+
299+
destReg, ok := arm.Xreg2num(inst.Args[0])
300+
if !ok {
301+
continue
302+
}
303+
304+
resetReg = destReg
305+
switch inst.Op {
306+
case aa.MOV:
307+
// Track register moves
308+
srcReg, ok := arm.Xreg2num(inst.Args[1])
309+
if !ok {
310+
continue
311+
}
312+
regs[destReg] = regs[srcReg]
313+
314+
case aa.MRS:
315+
// MRS X1, S3_3_C13_C0_2 (tpidr_el0)
316+
if inst.Args[1].String() == "S3_3_C13_C0_2" {
317+
regs[destReg] = regState{
318+
status: TSDBase, // Reuse TSDBase to mean thread pointer
319+
multiplier: 1,
320+
}
321+
}
322+
323+
case aa.LDUR:
324+
// LDUR X1, [X1,#-8]
325+
m, ok := inst.Args[1].(aa.MemImmediate)
326+
if !ok {
327+
continue
328+
}
329+
srcReg, ok := arm.Xreg2num(m.Base)
330+
if !ok {
331+
continue
332+
}
333+
if regs[srcReg].status == TSDBase {
334+
imm, ok := arm.DecodeImmediate(m)
335+
if !ok {
336+
continue
337+
}
338+
// This is loading the DTV pointer from thread pointer
339+
dtvOffset = int16(imm & 0xFFFF)
340+
regs[destReg] = regState{
341+
status: TSDElementBase, // DTV pointer
342+
offset: imm,
343+
multiplier: 1,
344+
}
345+
} else {
346+
continue
347+
}
348+
349+
case aa.LDR:
350+
if len(inst.Args) < 2 {
351+
continue
352+
}
353+
switch m := inst.Args[1].(type) {
354+
case aa.MemImmediate:
355+
// ldr x1, [x1, #0] or ldr x1, [x1]
356+
srcReg, ok := arm.Xreg2num(m.Base)
357+
if !ok {
358+
continue
359+
}
360+
if regs[srcReg].status == TSDBase {
361+
// Loading DTV pointer from thread pointer
362+
imm, ok := arm.DecodeImmediate(m)
363+
if !ok {
364+
imm = 0
365+
}
366+
dtvOffset = int16(imm & 0xFFFF)
367+
regs[destReg] = regState{
368+
status: TSDElementBase, // DTV pointer
369+
offset: imm,
370+
multiplier: 1,
371+
}
372+
} else {
373+
continue
374+
}
375+
376+
case aa.MemExtend:
377+
// ldr x1, [x1, x2, lsl #3]
378+
srcReg, ok := arm.Xreg2num(m.Base)
379+
if !ok {
380+
continue
381+
}
382+
if regs[srcReg].status == TSDElementBase {
383+
// This is indexing into the DTV array
384+
if m.Amount > 0 {
385+
entryWidth = uint32(1 << m.Amount)
386+
}
387+
}
388+
}
389+
390+
case aa.LSL:
391+
// lsl x3, x3, #4
392+
if len(inst.Args) >= 3 {
393+
if imm, ok := inst.Args[2].(aa.Imm); ok {
394+
entryWidth = uint32(1 << imm.Imm)
395+
}
396+
}
397+
398+
case aa.CMP, aa.CBZ, aa.CMN:
399+
// Opcode with no affect on first argument.
400+
// Noop to exit switch without default continue.
401+
402+
default:
403+
continue
404+
}
405+
resetReg = -1
406+
}
407+
408+
return DTVInfo{
409+
Offset: dtvOffset,
410+
Multiplier: uint8(entryWidth),
411+
Indirect: 1,
412+
}, nil
413+
}

0 commit comments

Comments
 (0)