|
@@ -81,6 +81,7 @@ enum {
|
|
|
FILT_ERR_TOO_MANY_PREDS,
|
|
|
FILT_ERR_MISSING_FIELD,
|
|
|
FILT_ERR_INVALID_FILTER,
|
|
|
+ FILT_ERR_IP_FIELD_ONLY,
|
|
|
};
|
|
|
|
|
|
static char *err_text[] = {
|
|
@@ -96,6 +97,7 @@ static char *err_text[] = {
|
|
|
"Too many terms in predicate expression",
|
|
|
"Missing field name and/or value",
|
|
|
"Meaningless filter expression",
|
|
|
+ "Only 'ip' field is supported for function trace",
|
|
|
};
|
|
|
|
|
|
struct opstack_op {
|
|
@@ -991,7 +993,12 @@ static int init_pred(struct filter_parse_state *ps,
|
|
|
fn = filter_pred_strloc;
|
|
|
else
|
|
|
fn = filter_pred_pchar;
|
|
|
- } else if (!is_function_field(field)) {
|
|
|
+ } else if (is_function_field(field)) {
|
|
|
+ if (strcmp(field->name, "ip")) {
|
|
|
+ parse_error(ps, FILT_ERR_IP_FIELD_ONLY, 0);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
if (field->is_signed)
|
|
|
ret = strict_strtoll(pred->regex.pattern, 0, &val);
|
|
|
else
|
|
@@ -1338,10 +1345,7 @@ static struct filter_pred *create_pred(struct filter_parse_state *ps,
|
|
|
|
|
|
strcpy(pred.regex.pattern, operand2);
|
|
|
pred.regex.len = strlen(pred.regex.pattern);
|
|
|
-
|
|
|
-#ifdef CONFIG_FTRACE_STARTUP_TEST
|
|
|
pred.field = field;
|
|
|
-#endif
|
|
|
return init_pred(ps, field, &pred) ? NULL : &pred;
|
|
|
}
|
|
|
|
|
@@ -1954,6 +1958,148 @@ void ftrace_profile_free_filter(struct perf_event *event)
|
|
|
__free_filter(filter);
|
|
|
}
|
|
|
|
|
|
+struct function_filter_data {
|
|
|
+ struct ftrace_ops *ops;
|
|
|
+ int first_filter;
|
|
|
+ int first_notrace;
|
|
|
+};
|
|
|
+
|
|
|
+#ifdef CONFIG_FUNCTION_TRACER
|
|
|
+static char **
|
|
|
+ftrace_function_filter_re(char *buf, int len, int *count)
|
|
|
+{
|
|
|
+ char *str, *sep, **re;
|
|
|
+
|
|
|
+ str = kstrndup(buf, len, GFP_KERNEL);
|
|
|
+ if (!str)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The argv_split function takes white space
|
|
|
+ * as a separator, so convert ',' into spaces.
|
|
|
+ */
|
|
|
+ while ((sep = strchr(str, ',')))
|
|
|
+ *sep = ' ';
|
|
|
+
|
|
|
+ re = argv_split(GFP_KERNEL, str, count);
|
|
|
+ kfree(str);
|
|
|
+ return re;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftrace_function_set_regexp(struct ftrace_ops *ops, int filter,
|
|
|
+ int reset, char *re, int len)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (filter)
|
|
|
+ ret = ftrace_set_filter(ops, re, len, reset);
|
|
|
+ else
|
|
|
+ ret = ftrace_set_notrace(ops, re, len, reset);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int __ftrace_function_set_filter(int filter, char *buf, int len,
|
|
|
+ struct function_filter_data *data)
|
|
|
+{
|
|
|
+ int i, re_cnt, ret;
|
|
|
+ int *reset;
|
|
|
+ char **re;
|
|
|
+
|
|
|
+ reset = filter ? &data->first_filter : &data->first_notrace;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The 'ip' field could have multiple filters set, separated
|
|
|
+ * either by space or comma. We first cut the filter and apply
|
|
|
+ * all pieces separatelly.
|
|
|
+ */
|
|
|
+ re = ftrace_function_filter_re(buf, len, &re_cnt);
|
|
|
+ if (!re)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ for (i = 0; i < re_cnt; i++) {
|
|
|
+ ret = ftrace_function_set_regexp(data->ops, filter, *reset,
|
|
|
+ re[i], strlen(re[i]));
|
|
|
+ if (ret)
|
|
|
+ break;
|
|
|
+
|
|
|
+ if (*reset)
|
|
|
+ *reset = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ argv_free(re);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftrace_function_check_pred(struct filter_pred *pred, int leaf)
|
|
|
+{
|
|
|
+ struct ftrace_event_field *field = pred->field;
|
|
|
+
|
|
|
+ if (leaf) {
|
|
|
+ /*
|
|
|
+ * Check the leaf predicate for function trace, verify:
|
|
|
+ * - only '==' and '!=' is used
|
|
|
+ * - the 'ip' field is used
|
|
|
+ */
|
|
|
+ if ((pred->op != OP_EQ) && (pred->op != OP_NE))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (strcmp(field->name, "ip"))
|
|
|
+ return -EINVAL;
|
|
|
+ } else {
|
|
|
+ /*
|
|
|
+ * Check the non leaf predicate for function trace, verify:
|
|
|
+ * - only '||' is used
|
|
|
+ */
|
|
|
+ if (pred->op != OP_OR)
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftrace_function_set_filter_cb(enum move_type move,
|
|
|
+ struct filter_pred *pred,
|
|
|
+ int *err, void *data)
|
|
|
+{
|
|
|
+ /* Checking the node is valid for function trace. */
|
|
|
+ if ((move != MOVE_DOWN) ||
|
|
|
+ (pred->left != FILTER_PRED_INVALID)) {
|
|
|
+ *err = ftrace_function_check_pred(pred, 0);
|
|
|
+ } else {
|
|
|
+ *err = ftrace_function_check_pred(pred, 1);
|
|
|
+ if (*err)
|
|
|
+ return WALK_PRED_ABORT;
|
|
|
+
|
|
|
+ *err = __ftrace_function_set_filter(pred->op == OP_EQ,
|
|
|
+ pred->regex.pattern,
|
|
|
+ pred->regex.len,
|
|
|
+ data);
|
|
|
+ }
|
|
|
+
|
|
|
+ return (*err) ? WALK_PRED_ABORT : WALK_PRED_DEFAULT;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftrace_function_set_filter(struct perf_event *event,
|
|
|
+ struct event_filter *filter)
|
|
|
+{
|
|
|
+ struct function_filter_data data = {
|
|
|
+ .first_filter = 1,
|
|
|
+ .first_notrace = 1,
|
|
|
+ .ops = &event->ftrace_ops,
|
|
|
+ };
|
|
|
+
|
|
|
+ return walk_pred_tree(filter->preds, filter->root,
|
|
|
+ ftrace_function_set_filter_cb, &data);
|
|
|
+}
|
|
|
+#else
|
|
|
+static int ftrace_function_set_filter(struct perf_event *event,
|
|
|
+ struct event_filter *filter)
|
|
|
+{
|
|
|
+ return -ENODEV;
|
|
|
+}
|
|
|
+#endif /* CONFIG_FUNCTION_TRACER */
|
|
|
+
|
|
|
int ftrace_profile_set_filter(struct perf_event *event, int event_id,
|
|
|
char *filter_str)
|
|
|
{
|
|
@@ -1974,9 +2120,16 @@ int ftrace_profile_set_filter(struct perf_event *event, int event_id,
|
|
|
goto out_unlock;
|
|
|
|
|
|
err = create_filter(call, filter_str, false, &filter);
|
|
|
- if (!err)
|
|
|
- event->filter = filter;
|
|
|
+ if (err)
|
|
|
+ goto free_filter;
|
|
|
+
|
|
|
+ if (ftrace_event_is_function(call))
|
|
|
+ err = ftrace_function_set_filter(event, filter);
|
|
|
else
|
|
|
+ event->filter = filter;
|
|
|
+
|
|
|
+free_filter:
|
|
|
+ if (err || ftrace_event_is_function(call))
|
|
|
__free_filter(filter);
|
|
|
|
|
|
out_unlock:
|