-
Notifications
You must be signed in to change notification settings - Fork 229
/
assembly_decode.go
93 lines (84 loc) · 2.58 KB
/
assembly_decode.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Apache License 2.0.
* See the file "LICENSE" for details.
*/
package tpbase
import (
"errors"
"fmt"
ah "github.com/elastic/otel-profiling-agent/libpf/armhelpers"
aa "golang.org/x/arch/arm64/arm64asm"
)
func arm64GetAnalyzers() []Analyzer {
return []Analyzer{
{"tls_set", AnalyzeTLSSetARM64},
}
}
// AnalyzeTLSSet looks at the assembly of the `tls_set` function in the
// kernel in order to compute the offset of `tp_value` into `task_struct`.
func AnalyzeTLSSetARM64(code []byte) (uint32, error) {
// This tries to extract offset of thread.uw.tp_value relative to
// struct task_struct. The code analyzed comes from:
// linux/arch/arm64/kernel/ptrace.c: tls_set(struct task_struct *target, ...) {
// [...]
// unsigned long tls = target->thread.uw.tp_value;
//
// Anyalysis is based on the fact that 'target' is in X0 at the start, and early
// in the assembly there is a direct load via this pointer. Because of reduced
// instruction set, the pointer often gets moved to another register before the
// load we are interested, so the arg []bool tracks which register is currently
// holding the tracked pointer. Once a proper load is matched, the offset is
// extracted from it.
// Start tracking of X0
var arg [32]bool
arg[0] = true
for offs := 0; offs < len(code); offs += 4 {
inst, err := aa.Decode(code[offs:])
if err != nil {
break
}
if inst.Op == aa.B {
break
}
switch inst.Op {
case aa.MOV:
// Track register moves
destReg, ok := ah.Xreg2num(inst.Args[0])
if !ok {
continue
}
if srcReg, ok := ah.Xreg2num(inst.Args[1]); ok {
arg[destReg] = arg[srcReg]
}
case aa.LDR:
// Track loads with offset of the argument pointer we care
m, ok := inst.Args[1].(aa.MemImmediate)
if !ok {
continue
}
var srcReg int
if srcReg, ok = ah.Xreg2num(m.Base); !ok || !arg[srcReg] {
continue
}
// FIXME: m.imm is not public, but should be.
// https://github.com/golang/go/issues/51517
imm, ok := ah.DecodeImmediate(m)
if !ok {
return 0, err
}
// Quick sanity check. Per example, the offset should
// be under 4k. But allow some leeway.
if imm < 64 || imm >= 65536 {
return 0, fmt.Errorf("detected tpbase %#x looks invalid", imm)
}
return uint32(imm), nil
default:
// Reset register state if something unsupported happens on it
if destReg, ok := ah.Xreg2num(inst.Args[0]); ok {
arg[destReg] = false
}
}
}
return 0, errors.New("tp base not found")
}