-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtracefs.go
343 lines (299 loc) · 9.07 KB
/
tracefs.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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package socket_tracer
import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
const (
debugFSTracingPath = "/sys/kernel/debug/tracing"
traceFSPath = "/sys/kernel/tracing"
)
var (
kprobeRegexp *regexp.Regexp
formatRegexp *regexp.Regexp
)
// TraceFS is an accessor to manage event tracing via tracefs or debugfs.
type TraceFS struct {
basePath string
}
func init() {
var err error
kprobeRegexp, err = regexp.Compile("^([pr]):(?:([^/ ]*)/)?([^/ ]+) ([^ ]+) ?(.*)")
if err != nil {
panic(err)
}
formatRegexp, err = regexp.Compile("\\s+([^:]+):([^;]*);")
if err != nil {
panic(err)
}
}
// NewTraceFS creates a new accessor for the event tracing feature.
// It autodetects a tracefs mounted on /sys/kernel/tracing or via
// debugfs at /sys/kernel/debug/tracing.
func NewTraceFS() (*TraceFS, error) {
ptr, err := NewTraceFSWithPath(traceFSPath)
if err != nil && os.IsNotExist(err) {
ptr, err = NewTraceFSWithPath(debugFSTracingPath)
}
return ptr, err
}
// NewTraceFSWithPath creates a new accessor for the event tracing feature
// at the given path.
func NewTraceFSWithPath(path string) (*TraceFS, error) {
if _, err := os.Stat(filepath.Join(path, kprobeCfgFile)); err != nil {
return nil, err
}
return &TraceFS{basePath: path}, nil
}
// ListKProbes lists the currently installed kprobes / kretprobes
func (dfs *TraceFS) ListKProbes() (kprobes []Probe, err error) {
return dfs.listProbes(kprobeCfgFile)
}
// ListUProbes lists the currently installed uprobes / uretprobes
func (dfs *TraceFS) ListUProbes() (uprobes []Probe, err error) {
return dfs.listProbes(uprobeCfgFile)
}
func (dfs *TraceFS) listProbes(filename string) (probes []Probe, err error) {
mapping, ok := probeFileInfo[filename]
if !ok {
return nil, fmt.Errorf("unknown probe events file: %s", filename)
}
file, err := os.Open(filepath.Join(dfs.basePath, filename))
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if matches := kprobeRegexp.FindStringSubmatch(scanner.Text()); len(matches) == 6 {
typ, ok := mapping[matches[1][0]]
if !ok {
return nil, fmt.Errorf("no mapping for probe of type '%c' in file %s", matches[1][0], filename)
}
probes = append(probes, Probe{
Type: typ,
Group: matches[2],
Name: matches[3],
Address: matches[4],
Fetchargs: matches[5],
})
}
}
return probes, nil
}
// AddKProbe installs a new kprobe/kretprobe.
func (dfs *TraceFS) AddKProbe(probe Probe) (err error) {
return dfs.appendToFile(kprobeCfgFile, probe.String())
}
// RemoveKProbe removes an installed kprobe/kretprobe.
func (dfs *TraceFS) RemoveKProbe(probe Probe) error {
return dfs.appendToFile(kprobeCfgFile, probe.RemoveString())
}
// AddUProbe installs a new uprobe/uretprobe.
func (dfs *TraceFS) AddUProbe(probe Probe) error {
return dfs.appendToFile(uprobeCfgFile, probe.String())
}
// RemoveUProbe removes an installed uprobe/uretprobe.
func (dfs *TraceFS) RemoveUProbe(probe Probe) error {
return dfs.appendToFile(uprobeCfgFile, probe.RemoveString())
}
// RemoveAllUProbes removes all installed kprobes and kretprobes.
func (dfs *TraceFS) RemoveAllKProbes() error {
return dfs.removeAllProbes(kprobeCfgFile)
}
// RemoveAllUProbes removes all installed uprobes and uretprobes.
func (dfs *TraceFS) RemoveAllUProbes() error {
return dfs.removeAllProbes(uprobeCfgFile)
}
func (dfs *TraceFS) removeAllProbes(filename string) error {
file, err := os.OpenFile(filepath.Join(dfs.basePath, filename), os.O_WRONLY|os.O_TRUNC|os.O_SYNC, 0)
if err != nil {
return err
}
return file.Close()
}
func (dfs *TraceFS) appendToFile(filename string, desc string) error {
file, err := os.OpenFile(filepath.Join(dfs.basePath, filename), os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(desc + "\n")
return err
}
// FieldType describes the type of a field in a event tracing probe.
type FieldType uint8
const (
// FieldTypeInteger describes a fixed-size integer field.
FieldTypeInteger = iota
// FieldTypeString describes a string field.
FieldTypeString
// FieldTypeMeta describes the metadata.
FieldTypeMeta
// FieldTypeRaw describes a field of raw bytes.
FieldTypeRaw
)
// Field describes a field returned by a event tracing probe.
type Field struct {
// Name is the name given to the field.
Name string
// Offset of the field inside the raw event.
Offset int
// Size in bytes of the serialised field: 1, 2, 4, 8 for fixed size integers
// or 4 for strings.
Size int
// Signed tells whether an integer is signed (true) or unsigned (false).
Signed bool
// Type of field.
Type FieldType
}
// ProbeDescription describes a Probe and the serialisation format used to
// encode its arguments into a tracing event.
type ProbeDescription struct {
// ID is the numeric ID given to this kprobe/kretprobe by the kernel.
ID int
// Probe is the probe described by this format.
Probe Probe
// Fields is a description of the fields (fetchargs) set by this kprobe.
Fields map[string]Field
}
var integerTypes = map[string]uint8{
"char": 1,
"s8": 1,
"u8": 1,
"short": 2,
"s16": 2,
"u16": 2,
"int": 4,
"s32": 4,
"u32": 4,
"long": strconv.IntSize / 8,
"s64": 8,
"u64": 8,
}
// LoadProbeDescription returns the format used for serialisation of the given
// kprobe/kretprobe into a tracing event. The probe needs to be installed
// for the kernel to provide its format.
func (dfs *TraceFS) LoadProbeDescription(probe Probe) (desc ProbeDescription, err error) {
path := filepath.Join(dfs.basePath, "events", probe.EffectiveGroup(), probe.Name, "format")
file, err := os.Open(path)
if err != nil {
return desc, err
}
defer file.Close()
desc.Probe = probe
desc.Fields = make(map[string]Field)
scanner := bufio.NewScanner(file)
parseFormat := false
for scanner.Scan() {
line := scanner.Text()
if !parseFormat {
// Parse the header
parts := strings.SplitN(line, ": ", 2)
switch {
case len(parts) == 2 && parts[0] == "ID":
if desc.ID, err = strconv.Atoi(parts[1]); err != nil {
return desc, err
}
case len(parts) == 1 && parts[0] == "format:":
parseFormat = true
}
} else {
// Parse the fields
// Ends on the first line that doesn't start with a TAB
if len(line) > 0 && line[0] != '\t' && line[0] != ' ' {
break
}
// Find all "<key>:<value>;" matches
// The actual format is:
// "\tfield:%s %s;\toffset:%u;\tsize:%u;\tsigned:%d;\n"
var f Field
matches := formatRegexp.FindAllStringSubmatch(line, -1)
if len(matches) != 4 {
continue
}
for _, match := range matches {
if len(match) != 3 {
continue
}
key, value := match[1], match[2]
switch key {
case "field":
fparts := strings.Split(value, " ")
n := len(fparts)
if n < 2 {
return desc, fmt.Errorf("bad format for kprobe '%s': `field` has no type: %s", probe.String(), value)
}
fparts, f.Name = fparts[:n-1], fparts[n-1]
typeIdx, isDataLoc := -1, false
for idx, part := range fparts {
switch part {
case "signed", "unsigned":
// ignore
case "__data_loc":
isDataLoc = true
default:
if typeIdx != -1 {
return desc, fmt.Errorf("bad format for kprobe '%s': unknown parameter=`%s` in type=`%s`", probe.String(), part, value)
}
typeIdx = idx
}
}
if typeIdx == -1 {
return desc, fmt.Errorf("bad format for kprobe '%s': type not found in `%s`", probe.String(), value)
}
intLen, isInt := integerTypes[fparts[typeIdx]]
if isInt {
f.Type = FieldTypeInteger
f.Size = int(intLen)
} else {
if fparts[typeIdx] != "char[]" || !isDataLoc {
return desc, fmt.Errorf("bad format for kprobe '%s': unsupported type in `%s`", probe.String(), value)
}
f.Type = FieldTypeString
}
case "offset":
f.Offset, err = strconv.Atoi(value)
if err != nil {
return desc, err
}
case "size":
prev := f.Size
f.Size, err = strconv.Atoi(value)
if err != nil {
return desc, err
}
if prev != 0 && prev != f.Size {
return desc, fmt.Errorf("bad format for kprobe '%s': int field length mismatch at `%s`", probe.String(), line)
}
case "signed":
f.Signed = len(value) > 0 && value[0] == '1'
}
}
if f.Type == FieldTypeString && f.Size != 4 {
return desc, fmt.Errorf("bad format for kprobe '%s': unexpected size for string in `%s`", probe.String(), line)
}
desc.Fields[f.Name] = f
}
}
return desc, nil
}
func (dfs *TraceFS) AvailableFilterFunctions() (functions []string, err error) {
path := filepath.Join(dfs.basePath, "available_filter_functions")
file, err := os.Open(path)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
functions = append(functions, scanner.Text())
}
return functions, nil
}