cregit-Linux how code gets into the kernel

Release 4.15 kernel/trace/trace_events_filter.c

Directory: kernel/trace
/*
 * trace_events_filter - generic event filtering
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * Copyright (C) 2009 Tom Zanussi <tzanussi@gmail.com>
 */

#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/mutex.h>
#include <linux/perf_event.h>
#include <linux/slab.h>

#include "trace.h"
#include "trace_output.h"


#define DEFAULT_SYS_FILTER_MESSAGE					\
	"### global filter ###\n"                                       \
        "# Use this to set filters for multiple events.\n"              \
        "# Only events with the given fields will be affected.\n"       \
        "# If no events are modified, an error message will be displayed here"


enum filter_op_ids
{
	
OP_OR,
	
OP_AND,
	
OP_GLOB,
	
OP_NE,
	
OP_EQ,
	
OP_LT,
	
OP_LE,
	
OP_GT,
	
OP_GE,
	
OP_BAND,
	
OP_NOT,
	
OP_NONE,
	
OP_OPEN_PAREN,
};


struct filter_op {
	
int id;
	
char *string;
	
int precedence;
};

/* Order must be the same as enum filter_op_ids above */

static struct filter_op filter_ops[] = {
	{ OP_OR,	"||",		1 },
	{ OP_AND,	"&&",		2 },
	{ OP_GLOB,	"~",		4 },
	{ OP_NE,	"!=",		4 },
	{ OP_EQ,	"==",		4 },
	{ OP_LT,	"<",		5 },
	{ OP_LE,	"<=",		5 },
	{ OP_GT,	">",		5 },
	{ OP_GE,	">=",		5 },
	{ OP_BAND,	"&",		6 },
	{ OP_NOT,	"!",		6 },
	{ OP_NONE,	"OP_NONE",	0 },
	{ OP_OPEN_PAREN, "(",		0 },
};

enum {
	
FILT_ERR_NONE,
	
FILT_ERR_INVALID_OP,
	
FILT_ERR_UNBALANCED_PAREN,
	
FILT_ERR_TOO_MANY_OPERANDS,
	
FILT_ERR_OPERAND_TOO_LONG,
	
FILT_ERR_FIELD_NOT_FOUND,
	
FILT_ERR_ILLEGAL_FIELD_OP,
	
FILT_ERR_ILLEGAL_INTVAL,
	
FILT_ERR_BAD_SUBSYS_FILTER,
	
FILT_ERR_TOO_MANY_PREDS,
	
FILT_ERR_MISSING_FIELD,
	
FILT_ERR_INVALID_FILTER,
	
FILT_ERR_IP_FIELD_ONLY,
	
FILT_ERR_ILLEGAL_NOT_OP,
};


static char *err_text[] = {
	"No error",
	"Invalid operator",
	"Unbalanced parens",
	"Too many operands",
	"Operand too long",
	"Field not found",
	"Illegal operation for field type",
	"Illegal integer value",
	"Couldn't find or set field in one of a subsystem's events",
	"Too many terms in predicate expression",
	"Missing field name and/or value",
	"Meaningless filter expression",
	"Only 'ip' field is supported for function trace",
	"Illegal use of '!'",
};


struct opstack_op {
	
enum filter_op_ids op;
	
struct list_head list;
};


struct postfix_elt {
	
enum filter_op_ids op;
	
char *operand;
	
struct list_head list;
};


struct filter_parse_state {
	
struct filter_op *ops;
	
struct list_head opstack;
	
struct list_head postfix;
	
int lasterr;
	
int lasterr_pos;

	struct {
		
char *string;
		
unsigned int cnt;
		
unsigned int tail;
	
} infix;

	struct {
		
char string[MAX_FILTER_STR_VAL];
		
int pos;
		
unsigned int tail;
	
} operand;
};


struct pred_stack {
	
struct filter_pred	**preds;
	
int			index;
};

/* If not of not match is equal to not of not, then it is a match */

#define DEFINE_COMPARISON_PRED(type)					\
static int filter_pred_LT_##type(struct filter_pred *pred, void *event) \
{                                                                       \
        type *addr = (type *)(event + pred->offset);                    \
        type val = (type)pred->val;                                     \
        int match = (*addr < val);                                      \
        return !!match == !pred->not;                                   \
}                                                                       \
static int filter_pred_LE_##type(struct filter_pred *pred, void *event) \
{                                                                       \
        type *addr = (type *)(event + pred->offset);                    \
        type val = (type)pred->val;                                     \
        int match = (*addr <= val);                                     \
        return !!match == !pred->not;                                   \
}                                                                       \
static int filter_pred_GT_##type(struct filter_pred *pred, void *event) \
{                                                                       \
        type *addr = (type *)(event + pred->offset);                    \
        type val = (type)pred->val;                                     \
        int match = (*addr > val);                                      \
        return !!match == !pred->not;                                   \
}                                                                       \
static int filter_pred_GE_##type(struct filter_pred *pred, void *event) \
{                                                                       \
        type *addr = (type *)(event + pred->offset);                    \
        type val = (type)pred->val;                                     \
        int match = (*addr >= val);                                     \
        return !!match == !pred->not;                                   \
}                                                                       \
static int filter_pred_BAND_##type(struct filter_pred *pred, void *event) \
{                                                                       \
        type *addr = (type *)(event + pred->offset);                    \
        type val = (type)pred->val;                                     \
        int match = !!(*addr & val);                                    \
        return match == !pred->not;                                     \
}                                                                       \
static const filter_pred_fn_t pred_funcs_##type[] = {                   \
        filter_pred_LT_##type,                                          \
        filter_pred_LE_##type,                                          \
        filter_pred_GT_##type,                                          \
        filter_pred_GE_##type,                                          \
        filter_pred_BAND_##type,                                        \
};


#define PRED_FUNC_START			OP_LT


#define DEFINE_EQUALITY_PRED(size)					\
static int filter_pred_##size(struct filter_pred *pred, void *event)    \
{                                                                       \
        u##size *addr = (u##size *)(event + pred->offset);              \
        u##size val = (u##size)pred->val;                               \
        int match;                                                      \
                                                                        \
        match = (val == *addr) ^ pred->not;                             \
                                                                        \
        return match;                                                   \
}


DEFINE_COMPARISON_PRED(s64);

DEFINE_COMPARISON_PRED(u64);

DEFINE_COMPARISON_PRED(s32);

DEFINE_COMPARISON_PRED(u32);

DEFINE_COMPARISON_PRED(s16);

DEFINE_COMPARISON_PRED(u16);

DEFINE_COMPARISON_PRED(s8);

DEFINE_COMPARISON_PRED(u8);

DEFINE_EQUALITY_PRED(64);
DEFINE_EQUALITY_PRED(32);
DEFINE_EQUALITY_PRED(16);
DEFINE_EQUALITY_PRED(8);

/* Filter predicate for fixed sized arrays of characters */

static int filter_pred_string(struct filter_pred *pred, void *event) { char *addr = (char *)(event + pred->offset); int cmp, match; cmp = pred->regex.match(addr, &pred->regex, pred->regex.field_len); match = cmp ^ pred->not; return match; }

Contributors

PersonTokensPropCommitsCommitProp
Tom Zanussi5985.51%150.00%
Frédéric Weisbecker1014.49%150.00%
Total69100.00%2100.00%

/* Filter predicate for char * pointers */
static int filter_pred_pchar(struct filter_pred *pred, void *event) { char **addr = (char **)(event + pred->offset); int cmp, match; int len = strlen(*addr) + 1; /* including tailing '\0' */ cmp = pred->regex.match(*addr, &pred->regex, len); match = cmp ^ pred->not; return match; }

Contributors

PersonTokensPropCommitsCommitProp
Li Zefan7391.25%266.67%
Frédéric Weisbecker78.75%133.33%
Total80100.00%3100.00%

/* * Filter predicate for dynamic sized arrays of characters. * These are implemented through a list of strings at the end * of the entry. * Also each of these strings have a field in the entry which * contains its offset from the beginning of the entry. * We have then first to get this field, dereference it * and add it to the address of the entry, and at last we have * the address of the string. */
static int filter_pred_strloc(struct filter_pred *pred, void *event) { u32 str_item = *(u32 *)(event + pred->offset); int str_loc = str_item & 0xffff; int str_len = str_item >> 16; char *addr = (char *)(event + str_loc); int cmp, match; cmp = pred->regex.match(addr, &pred->regex, str_len); match = cmp ^ pred->not; return match; }

Contributors

PersonTokensPropCommitsCommitProp
Frédéric Weisbecker7681.72%266.67%
Li Zefan1718.28%133.33%
Total93100.00%3100.00%

/* Filter predicate for CPUs. */
static int filter_pred_cpu(struct filter_pred *pred, void *event) { int cpu, cmp; int match = 0; cpu = raw_smp_processor_id(); cmp = pred->val; switch (pred->op) { case OP_EQ: match = cpu == cmp; break; case OP_LT: match = cpu < cmp; break; case OP_LE: match = cpu <= cmp; break; case OP_GT: match = cpu > cmp; break; case OP_GE: match = cpu >= cmp; break; default: break; } return !!match == !pred->not; }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Wagner106100.00%1100.00%
Total106100.00%1100.00%

/* Filter predicate for COMM. */
static int filter_pred_comm(struct filter_pred *pred, void *event) { int cmp, match; cmp = pred->regex.match(current->comm, &pred->regex, pred->regex.field_len); match = cmp ^ pred->not; return match; }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Wagner55100.00%1100.00%
Total55100.00%1100.00%


static int filter_pred_none(struct filter_pred *pred, void *event) { return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Tom Zanussi18100.00%1100.00%
Total18100.00%1100.00%

/* * regex_match_foo - Basic regex callbacks * * @str: the string to be searched * @r: the regex structure containing the pattern string * @len: the length of the string to be searched (including '\0') * * Note: * - @str might not be NULL-terminated if it's of type DYN_STRING * or STATIC_STRING */
static int regex_match_full(char *str, struct regex *r, int len) { if (strncmp(str, r->pattern, len) == 0) return 1; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Frédéric Weisbecker39100.00%1100.00%
Total39100.00%1100.00%


static int regex_match_front(char *str, struct regex *r, int len) { if (strncmp(str, r->pattern, r->len) == 0) return 1; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Frédéric Weisbecker3995.12%150.00%
Li Zefan24.88%150.00%
Total41100.00%2100.00%


static int regex_match_middle(char *str, struct regex *r, int len) { if (strnstr(str, r->pattern, len)) return 1; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Frédéric Weisbecker3491.89%150.00%
Li Zefan38.11%150.00%
Total37100.00%2100.00%


static int regex_match_end(char *str, struct regex *r, int len) { int strlen = len - 1; if (strlen >= r->len && memcmp(str + strlen - r->len, r->pattern, r->len) == 0) return 1; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Frédéric Weisbecker3761.67%150.00%
Li Zefan2338.33%150.00%
Total60100.00%2100.00%


static int regex_match_glob(char *str, struct regex *r, int len __maybe_unused) { if (glob_match(r->pattern, str)) return 1; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Masami Hiramatsu36100.00%1100.00%
Total36100.00%1100.00%

/** * filter_parse_regex - parse a basic regex * @buff: the raw regex * @len: length of the regex * @search: will point to the beginning of the string to compare * @not: tell whether the match will have to be inverted * * This passes in a buffer containing a regex and this function will * set search to point to the search part of the buffer and * return the type of search it is (see enum above). * This does modify buff. * * Returns enum type. * search returns the pointer to use for comparison. * not returns 1 if buff started with a '!' * 0 otherwise. */
enum regex_type filter_parse_regex(char *buff, int len, char **search, int *not) { int type = MATCH_FULL; int i; if (buff[0] == '!') { *not = 1; buff++; len--; } else *not = 0; *search = buff; for (i = 0; i < len; i++) { if (buff[i] == '*') { if (!i) { *search = buff + 1; type = MATCH_END_ONLY; } else if (i == len - 1) { if (type == MATCH_END_ONLY) type = MATCH_MIDDLE_ONLY; else type = MATCH_FRONT_ONLY; buff[i] = 0; break; } else { /* pattern continues, use full glob */ type = MATCH_GLOB; break; } } else if (strchr("[?\\", buff[i])) { type = MATCH_GLOB; break; } } return type; }

Contributors

PersonTokensPropCommitsCommitProp
Frédéric Weisbecker13578.03%150.00%
Masami Hiramatsu3821.97%150.00%
Total173100.00%2100.00%


static void filter_build_regex(struct filter_pred *pred) { struct regex *r = &pred->regex; char *search; enum regex_type type = MATCH_FULL; int not = 0; if (pred->op == OP_GLOB) { type = filter_parse_regex(r->pattern, r->len, &search, &not); r->len = strlen(search); memmove(r->pattern, search, r->len+1); } switch (type) { case MATCH_FULL: r->match = regex_match_full; break; case MATCH_FRONT_ONLY: r->match = regex_match_front; break; case MATCH_MIDDLE_ONLY: r->match = regex_match_middle; break; case MATCH_END_ONLY: r->match = regex_match_end; break; case MATCH_GLOB: r->match = regex_match_glob; break; } pred->not ^= not; }

Contributors

PersonTokensPropCommitsCommitProp
Frédéric Weisbecker11777.48%133.33%
Li Zefan2415.89%133.33%
Masami Hiramatsu106.62%133.33%
Total151100.00%3100.00%

enum move_type { MOVE_DOWN, MOVE_UP_FROM_LEFT, MOVE_UP_FROM_RIGHT };
static struct filter_pred * get_pred_parent(struct filter_pred *pred, struct filter_pred *preds, int index, enum move_type *move) { if (pred->parent & FILTER_PRED_IS_RIGHT) *move = MOVE_UP_FROM_RIGHT; else *move = MOVE_UP_FROM_LEFT; pred = &preds[pred->parent & ~FILTER_PRED_IS_RIGHT]; return pred; }

Contributors

PersonTokensPropCommitsCommitProp
Steven Rostedt61100.00%1100.00%
Total61100.00%1100.00%

enum walk_return { WALK_PRED_ABORT, WALK_PRED_PARENT, WALK_PRED_DEFAULT, }; typedef int (*filter_pred_walkcb_t) (enum move_type move, struct filter_pred *pred, int *err, void *data);
static int walk_pred_tree(struct filter_pred *preds, struct filter_pred *root, filter_pred_walkcb_t cb, void *data) { struct filter_pred *pred = root; enum move_type move = MOVE_DOWN; int done = 0; if (!preds) return -EINVAL; do { int err = 0, ret; ret = cb(move, pred, &err, data); if (ret == WALK_PRED_ABORT) return err; if (ret == WALK_PRED_PARENT) goto get_parent; switch (move) { case MOVE_DOWN: if (pred->left != FILTER_PRED_INVALID) { pred = &preds[pred->left]; continue; } goto get_parent; case MOVE_UP_FROM_LEFT: pred = &preds[pred->right]; move = MOVE_DOWN; continue; case MOVE_UP_FROM_RIGHT: get_parent: if (pred == root) break; pred = get_pred_parent(pred, preds, pred->parent, &move); continue; } done = 1; } while (!done); /* We are fine. */ return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Jiri Olsa186100.00%1100.00%
Total186100.00%1100.00%

/* * A series of AND or ORs where found together. Instead of * climbing up and down the tree branches, an array of the * ops were made in order of checks. We can just move across * the array and short circuit if needed. */
static int process_ops(struct filter_pred *preds, struct filter_pred *op, void *rec) { struct filter_pred *pred; int match = 0; int type; int i; /* * Micro-optimization: We set type to true if op * is an OR and false otherwise (AND). Then we * just need to test if the match is equal to * the type, and if it is, we can short circuit the * rest of the checks: * * if ((match && op->op == OP_OR) || * (!match && op->op == OP_AND)) * return match; */ type = op->op == OP_OR; for (i = 0; i < op->val; i++) { pred = &preds[op->ops[i]]; if (!WARN_ON_ONCE(!pred->fn)) match = pred->fn(pred, rec); if (!!match == type) break; } /* If not of not match is equal to not of not, then it is a match */ return !!match == !op->not; }

Contributors

PersonTokensPropCommitsCommitProp
Steven Rostedt10287.18%250.00%
Jiri Olsa119.40%125.00%
Ingo Molnar43.42%125.00%
Total117100.00%4100.00%

struct filter_match_preds_data { struct filter_pred *preds; int match; void *rec; };
static int filter_match_preds_cb(enum move_type move, struct filter_pred *pred, int *err, void *data) { struct filter_match_preds_data *d = data; *err = 0; switch (move) { case MOVE_DOWN: /* only AND and OR have children */ if (pred->left != FILTER_PRED_INVALID) { /* If ops is set, then it was folded. */ if (!pred->ops) return WALK_PRED_DEFAULT; /* We can treat folded ops as a leaf node */ d->match = process_ops(d->preds, pred, d->rec); } else { if (!WARN_ON_ONCE(!pred->fn)) d->match = pred->fn(pred, d->rec); } return WALK_PRED_PARENT; case MOVE_UP_FROM_LEFT: /* * Check for short circuits. * * Optimization: !!match == (pred->op == OP_OR) * is the same as: * if ((match && pred->op == OP_OR) || * (!match && pred->op == OP_AND)) */ if (!!d->match == (pred->op == OP_OR)) return WALK_PRED_PARENT; break; case MOVE_UP_FROM_RIGHT: break; } return WALK_PRED_DEFAULT; }

Contributors

PersonTokensPropCommitsCommitProp
Jiri Olsa6544.22%116.67%
Steven Rostedt6544.22%466.67%
Tom Zanussi1711.56%116.67%
Total147100.00%6100.00%

/* return 1 if event matches, 0 otherwise (discard) */
int filter_match_preds(struct event_filter *filter, void *rec) { struct filter_pred *preds; struct filter_pred *root; struct filter_match_preds_data data = { /* match is currently meaningless */ .match = -1, .rec = rec, }; int n_preds, ret; /* no filter is considered a match */ if (!filter) return 1; n_preds = filter->n_preds; if (!n_preds) return 1; /* * n_preds, root and filter->preds are protect with preemption disabled. */ root = rcu_dereference_sched(filter->root); if (!root) return 1; data.preds = preds = rcu_dereference_sched(filter->preds); ret = walk_pred_tree(preds, root, filter_match_preds_cb, &data); WARN_ON(ret); return data.match; }

Contributors

PersonTokensPropCommitsCommitProp
Jiri Olsa9878.40%125.00%
Steven Rostedt1713.60%250.00%
Tom Zanussi108.00%125.00%
Total125100.00%4100.00%

EXPORT_SYMBOL_GPL(filter_match_preds);
static void parse_error(struct filter_parse_state *ps, int err, int pos) { ps->lasterr = err; ps->lasterr_pos = pos; }

Contributors

PersonTokensPropCommitsCommitProp
Tom Zanussi29100.00%1100.00%
Total29100.00%1100.00%


static void remove_filter_string(struct event_filter *filter) { if (!filter) return; kfree(filter->filter_string); filter->filter_string = NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Tom Zanussi2480.00%150.00%
Steven Rostedt620.00%150.00%
Total30100.00%2100.00%


static int replace_filter_string(struct event_filter *filter, char *filter_string) { kfree(filter->filter_string); filter->filter_string = kstrdup(filter_string, GFP_KERNEL); if (!filter->filter_string) return -ENOMEM; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Tom Zanussi47100.00%1100.00%
Total47100.00%1100.00%


static int append_filter_string(struct event_filter *filter, char *string) { int newlen; char *new_filter_string; BUG_ON(!filter->filter_string); newlen = strlen(filter->filter_string) + strlen(string) + 1; new_filter_string = kmalloc(newlen, GFP_KERNEL); if (!new_filter_string) return -ENOMEM; strcpy(new_filter_string, filter->filter_string); strcat(new_filter_string, string); kfree(filter->filter_string); filter->filter_string = new_filter_string; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Tom Zanussi96100.00%1100.00%
Total96100.00%1100.00%


static void append_filter_err(struct filter_parse_state *ps, struct event_filter *filter) { int pos = ps->lasterr_pos; char *buf, *pbuf; buf = (char *)__get_free_page(GFP_KERNEL); if (!buf) return; append_filter_string(filter, "\n"); memset(buf, ' ', PAGE_SIZE); if (pos > PAGE_SIZE - 128) pos = 0; buf[pos] = '^'; pbuf = &buf[pos] + 1; sprintf(pbuf, "\nparse_error: %s\n", err_text[ps->lasterr]); append_filter_string(filter, buf); free_page((unsigned long) buf); }

Contributors

PersonTokensPropCommitsCommitProp
Tom Zanussi12199.18%150.00%
Michal Hocko10.82%150.00%
Total122100.00%2100.00%


static inline struct event_filter *event_filter(struct trace_event_file *file) { return file->filter; }

Contributors

PersonTokensPropCommitsCommitProp
Tom Zanussi1894.74%150.00%
Steven Rostedt15.26%150.00%
Total19100.00%2100.00%

/* caller must hold event_mutex */
void print_event_filter(struct trace_event_file *file, struct trace_seq *s) { struct event_filter *filter = event_filter(file); if (filter && filter->filter_string) trace_seq_printf(s, "%s\n", filter->filter_string); else trace_seq_puts(s, "none\n"); }

Contributors

PersonTokensPropCommitsCommitProp
Tom Zanussi4688.46%233.33%
Oleg Nesterov23.85%116.67%
Li Zefan23.85%116.67%
Jovi Zhangwei11.92%116.67%
Steven Rostedt11.92%116.67%
Total52100.00%6100.00%


void print_subsystem_event_filter(struct event_subsystem *system, struct trace_seq *s) { struct event_filter *filter; mutex_lock(&event_mutex); filter = system->filter; if (filter && filter->filter_string) trace_seq_printf(s, "%s\n", filter->filter_string); else trace_seq_puts(s, DEFAULT_SYS_FILTER_MESSAGE "\n"); mutex_unlock(&event_mutex); }

Contributors

PersonTokensPropCommitsCommitProp
Tom Zanussi5380.30%116.67%
Steven Rostedt812.12%233.33%
Li Zefan46.06%233.33%
Jovi Zhangwei11.52%116.67%
Total66100.00%6100.00%


static int __alloc_pred_stack(struct pred_stack *stack, int n_preds) { stack->preds = kcalloc(n_preds + 1, sizeof(*stack->preds), GFP_KERNEL); if (!stack->preds) return -ENOMEM; stack->index = n_preds; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Steven Rostedt5090.91%150.00%
Thomas Meyer59.09%150.00%
Total55100.00%2100.00%


static void __free_pred_stack(struct pred_stack *stack) { kfree(stack->preds); stack->index = 0; }

Contributors

PersonTokensPropCommitsCommitProp
Steven Rostedt24100.00%1100.00%
Total24100.00%1100.00%


static int __push_pred_stack(struct pred_stack *stack, struct filter_pred *pred) { int index = stack->index; if (WARN_ON(index == 0)) return -ENOSPC; stack