Commit | Line | Data |
---|---|---|
7ce7e424 TZ |
1 | /* |
2 | * trace_events_filter - generic event filtering | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program; if not, write to the Free Software | |
16 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
17 | * | |
18 | * Copyright (C) 2009 Tom Zanussi <tzanussi@gmail.com> | |
19 | */ | |
20 | ||
21 | #include <linux/debugfs.h> | |
22 | #include <linux/uaccess.h> | |
23 | #include <linux/module.h> | |
24 | #include <linux/ctype.h> | |
25 | ||
26 | #include "trace.h" | |
4bda2d51 | 27 | #include "trace_output.h" |
7ce7e424 TZ |
28 | |
29 | static int filter_pred_64(struct filter_pred *pred, void *event) | |
30 | { | |
31 | u64 *addr = (u64 *)(event + pred->offset); | |
32 | u64 val = (u64)pred->val; | |
33 | int match; | |
34 | ||
35 | match = (val == *addr) ^ pred->not; | |
36 | ||
37 | return match; | |
38 | } | |
39 | ||
40 | static int filter_pred_32(struct filter_pred *pred, void *event) | |
41 | { | |
42 | u32 *addr = (u32 *)(event + pred->offset); | |
43 | u32 val = (u32)pred->val; | |
44 | int match; | |
45 | ||
46 | match = (val == *addr) ^ pred->not; | |
47 | ||
48 | return match; | |
49 | } | |
50 | ||
51 | static int filter_pred_16(struct filter_pred *pred, void *event) | |
52 | { | |
53 | u16 *addr = (u16 *)(event + pred->offset); | |
54 | u16 val = (u16)pred->val; | |
55 | int match; | |
56 | ||
57 | match = (val == *addr) ^ pred->not; | |
58 | ||
59 | return match; | |
60 | } | |
61 | ||
62 | static int filter_pred_8(struct filter_pred *pred, void *event) | |
63 | { | |
64 | u8 *addr = (u8 *)(event + pred->offset); | |
65 | u8 val = (u8)pred->val; | |
66 | int match; | |
67 | ||
68 | match = (val == *addr) ^ pred->not; | |
69 | ||
70 | return match; | |
71 | } | |
72 | ||
73 | static int filter_pred_string(struct filter_pred *pred, void *event) | |
74 | { | |
75 | char *addr = (char *)(event + pred->offset); | |
76 | int cmp, match; | |
77 | ||
78 | cmp = strncmp(addr, pred->str_val, pred->str_len); | |
79 | ||
80 | match = (!cmp) ^ pred->not; | |
81 | ||
82 | return match; | |
83 | } | |
84 | ||
85 | /* return 1 if event matches, 0 otherwise (discard) */ | |
86 | int filter_match_preds(struct ftrace_event_call *call, void *rec) | |
87 | { | |
88 | int i, matched, and_failed = 0; | |
89 | struct filter_pred *pred; | |
90 | ||
91 | for (i = 0; i < MAX_FILTER_PRED; i++) { | |
92 | if (call->preds[i]) { | |
93 | pred = call->preds[i]; | |
94 | if (and_failed && !pred->or) | |
95 | continue; | |
96 | matched = pred->fn(pred, rec); | |
97 | if (!matched && !pred->or) { | |
98 | and_failed = 1; | |
99 | continue; | |
100 | } else if (matched && pred->or) | |
101 | return 1; | |
102 | } else | |
103 | break; | |
104 | } | |
105 | ||
106 | if (and_failed) | |
107 | return 0; | |
108 | ||
109 | return 1; | |
110 | } | |
111 | ||
4bda2d51 | 112 | void filter_print_preds(struct filter_pred **preds, struct trace_seq *s) |
7ce7e424 | 113 | { |
7ce7e424 TZ |
114 | char *field_name; |
115 | struct filter_pred *pred; | |
116 | int i; | |
117 | ||
118 | if (!preds) { | |
4bda2d51 TZ |
119 | trace_seq_printf(s, "none\n"); |
120 | return; | |
7ce7e424 TZ |
121 | } |
122 | ||
123 | for (i = 0; i < MAX_FILTER_PRED; i++) { | |
124 | if (preds[i]) { | |
125 | pred = preds[i]; | |
126 | field_name = pred->field_name; | |
127 | if (i) | |
4bda2d51 TZ |
128 | trace_seq_printf(s, pred->or ? "|| " : "&& "); |
129 | trace_seq_printf(s, "%s ", field_name); | |
130 | trace_seq_printf(s, pred->not ? "!= " : "== "); | |
7ce7e424 | 131 | if (pred->str_val) |
4bda2d51 | 132 | trace_seq_printf(s, "%s\n", pred->str_val); |
7ce7e424 | 133 | else |
4bda2d51 | 134 | trace_seq_printf(s, "%llu\n", pred->val); |
7ce7e424 TZ |
135 | } else |
136 | break; | |
137 | } | |
7ce7e424 TZ |
138 | } |
139 | ||
140 | static struct ftrace_event_field * | |
141 | find_event_field(struct ftrace_event_call *call, char *name) | |
142 | { | |
1fc2d5c1 | 143 | struct ftrace_event_field *field; |
7ce7e424 | 144 | |
1fc2d5c1 | 145 | list_for_each_entry(field, &call->fields, link) { |
7ce7e424 TZ |
146 | if (!strcmp(field->name, name)) |
147 | return field; | |
148 | } | |
149 | ||
150 | return NULL; | |
151 | } | |
152 | ||
153 | void filter_free_pred(struct filter_pred *pred) | |
154 | { | |
155 | if (!pred) | |
156 | return; | |
157 | ||
158 | kfree(pred->field_name); | |
159 | kfree(pred->str_val); | |
160 | kfree(pred); | |
161 | } | |
162 | ||
163 | void filter_free_preds(struct ftrace_event_call *call) | |
164 | { | |
165 | int i; | |
166 | ||
167 | if (call->preds) { | |
168 | for (i = 0; i < MAX_FILTER_PRED; i++) | |
169 | filter_free_pred(call->preds[i]); | |
170 | kfree(call->preds); | |
171 | call->preds = NULL; | |
172 | } | |
173 | } | |
174 | ||
cfb180f3 TZ |
175 | void filter_free_subsystem_preds(struct event_subsystem *system) |
176 | { | |
177 | struct ftrace_event_call *call = __start_ftrace_events; | |
178 | int i; | |
179 | ||
180 | if (system->preds) { | |
181 | for (i = 0; i < MAX_FILTER_PRED; i++) | |
182 | filter_free_pred(system->preds[i]); | |
183 | kfree(system->preds); | |
184 | system->preds = NULL; | |
185 | } | |
186 | ||
187 | events_for_each(call) { | |
188 | if (!call->name || !call->regfunc) | |
189 | continue; | |
190 | ||
191 | if (!strcmp(call->system, system->name)) | |
192 | filter_free_preds(call); | |
193 | } | |
194 | } | |
195 | ||
7ce7e424 TZ |
196 | static int __filter_add_pred(struct ftrace_event_call *call, |
197 | struct filter_pred *pred) | |
198 | { | |
199 | int i; | |
200 | ||
201 | if (call->preds && !pred->compound) | |
202 | filter_free_preds(call); | |
203 | ||
204 | if (!call->preds) { | |
205 | call->preds = kzalloc(MAX_FILTER_PRED * sizeof(pred), | |
206 | GFP_KERNEL); | |
207 | if (!call->preds) | |
208 | return -ENOMEM; | |
209 | } | |
210 | ||
211 | for (i = 0; i < MAX_FILTER_PRED; i++) { | |
212 | if (!call->preds[i]) { | |
213 | call->preds[i] = pred; | |
214 | return 0; | |
215 | } | |
216 | } | |
217 | ||
44e9c8b7 | 218 | return -ENOSPC; |
7ce7e424 TZ |
219 | } |
220 | ||
221 | static int is_string_field(const char *type) | |
222 | { | |
223 | if (strchr(type, '[') && strstr(type, "char")) | |
224 | return 1; | |
225 | ||
226 | return 0; | |
227 | } | |
228 | ||
229 | int filter_add_pred(struct ftrace_event_call *call, struct filter_pred *pred) | |
230 | { | |
231 | struct ftrace_event_field *field; | |
232 | ||
233 | field = find_event_field(call, pred->field_name); | |
234 | if (!field) | |
235 | return -EINVAL; | |
236 | ||
237 | pred->offset = field->offset; | |
238 | ||
239 | if (is_string_field(field->type)) { | |
9f58a159 TZ |
240 | if (!pred->str_val) |
241 | return -EINVAL; | |
7ce7e424 TZ |
242 | pred->fn = filter_pred_string; |
243 | pred->str_len = field->size; | |
244 | return __filter_add_pred(call, pred); | |
9f58a159 TZ |
245 | } else { |
246 | if (pred->str_val) | |
247 | return -EINVAL; | |
7ce7e424 TZ |
248 | } |
249 | ||
250 | switch (field->size) { | |
251 | case 8: | |
252 | pred->fn = filter_pred_64; | |
253 | break; | |
254 | case 4: | |
255 | pred->fn = filter_pred_32; | |
256 | break; | |
257 | case 2: | |
258 | pred->fn = filter_pred_16; | |
259 | break; | |
260 | case 1: | |
261 | pred->fn = filter_pred_8; | |
262 | break; | |
263 | default: | |
264 | return -EINVAL; | |
265 | } | |
266 | ||
267 | return __filter_add_pred(call, pred); | |
268 | } | |
269 | ||
cfb180f3 TZ |
270 | static struct filter_pred *copy_pred(struct filter_pred *pred) |
271 | { | |
272 | struct filter_pred *new_pred = kmalloc(sizeof(*pred), GFP_KERNEL); | |
273 | if (!new_pred) | |
274 | return NULL; | |
275 | ||
276 | memcpy(new_pred, pred, sizeof(*pred)); | |
ee6cdabc TZ |
277 | |
278 | if (pred->field_name) { | |
279 | new_pred->field_name = kstrdup(pred->field_name, GFP_KERNEL); | |
280 | if (!new_pred->field_name) { | |
281 | kfree(new_pred); | |
282 | return NULL; | |
283 | } | |
284 | } | |
285 | ||
cfb180f3 TZ |
286 | if (pred->str_val) { |
287 | new_pred->str_val = kstrdup(pred->str_val, GFP_KERNEL); | |
cfb180f3 | 288 | if (!new_pred->str_val) { |
ee6cdabc | 289 | filter_free_pred(new_pred); |
cfb180f3 TZ |
290 | return NULL; |
291 | } | |
292 | } | |
293 | ||
294 | return new_pred; | |
295 | } | |
296 | ||
297 | int filter_add_subsystem_pred(struct event_subsystem *system, | |
298 | struct filter_pred *pred) | |
299 | { | |
300 | struct ftrace_event_call *call = __start_ftrace_events; | |
301 | struct filter_pred *event_pred; | |
302 | int i; | |
303 | ||
304 | if (system->preds && !pred->compound) | |
305 | filter_free_subsystem_preds(system); | |
306 | ||
307 | if (!system->preds) { | |
308 | system->preds = kzalloc(MAX_FILTER_PRED * sizeof(pred), | |
309 | GFP_KERNEL); | |
310 | if (!system->preds) | |
311 | return -ENOMEM; | |
312 | } | |
313 | ||
314 | for (i = 0; i < MAX_FILTER_PRED; i++) { | |
315 | if (!system->preds[i]) { | |
316 | system->preds[i] = pred; | |
317 | break; | |
318 | } | |
cfb180f3 TZ |
319 | } |
320 | ||
c4cff064 | 321 | if (i == MAX_FILTER_PRED) |
44e9c8b7 | 322 | return -ENOSPC; |
c4cff064 | 323 | |
cfb180f3 | 324 | events_for_each(call) { |
c4cff064 TZ |
325 | int err; |
326 | ||
cfb180f3 TZ |
327 | if (!call->name || !call->regfunc) |
328 | continue; | |
329 | ||
c4cff064 TZ |
330 | if (strcmp(call->system, system->name)) |
331 | continue; | |
332 | ||
333 | if (!find_event_field(call, pred->field_name)) | |
334 | continue; | |
335 | ||
336 | event_pred = copy_pred(pred); | |
337 | if (!event_pred) | |
338 | goto oom; | |
339 | ||
340 | err = filter_add_pred(call, event_pred); | |
341 | if (err) | |
342 | filter_free_pred(event_pred); | |
343 | if (err == -ENOMEM) | |
344 | goto oom; | |
cfb180f3 TZ |
345 | } |
346 | ||
347 | return 0; | |
c4cff064 TZ |
348 | |
349 | oom: | |
350 | system->preds[i] = NULL; | |
351 | return -ENOMEM; | |
cfb180f3 TZ |
352 | } |
353 | ||
7ce7e424 TZ |
354 | int filter_parse(char **pbuf, struct filter_pred *pred) |
355 | { | |
356 | char *tmp, *tok, *val_str = NULL; | |
357 | int tok_n = 0; | |
358 | ||
359 | /* field ==/!= number, or/and field ==/!= number, number */ | |
360 | while ((tok = strsep(pbuf, " \n"))) { | |
361 | if (tok_n == 0) { | |
362 | if (!strcmp(tok, "0")) { | |
363 | pred->clear = 1; | |
364 | return 0; | |
365 | } else if (!strcmp(tok, "&&")) { | |
366 | pred->or = 0; | |
367 | pred->compound = 1; | |
368 | } else if (!strcmp(tok, "||")) { | |
369 | pred->or = 1; | |
370 | pred->compound = 1; | |
371 | } else | |
372 | pred->field_name = tok; | |
373 | tok_n = 1; | |
374 | continue; | |
375 | } | |
376 | if (tok_n == 1) { | |
377 | if (!pred->field_name) | |
378 | pred->field_name = tok; | |
379 | else if (!strcmp(tok, "!=")) | |
380 | pred->not = 1; | |
381 | else if (!strcmp(tok, "==")) | |
382 | pred->not = 0; | |
383 | else { | |
384 | pred->field_name = NULL; | |
385 | return -EINVAL; | |
386 | } | |
387 | tok_n = 2; | |
388 | continue; | |
389 | } | |
390 | if (tok_n == 2) { | |
391 | if (pred->compound) { | |
392 | if (!strcmp(tok, "!=")) | |
393 | pred->not = 1; | |
394 | else if (!strcmp(tok, "==")) | |
395 | pred->not = 0; | |
396 | else { | |
397 | pred->field_name = NULL; | |
398 | return -EINVAL; | |
399 | } | |
400 | } else { | |
401 | val_str = tok; | |
402 | break; /* done */ | |
403 | } | |
404 | tok_n = 3; | |
405 | continue; | |
406 | } | |
407 | if (tok_n == 3) { | |
408 | val_str = tok; | |
409 | break; /* done */ | |
410 | } | |
411 | } | |
412 | ||
bcabd91c LZ |
413 | if (!val_str) { |
414 | pred->field_name = NULL; | |
415 | return -EINVAL; | |
416 | } | |
417 | ||
7ce7e424 TZ |
418 | pred->field_name = kstrdup(pred->field_name, GFP_KERNEL); |
419 | if (!pred->field_name) | |
420 | return -ENOMEM; | |
421 | ||
a3e0ab05 | 422 | pred->val = simple_strtoull(val_str, &tmp, 0); |
7ce7e424 TZ |
423 | if (tmp == val_str) { |
424 | pred->str_val = kstrdup(val_str, GFP_KERNEL); | |
425 | if (!pred->str_val) | |
426 | return -ENOMEM; | |
a3e0ab05 LZ |
427 | } else if (*tmp != '\0') |
428 | return -EINVAL; | |
7ce7e424 TZ |
429 | |
430 | return 0; | |
431 | } | |
432 | ||
433 |