-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathfields.go
353 lines (295 loc) · 10.3 KB
/
fields.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
344
345
346
347
348
349
350
351
352
353
package skbtrace
import (
"fmt"
"regexp"
"strings"
)
const (
// Either an object name starting with dollar sign (i.e. $)
// or a global variable/field alias (latter might have dashes)
reFieldObjGroup = "([$]\\w+|[0-9a-z_\\-|]+)"
reFieldFieldGroups = "(?:->)?([0-9a-z_\\->.|]+)?"
)
var reField = regexp.MustCompile("^" + reFieldObjGroup + reFieldFieldGroups + "$")
func newFieldRefErrorf(
obj, fieldName string, err error,
fmtstr string, args ...interface{},
) *errorImpl {
fieldIdent := string(ExprField(obj, fieldName))
return newErrorf(ErrLevelField, fieldIdent, err, fmtstr, args...)
}
const (
// Use converters when dumping value or as a key in printed map,
// default if no mask is specified
ConverterDump uint = 1 << iota
// Use converters when generating key in non-printable map
ConverterHiddenKey
// Use converter in filters. Not recommended, instead filter
// preprocessor should process user-supplied constant
ConverterFilter
// Use Converter as argument to argument to aggregation function
// For now it uses same converter as prettifier to apply ntohs
// conversion (it is absurd to require aggregation on really
// pretty fields like IP address)
ConverterAggregateArg = ConverterDump
)
// FieldConverter is a prototype of Field.Converter field
type FieldConverter func(obj, field string) ([]Statement, Expression)
// FieldPreprocessor is a prototype of Field.Preprocessor field
type FieldPreprocessor func(op, value string) (string, error)
// Field describes a single position in a printf-statement generated by FieldGroup and
// used as a handler for structure field name (structure itself is stored in FieldGroup),
// help (description of field), formatting specifier and text which precedes value and
// optional converter function which may produce extra statements and expression for field
// value.
type Field struct {
// Name of struct field defined in header file
Name string
// Alias for using single-string shortcut without verbose object->field form
// i.e. 'src' is '$iph->saddr'
Alias string
// If specified allows to specify an alias accessible from multiple paths
// Whichever is more favorable wins
WeakAlias bool
// Format key which precedes value when printing. If omitted, Name is used
FmtKey string
// Format specifier for BPFTrace printf()
FmtSpec string
// Converter function which is used to retrieve field value (which returned as expression)
// using statements from object obj and its field named field
Converter FieldConverter
// ConverterMask overrides contexts in which converter will be used
ConverterMask uint
// Preprocessor function is used to convert human-readable value used in filter
// to machine value
Preprocessor FieldPreprocessor
// FilterOperator processes op specified in filter into an expression by filter
FilterOperator func(expr Expression, op, value string) (Expression, error)
// SanityFilter produces a template of filter which should be checked
// when getting the object to avoid printing junk data
SanityFilter *Filter
// Help for fields helper subcommand
Help string
}
// FieldGroup produces a single printf statement (and some converter statements from
// the fields) for a single object. If object is omitted, then fields are interpreted as
// global variables.
// NOTE: bpftrace has limitation of 7 format specs per statement, hence splitting
// fields into rows. Also, it is a bit prettier on my vertical display.
type FieldGroup struct {
// Name of the row (might be non-unique, in this case multiple rows will be dumped)
Row string
// Object variable name to be dumped. Might be empty for objectless fields
// taken from probe args or global variables, otherwise must refer to a valid
// object registered by AddObjects
Object string
// List of fields in this group
Fields []*Field
// Name of the row for base group (used only for help)
BaseFieldGroup string
// Prefix for field aliases: if non empty, field copies will get different
// aliases prefixed with this string and a dash. Useful for multiple instances
// of header, or in/out interface logic
FieldAliasPrefix string
}
type fieldAliasRef struct {
fg *FieldGroup
field *Field
weakGroups []*FieldGroup
}
func (fref *fieldAliasRef) Ref() *fieldAliasRef {
return fref
}
func (fref *fieldAliasRef) Level() ErrorLevel {
return ErrLevelField
}
func (fref *fieldAliasRef) Resolve(fg *FieldGroup) {
fref.fg = fg
// When resolving field aliases, field found originally may have different preprocessors,
// so retry searching.
fref.field = fg.findFieldByAlias(fref.field.Alias)
if fref.field == nil {
panic("cannot find field while re-resolving fref alias")
}
}
// Wraps base field group into prefix. Useful for inner headers
func (fg FieldGroup) Wrap(obj, prefix string) FieldGroup {
fg.Object = obj
fg.BaseFieldGroup = fg.Row
fg.FieldAliasPrefix = prefix
fg.Row = fmt.Sprintf("%s-%s", prefix, fg.Row)
return fg
}
// Find a field from the pair of strings. Token can be one of
// - field alias
// - global variable name or probe context name
// - object name, in this case fieldName is expected to be non-empty
func (b *Builder) findField(token, fieldName string) *fieldAliasRef {
if fieldName == "" {
// First try to interpret this as alias, but fallback to objectless fields
// if not successful
fref := b.fieldAliasMap[token]
if fref != nil {
return fref
}
}
objFields := b.fieldObjectMap[token]
if len(objFields) == 0 {
// Try to interpret obj/fieldName as objectless global variable, such as comm
// This is useful when parsing filters, but might be of use in other contexts
// NOTE: that will require caller to swap fields as well
if fieldName == "" {
token, fieldName = fieldName, token
objFields = b.fieldObjectMap[token]
}
if len(objFields) == 0 {
return nil
}
}
for _, fref := range objFields {
if fref.field.Name == fieldName || fref.field.FmtKey == fieldName {
return fref
}
}
return nil
}
func (fg *FieldGroup) findFieldByAlias(aliasName string) *Field {
for _, field := range fg.Fields {
if field.Alias == aliasName {
return field
}
}
return nil
}
// generateFieldExpression generates statements which are required to convert value,
// expression which allows to access it which can be probe argument, global variable
// or an object's field or returns an error if expression cannot be produced.
// If field is being printed (in dump or aggregate) useConverters may be passed to
// apply human-readable converters
func (b *Builder) generateFieldExpression(
fg *FieldGroup, field *Field, probe *Probe, convMask uint,
) ([]Statement, Expression, error) {
if field.Converter != nil {
fieldConvMask := field.ConverterMask
if fieldConvMask == 0 {
fieldConvMask = ConverterDump
}
if convMask&fieldConvMask != 0 {
convStmts, expr := field.Converter(fg.Object, field.Name)
return convStmts, expr, nil
}
}
if fg.Object == "" {
if varExpr, ok := b.globalVars[field.Name]; ok {
return nil, varExpr, nil
}
if arg, ok := probe.Args[field.Name]; ok {
return nil, Expr(arg), nil
}
return nil, "", newFieldRefErrorf(fg.Object, field.Name, nil,
"cannot generate field expression for objectless field")
}
return nil, ExprField(fg.Object, field.Name), nil
}
// Generates printf() statements for the corresponding field group
// No context checking is performed here as it is expected that caller already
// performed necessary casts
func (b *Builder) generatePrintStatements(fg *FieldGroup, probe *Probe) ([]Statement, error) {
var stmts []Statement
var values []Expression
var fmtSpecs []string
for _, field := range fg.Fields {
fieldStms, expr, err := b.generateFieldExpression(fg, field, probe, ConverterDump)
if err != nil {
return nil, err
}
stmts = append(stmts, fieldStms...)
values = append(values, expr)
fmtKey := field.FmtKey
if len(fmtKey) == 0 {
fmtKey = field.Name
}
fmtSpec := field.FmtSpec
if len(fmtSpec) == 0 {
fmtSpec = "%d"
}
fmtSpecs = append(fmtSpecs, fmt.Sprintf("%s %s", fmtKey, fmtSpec))
}
return append(stmts, Stmtf(`printf("%s: %s\n", %s)`,
strings.ToUpper(fg.Row), strings.Join(fmtSpecs, " "), ExprJoin(values))), nil
}
// Parses key as supplied in CLI in obj->field notation to pair of token and
// fieldName which can be used for
func (b *Builder) parseKey(key string) (token string, fieldName string, err error) {
groups := reField.FindStringSubmatch(key)
if len(groups) < 2 || len(groups) > 3 {
err = newCommonError(ErrLevelField, key, ErrMsgParseError)
return
}
token = groups[1]
if len(groups) == 3 {
fieldName = groups[2]
}
return
}
// Resolves raw keys passed in command line into field references
func (b *Builder) prepareKeys(rawKeys []string) ([]*fieldAliasRef, error) {
keys := make([]*fieldAliasRef, 0, len(rawKeys))
for _, rawKey := range rawKeys {
token, fieldName, err := b.parseKey(rawKey)
if err != nil {
return nil, err
}
key := b.findField(token, fieldName)
if key == nil {
return nil, newCommonError(ErrLevelField, rawKey, ErrMsgNotFound)
}
keys = append(keys, key)
}
return keys, nil
}
// Generates expressions and statements for keys used by associative arrays.
// If array is dumped to stdout, useConverters should be set to true to apply converters
func (b *Builder) generateKeyExpressions(
keys []*fieldAliasRef, probe *Probe, convMask uint,
) (stmts []Statement, exprs []Expression, err error) {
for _, fref := range keys {
keyStmts, expr, err := b.generateFieldExpression(fref.fg, fref.field, probe, convMask)
if err != nil {
return nil, nil, err
}
stmts = append(stmts, keyStmts...)
exprs = append(exprs, expr)
}
return
}
// Builds a block (or finds it) having all key variables available
func (b *Builder) getBlockWithKeys(
block *Block, keys []*fieldAliasRef, convMask uint,
) (*Block, []Expression, error) {
if len(keys) == 0 {
return block, nil, nil
}
for _, key := range keys {
keyBlock, err := b.getBlockWithObject(block, key.fg.Object)
if err != nil {
return nil, nil, err
}
block = keyBlock
}
stmts, exprs, err := b.generateKeyExpressions(keys, block.probe, convMask)
if err != nil {
return nil, nil, err
}
block.Add(stmts...)
return block, exprs, nil
}
func (b *Builder) getFieldWeakRefs(frefs []*fieldAliasRef) []weakAliasRef {
weakRefs := make([]weakAliasRef, 0)
for _, fref := range frefs {
if fref.weakGroups != nil {
weakRefs = append(weakRefs, fref)
}
}
return weakRefs
}