2020-12-17 21:17:11

by Tom Zanussi

[permalink] [raw]
Subject: [PATCH v4 0/5] tracing: More synthetic event error fixes

Hi,

This is v4 of the sythetic event error fix patchset. As suggested by
Steve, I added a new pass that adds semicolons to 'old' commands that
may be missing them, in order to maintain backward compatibility. All
commands are handled by the new and improved parsing code, but
commands missing the semicolons have them added before processing and
are therefore still valid. At some point in the future, as new
features are added and we can require any command containing them to
require semicolons, this pass can be completely skipped by detecting
those features in the currently empty audit_old_buffer() hook.

Also, as a result, the patch adding semicolons to the selftests is no
longer necessary (selftests/ftrace: Add synthetic event field
separators) and has been dropped in this series.

Tom


v3 text:

Hi,

This is v3 of the sythetic event error fix patchset. As suggested by
Masami, I split the 'tracing/dynevent: Delegate parsing to create
function' into two - one containing just the interface changes and the
second with the synthetic event parsing changes the first enabled.

I also replaced a couple argv_split() with strpbrk() as suggested by
Masami, along with removing the no-longer-used consume lines and
another line that tested ECANCELED that was no longer needed.

Also, removed a test case that was no longer needed since the commands
are now stripped of whitespace first.

Thanks, Masami, for the suggestions.

Tom

v2 text:

This is v2 of the previous sythetic event error fix patchset.

This version drops the original ([PATCH 1/4] tracing: Make
trace_*_run_command() more flexible) and (tracing: Use new
trace_run_command() options) patches and replaces them with Masami's
patch (tracing/dynevent: Delegate parsing to create function) [1].
The new version adds in all the synthetic event changes needed to
compile and use the new interface.

A new patch was also added (selftests/ftrace: Add synthetic event
field separators) that fixes more invalid synthetic event syntax I
found while testing.

I also added some more new checks to the synthetic event sytax error
testcase.

As before, I didn't see any problems running the entire ftrace
testsuite or the test modules that also use the things that were
touched here.

[1] https://lore.kernel.org/lkml/[email protected]/

Thanks,

Tom


v1 text:

Hi,

This patchset addresses the synthetic event error anomalies reported
by Masami in the last patchset [1].

It turns out that most of the problems boil down to clunky separator
parsing; adding a couple new abilities to trace_run_command() and then
adapting the existing users seemed to me the best way to fix these
things, and also gets rid of some code.

Also, to make things easier for error display, I changed these to
preserve the original command string and pass it through the callback
instead of rebuilding it for error display.

I added some new error strings and removed unused ones as well, and
added a bunch of new test cases to the synthetic parser error test
case.

I didn't see any problems running the entire ftrace testsuite or the
test modules that also use the things that were touched here.

Thanks,

Tom

[1] https://lore.kernel.org/lkml/[email protected]/

The following changes since commit f6a694665f132cbf6e2222dd2f173dc35330a8aa:

tracing: Offload eval map updates to a work queue (2020-12-15 09:29:14 -0500)

are available in the Git repository at:

git://git.kernel.org/pub/scm/linux/kernel/git/zanussi/linux-trace.git ftrace/synth-fixes-v4

Masami Hiramatsu (1):
tracing/dynevent: Delegate parsing to create function

Tom Zanussi (4):
tracing: Rework synthetic event command parsing
tracing: Update synth command errors
tracing: Add a backward-compatibility check for synthetic event
creation
selftests/ftrace: Update synthetic event syntax errors

kernel/trace/trace.c | 23 +-
kernel/trace/trace.h | 3 +-
kernel/trace/trace_dynevent.c | 35 +-
kernel/trace/trace_dynevent.h | 4 +-
kernel/trace/trace_events_synth.c | 503 +++++++++++++++---
kernel/trace/trace_kprobe.c | 33 +-
kernel/trace/trace_probe.c | 17 +
kernel/trace/trace_probe.h | 1 +
kernel/trace/trace_uprobe.c | 17 +-
.../trigger-synthetic_event_syntax_errors.tc | 35 +-
10 files changed, 521 insertions(+), 150 deletions(-)

--
2.17.1


2020-12-17 21:17:29

by Tom Zanussi

[permalink] [raw]
Subject: [PATCH v4 4/5] tracing: Add a backward-compatibility check for synthetic event creation

The synthetic event parsing rework requiring semicolons between
synthetic event fields. That requirement breaks existing users who
might already have used the old form, so this adds a pre-parsing pass
that adds semicolons between fields to any string missing them. If
none are required, the original string is used.

In the future, if/when new features are added, the requirement will be
that any string containing the new feature will be required to use
semicolons, and the audit_old_buffer() check can check for those and
avoid the pre-parsing semicolon pass altogether.

As it stands, the pre-parsing pass creates a new string with
semicolons only if one or more semicolons were actually needed and
only if no errors were found in pre-parsing. The assumption is that
the real parsing pass will find and flag any errors and the user
should see them in reference to the original unmodified string.

Signed-off-by: Tom Zanussi <[email protected]>
---
kernel/trace/trace_events_synth.c | 294 ++++++++++++++++++++++++++++--
1 file changed, 274 insertions(+), 20 deletions(-)

diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c
index 2a9c8bf74bb2..6bff54ed31ce 100644
--- a/kernel/trace/trace_events_synth.c
+++ b/kernel/trace/trace_events_synth.c
@@ -1373,6 +1373,245 @@ int synth_event_delete(const char *event_name)
}
EXPORT_SYMBOL_GPL(synth_event_delete);

+static int save_synth_field(int argc, char **argv, int *consumed,
+ struct seq_buf *s, bool *added_semi)
+{
+ const char *prefix = NULL, *field_name, *field_type = argv[0];
+ const char *save_field_type, *array, *next_tok;
+ int len, ret = -EINVAL;
+ struct seq_buf f;
+ ssize_t size;
+ char *tmp;
+
+ *added_semi = false;
+
+ if (field_type[0] == ';')
+ field_type++;
+
+ if (!strcmp(field_type, "unsigned")) {
+ if (argc < 3)
+ goto out;
+ prefix = "unsigned";
+ field_type = argv[1];
+ field_name = argv[2];
+ *consumed = 3;
+ } else {
+ field_type = argv[0];
+ field_name = argv[1];
+ *consumed = 2;
+ }
+
+ len = strlen(field_name);
+ array = strchr(field_name, '[');
+ if (array)
+ len -= strlen(array);
+ else if (field_name[len - 1] == ';')
+ len--;
+
+ tmp = kmemdup_nul(field_name, len, GFP_KERNEL);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (!is_good_name(tmp)) {
+ kfree(tmp);
+ goto out;
+ }
+
+ kfree(tmp);
+
+ save_field_type = field_type;
+ if (field_type[0] == ';')
+ field_type++;
+ len = strlen(field_type) + 1;
+
+ if (array)
+ len += strlen(array);
+
+ if (prefix)
+ len += strlen(prefix) + 1;
+
+ tmp = kzalloc(len, GFP_KERNEL);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ seq_buf_init(&f, tmp, len);
+ if (prefix) {
+ seq_buf_puts(&f, prefix);
+ seq_buf_putc(&f, ' ');
+ }
+ seq_buf_puts(&f, field_type);
+ if (array) {
+ seq_buf_puts(&f, array);
+ if (f.buffer[f.len - 1] == ';')
+ f.len--;
+ }
+ if (WARN_ON_ONCE(!seq_buf_buffer_left(&f))) {
+ kfree(tmp);
+ goto out;
+ }
+
+ f.buffer[f.len] = '\0';
+
+ field_type = save_field_type;
+
+ size = synth_field_size(tmp);
+ if (size < 0 || ((size == 0) && (!synth_field_is_string(tmp)))) {
+ kfree(tmp);
+ goto out;
+ }
+
+ kfree(tmp);
+
+ if (prefix) {
+ seq_buf_puts(s, prefix);
+ seq_buf_putc(s, ' ');
+ }
+ seq_buf_puts(s, field_type);
+ seq_buf_putc(s, ' ');
+ seq_buf_puts(s, field_name);
+ if (field_name[strlen(field_name) - 1] == ';')
+ seq_buf_putc(s, ' ');
+
+ if (*consumed < argc) {
+ next_tok = argv[*consumed];
+ if (field_name[strlen(field_name) - 1] != ';' &&
+ next_tok[0] != ';') {
+ seq_buf_puts(s, "; ");
+ *added_semi = true;
+ }
+ }
+
+ ret = 0;
+ out:
+ return ret;
+}
+
+static char *insert_semicolons(const char *raw_command)
+{
+ int i, argc, consumed = 0, n_fields = 0, semis_added = 0;
+ char *name, **argv, **save_argv;
+ int ret = -EINVAL;
+ struct seq_buf s;
+ bool added_semi;
+ char *buf;
+
+ argc = 0;
+
+ argv = argv_split(GFP_KERNEL, raw_command, &argc);
+ if (!argv)
+ return NULL;
+
+ if (!argc)
+ goto out;
+
+ name = argv[0];
+ save_argv = argv;
+ argv++;
+ argc--;
+
+ buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ seq_buf_init(&s, buf, MAX_DYNEVENT_CMD_LEN);
+
+ seq_buf_puts(&s, name);
+ seq_buf_putc(&s, ' ');
+
+ if (name[0] == '\0' || argc < 1)
+ goto err;
+
+ for (i = 0; i < argc - 1; i++) {
+ if (strcmp(argv[i], ";") == 0) {
+ seq_buf_puts(&s, " ; ");
+ continue;
+ }
+
+ if (n_fields == SYNTH_FIELDS_MAX)
+ goto err;
+
+ ret = save_synth_field(argc - i, &argv[i], &consumed,
+ &s, &added_semi);
+ if (ret)
+ goto err;
+
+ if (added_semi)
+ semis_added++;
+
+ i += consumed - 1;
+ }
+
+ if (i < argc && strcmp(argv[i], ";") != 0)
+ goto err;
+
+ if (!semis_added) {
+ kfree(buf);
+ buf = NULL;
+ goto out;
+ }
+
+ if (WARN_ON_ONCE(!seq_buf_buffer_left(&s)))
+ goto err;
+
+ buf[s.len] = '\0';
+ out:
+ argv_free(save_argv);
+
+ return buf;
+ err:
+ kfree(buf);
+ buf = ERR_PTR(ret);
+
+ goto out;
+}
+
+static bool audit_old_buffer(const char *cmd)
+{
+ /* as of now, every cmd is an old cmd */
+ return true;
+}
+
+/**
+ * get_parseable_cmd - Return a modifiable string for parsing
+ * @raw_command: The command to start with
+ *
+ * Create a cmd string that can be modified by the caller for command
+ * parsing purposes. If successful, the caller must free the command
+ * returned.
+ *
+ * The input string will first be checked to see whether or not it can
+ * be considered an 'old command' - a command that doesn't require
+ * semicolons between fields - for which backward compatibility must
+ * be maintained. If it can be considered an old command, a semicolon
+ * will be added between any two fields missing one. If no semicolons
+ * were required, or if the preparsing required for the pass
+ * encountered errors, a modifiable copy of the original string will
+ * be returned.
+ *
+ * Return: parseable cmd if successful, error or NULL string otherwise.
+ */
+static char *get_parseable_cmd(const char *raw_command)
+{
+ char *cmd = NULL;
+
+ if (audit_old_buffer(raw_command))
+ cmd = insert_semicolons(raw_command);
+
+ if (IS_ERR_OR_NULL(cmd)) {
+ cmd = kstrdup(raw_command, GFP_KERNEL);
+ if (!cmd)
+ cmd = ERR_PTR(-ENOMEM);
+ }
+
+ return cmd;
+}
+
static int check_command(const char *raw_command)
{
char **argv = NULL, *cmd, *saved_cmd, *name_and_field;
@@ -1406,28 +1645,33 @@ static int check_command(const char *raw_command)

static int create_or_delete_synth_event(const char *raw_command)
{
- char *name = NULL, *fields, *p;
+ char *name = NULL, *fields, *p, *cmd;
int ret = 0;

raw_command = skip_spaces(raw_command);
if (raw_command[0] == '\0')
return ret;

- last_cmd_set(raw_command);
+ cmd = get_parseable_cmd(raw_command);
+ if (IS_ERR(cmd))
+ return PTR_ERR(cmd);

- ret = check_command(raw_command);
+ last_cmd_set(cmd);
+
+ ret = check_command(cmd);
if (ret) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return ret;
+ goto free;
}

- p = strpbrk(raw_command, " \t");
+ p = strpbrk(cmd, " \t");
if (!p) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free;
}

- name = kmemdup_nul(raw_command, p - raw_command, GFP_KERNEL);
+ name = kmemdup_nul(cmd, p - cmd, GFP_KERNEL);
if (!name)
return -ENOMEM;

@@ -1441,6 +1685,7 @@ static int create_or_delete_synth_event(const char *raw_command)
ret = __create_synth_event(name, fields);
free:
kfree(name);
+ kfree(cmd);

return ret;
}
@@ -1988,7 +2233,7 @@ EXPORT_SYMBOL_GPL(synth_event_trace_end);

static int create_synth_event(const char *raw_command)
{
- char *fields, *p;
+ char *fields, *p, *cmd;
const char *name;
int len, ret = 0;

@@ -1996,20 +2241,27 @@ static int create_synth_event(const char *raw_command)
if (raw_command[0] == '\0')
return ret;

- last_cmd_set(raw_command);
+ cmd = get_parseable_cmd(raw_command);
+ if (IS_ERR(cmd))
+ return PTR_ERR(cmd);
+
+ last_cmd_set(cmd);

- p = strpbrk(raw_command, " \t");
+ p = strpbrk(cmd, " \t");
if (!p) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free;
}

fields = skip_spaces(p);

- name = raw_command;
+ name = cmd;

- if (name[0] != 's' || name[1] != ':')
- return -ECANCELED;
+ if (name[0] != 's' || name[1] != ':') {
+ ret = -ECANCELED;
+ goto free;
+ }
name += 2;

/* This interface accepts group name prefix */
@@ -2017,26 +2269,28 @@ static int create_synth_event(const char *raw_command)
len = str_has_prefix(name, SYNTH_SYSTEM "/");
if (len == 0) {
synth_err(SYNTH_ERR_INVALID_DYN_CMD, 0);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free;
}
name += len;
}

- len = name - raw_command;
+ len = name - cmd;

- ret = check_command(raw_command + len);
+ ret = check_command(cmd + len);
if (ret) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return ret;
+ goto free;
}

- name = kmemdup_nul(raw_command + len, p - raw_command - len, GFP_KERNEL);
+ name = kmemdup_nul(cmd + len, p - cmd - len, GFP_KERNEL);
if (!name)
return -ENOMEM;

ret = __create_synth_event(name, fields);
-
kfree(name);
+ free:
+ kfree(cmd);

return ret;
}
--
2.17.1

2020-12-17 21:17:41

by Tom Zanussi

[permalink] [raw]
Subject: [PATCH v4 3/5] tracing: Update synth command errors

Since array types are handled differently, errors referencing them
also need to be handled differently. Add and use a new
INVALID_ARRAY_SPEC error. Also add INVALID_CMD and INVALID_DYN_CMD to
catch and display the correct form for badly-formed commands, which
can also be used in place of CMD_INCOMPLETE, which is removed, and
remove CMD_TOO_LONG, since it's no longer used.

Signed-off-by: Tom Zanussi <[email protected]>
---
kernel/trace/trace_events_synth.c | 71 +++++++++++++++++++++++++++----
1 file changed, 63 insertions(+), 8 deletions(-)

diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c
index 66ccbab3483b..2a9c8bf74bb2 100644
--- a/kernel/trace/trace_events_synth.c
+++ b/kernel/trace/trace_events_synth.c
@@ -23,13 +23,14 @@
#undef ERRORS
#define ERRORS \
C(BAD_NAME, "Illegal name"), \
- C(CMD_INCOMPLETE, "Incomplete command"), \
+ C(INVALID_CMD, "Command must be of the form: <name> field[;field] ..."),\
+ C(INVALID_DYN_CMD, "Command must be of the form: s or -:[synthetic/]<name> field[;field] ..."),\
C(EVENT_EXISTS, "Event already exists"), \
C(TOO_MANY_FIELDS, "Too many fields"), \
C(INCOMPLETE_TYPE, "Incomplete type"), \
C(INVALID_TYPE, "Invalid type"), \
- C(INVALID_FIELD, "Invalid field"), \
- C(CMD_TOO_LONG, "Command too long"),
+ C(INVALID_FIELD, "Invalid field"), \
+ C(INVALID_ARRAY_SPEC, "Invalid array specification"),

#undef C
#define C(a, b) SYNTH_ERR_##a
@@ -651,6 +652,10 @@ static struct synth_field *parse_synth_field(int argc, char **argv,

size = synth_field_size(field->type);
if (size < 0) {
+ if (array)
+ synth_err(SYNTH_ERR_INVALID_ARRAY_SPEC, errpos(field_name));
+ else
+ synth_err(SYNTH_ERR_INVALID_TYPE, errpos(field_type));
ret = -EINVAL;
goto free;
} else if (size == 0) {
@@ -1171,7 +1176,7 @@ static int __create_synth_event(const char *name, const char *raw_fields)
mutex_lock(&event_mutex);

if (name[0] == '\0') {
- synth_err(SYNTH_ERR_CMD_INCOMPLETE, 0);
+ synth_err(SYNTH_ERR_INVALID_CMD, 0);
ret = -EINVAL;
goto out;
}
@@ -1221,6 +1226,7 @@ static int __create_synth_event(const char *name, const char *raw_fields)
argv_free(argv);

if (consumed < argc) {
+ synth_err(SYNTH_ERR_INVALID_CMD, 0);
ret = -EINVAL;
goto err;
}
@@ -1229,7 +1235,7 @@ static int __create_synth_event(const char *name, const char *raw_fields)
}

if (n_fields == 0) {
- synth_err(SYNTH_ERR_CMD_INCOMPLETE, 0);
+ synth_err(SYNTH_ERR_INVALID_CMD, 0);
ret = -EINVAL;
goto err;
}
@@ -1367,6 +1373,37 @@ int synth_event_delete(const char *event_name)
}
EXPORT_SYMBOL_GPL(synth_event_delete);

+static int check_command(const char *raw_command)
+{
+ char **argv = NULL, *cmd, *saved_cmd, *name_and_field;
+ int argc, ret = 0;
+
+ cmd = saved_cmd = kstrdup(raw_command, GFP_KERNEL);
+ if (!cmd)
+ return -ENOMEM;
+
+ name_and_field = strsep(&cmd, ";");
+ if (!name_and_field) {
+ ret = -EINVAL;
+ goto free;
+ }
+
+ argv = argv_split(GFP_KERNEL, name_and_field, &argc);
+ if (!argv) {
+ ret = -ENOMEM;
+ goto free;
+ }
+
+ if (argc < 3)
+ ret = -EINVAL;
+free:
+ kfree(saved_cmd);
+ if (argv)
+ argv_free(argv);
+
+ return ret;
+}
+
static int create_or_delete_synth_event(const char *raw_command)
{
char *name = NULL, *fields, *p;
@@ -1378,9 +1415,17 @@ static int create_or_delete_synth_event(const char *raw_command)

last_cmd_set(raw_command);

+ ret = check_command(raw_command);
+ if (ret) {
+ synth_err(SYNTH_ERR_INVALID_CMD, 0);
+ return ret;
+ }
+
p = strpbrk(raw_command, " \t");
- if (!p)
+ if (!p) {
+ synth_err(SYNTH_ERR_INVALID_CMD, 0);
return -EINVAL;
+ }

name = kmemdup_nul(raw_command, p - raw_command, GFP_KERNEL);
if (!name)
@@ -1954,8 +1999,10 @@ static int create_synth_event(const char *raw_command)
last_cmd_set(raw_command);

p = strpbrk(raw_command, " \t");
- if (!p)
+ if (!p) {
+ synth_err(SYNTH_ERR_INVALID_CMD, 0);
return -EINVAL;
+ }

fields = skip_spaces(p);

@@ -1968,13 +2015,21 @@ static int create_synth_event(const char *raw_command)
/* This interface accepts group name prefix */
if (strchr(name, '/')) {
len = str_has_prefix(name, SYNTH_SYSTEM "/");
- if (len == 0)
+ if (len == 0) {
+ synth_err(SYNTH_ERR_INVALID_DYN_CMD, 0);
return -EINVAL;
+ }
name += len;
}

len = name - raw_command;

+ ret = check_command(raw_command + len);
+ if (ret) {
+ synth_err(SYNTH_ERR_INVALID_CMD, 0);
+ return ret;
+ }
+
name = kmemdup_nul(raw_command + len, p - raw_command - len, GFP_KERNEL);
if (!name)
return -ENOMEM;
--
2.17.1

2020-12-17 21:18:47

by Tom Zanussi

[permalink] [raw]
Subject: [PATCH v4 5/5] selftests/ftrace: Update synthetic event syntax errors

Some of the synthetic event errors and positions have changed in the
code - update those and add several more tests.

Also add a runtime check to ensure that the kernel supports dynamic
strings in synthetic events, which these tests require.

Fixes: 81ff92a93d95 (selftests/ftrace: Add test case for synthetic
event syntax errors)

Reported-by: Masami Hiramatsu <[email protected]>
Signed-off-by: Tom Zanussi <[email protected]>
---
.../trigger-synthetic_event_syntax_errors.tc | 35 ++++++++++++++-----
1 file changed, 27 insertions(+), 8 deletions(-)

diff --git a/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic_event_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic_event_syntax_errors.tc
index ada594fe16cb..955e3ceea44b 100644
--- a/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic_event_syntax_errors.tc
+++ b/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic_event_syntax_errors.tc
@@ -1,19 +1,38 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# description: event trigger - test synthetic_events syntax parser errors
-# requires: synthetic_events error_log
+# requires: synthetic_events error_log "char name[]' >> synthetic_events":README

check_error() { # command-with-error-pos-by-^
ftrace_errlog_check 'synthetic_events' "$1" 'synthetic_events'
}

+check_dyn_error() { # command-with-error-pos-by-^
+ ftrace_errlog_check 'synthetic_events' "$1" 'dynamic_events'
+}
+
check_error 'myevent ^chr arg' # INVALID_TYPE
-check_error 'myevent ^char str[];; int v' # INVALID_TYPE
-check_error 'myevent char ^str]; int v' # INVALID_NAME
-check_error 'myevent char ^str;[]' # INVALID_NAME
-check_error 'myevent ^char str[; int v' # INVALID_TYPE
-check_error '^mye;vent char str[]' # BAD_NAME
-check_error 'myevent char str[]; ^int' # INVALID_FIELD
-check_error '^myevent' # INCOMPLETE_CMD
+check_error 'myevent ^unsigned arg' # INCOMPLETE_TYPE
+
+check_error 'myevent char ^str]; int v' # BAD_NAME
+check_error '^mye-vent char str[]' # BAD_NAME
+check_error 'myevent char ^st-r[]' # BAD_NAME
+
+check_error 'myevent char str;^[]' # INVALID_FIELD
+check_error 'myevent char str; ^int' # INVALID_FIELD
+
+check_error 'myevent char ^str[; int v' # INVALID_ARRAY_SPEC
+check_error 'myevent char ^str[kdjdk]' # INVALID_ARRAY_SPEC
+check_error 'myevent char ^str[257]' # INVALID_ARRAY_SPEC
+
+check_error '^mye;vent char str[]' # INVALID_CMD
+check_error '^myevent ; char str[]' # INVALID_CMD
+check_error '^myevent; char str[]' # INVALID_CMD
+check_error '^myevent ;char str[]' # INVALID_CMD
+check_error '^; char str[]' # INVALID_CMD
+check_error '^;myevent char str[]' # INVALID_CMD
+check_error '^myevent' # INVALID_CMD
+
+check_dyn_error '^s:junk/myevent char str[' # INVALID_DYN_CMD

exit 0
--
2.17.1

2020-12-17 21:19:19

by Tom Zanussi

[permalink] [raw]
Subject: [PATCH v4 1/5] tracing/dynevent: Delegate parsing to create function

From: Masami Hiramatsu <[email protected]>

Delegate command parsing to each create function so that the
command syntax can be customized.

This requires changes to the kprobe/uprobe/synthetic event handling,
which are also included here.

Signed-off-by: Masami Hiramatsu <[email protected]>
[ [email protected]: added synthetic event modifications ]
Signed-off-by: Tom Zanussi <[email protected]>
---
kernel/trace/trace.c | 23 ++----------
kernel/trace/trace.h | 3 +-
kernel/trace/trace_dynevent.c | 35 +++++++++++-------
kernel/trace/trace_dynevent.h | 4 +--
kernel/trace/trace_events_synth.c | 60 +++++++++++++++++++++++--------
kernel/trace/trace_kprobe.c | 33 +++++++++--------
kernel/trace/trace_probe.c | 17 +++++++++
kernel/trace/trace_probe.h | 1 +
kernel/trace/trace_uprobe.c | 17 +++++----
9 files changed, 120 insertions(+), 73 deletions(-)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index eb5205e48733..e9cde7a3e0a6 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -9426,30 +9426,11 @@ void ftrace_dump(enum ftrace_dump_mode oops_dump_mode)
}
EXPORT_SYMBOL_GPL(ftrace_dump);

-int trace_run_command(const char *buf, int (*createfn)(int, char **))
-{
- char **argv;
- int argc, ret;
-
- argc = 0;
- ret = 0;
- argv = argv_split(GFP_KERNEL, buf, &argc);
- if (!argv)
- return -ENOMEM;
-
- if (argc)
- ret = createfn(argc, argv);
-
- argv_free(argv);
-
- return ret;
-}
-
#define WRITE_BUFSIZE 4096

ssize_t trace_parse_run_command(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos,
- int (*createfn)(int, char **))
+ int (*createfn)(const char *))
{
char *kbuf, *buf, *tmp;
int ret = 0;
@@ -9497,7 +9478,7 @@ ssize_t trace_parse_run_command(struct file *file, const char __user *buffer,
if (tmp)
*tmp = '\0';

- ret = trace_run_command(buf, createfn);
+ ret = createfn(buf);
if (ret)
goto out;
buf += size;
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index e448d2da0b99..98211637e545 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -1830,10 +1830,9 @@ extern int tracing_set_cpumask(struct trace_array *tr,

#define MAX_EVENT_NAME_LEN 64

-extern int trace_run_command(const char *buf, int (*createfn)(int, char**));
extern ssize_t trace_parse_run_command(struct file *file,
const char __user *buffer, size_t count, loff_t *ppos,
- int (*createfn)(int, char**));
+ int (*createfn)(const char *));

extern unsigned int err_pos(char *cmd, const char *str);
extern void tracing_log_err(struct trace_array *tr,
diff --git a/kernel/trace/trace_dynevent.c b/kernel/trace/trace_dynevent.c
index 4f967d5cd917..dc971a68dda4 100644
--- a/kernel/trace/trace_dynevent.c
+++ b/kernel/trace/trace_dynevent.c
@@ -31,23 +31,31 @@ int dyn_event_register(struct dyn_event_operations *ops)
return 0;
}

-int dyn_event_release(int argc, char **argv, struct dyn_event_operations *type)
+int dyn_event_release(const char *raw_command, struct dyn_event_operations *type)
{
struct dyn_event *pos, *n;
char *system = NULL, *event, *p;
- int ret = -ENOENT;
+ int argc, ret = -ENOENT;
+ char **argv;
+
+ argv = argv_split(GFP_KERNEL, raw_command, &argc);
+ if (!argv)
+ return -ENOMEM;

if (argv[0][0] == '-') {
- if (argv[0][1] != ':')
- return -EINVAL;
+ if (argv[0][1] != ':') {
+ ret = -EINVAL;
+ goto out;
+ }
event = &argv[0][2];
} else {
event = strchr(argv[0], ':');
- if (!event)
- return -EINVAL;
+ if (!event) {
+ ret = -EINVAL;
+ goto out;
+ }
event++;
}
- argc--; argv++;

p = strchr(event, '/');
if (p) {
@@ -63,7 +71,7 @@ int dyn_event_release(int argc, char **argv, struct dyn_event_operations *type)
if (type && type != pos->ops)
continue;
if (!pos->ops->match(system, event,
- argc, (const char **)argv, pos))
+ argc - 1, (const char **)argv + 1, pos))
continue;

ret = pos->ops->free(pos);
@@ -71,21 +79,22 @@ int dyn_event_release(int argc, char **argv, struct dyn_event_operations *type)
break;
}
mutex_unlock(&event_mutex);
-
+out:
+ argv_free(argv);
return ret;
}

-static int create_dyn_event(int argc, char **argv)
+static int create_dyn_event(const char *raw_command)
{
struct dyn_event_operations *ops;
int ret = -ENODEV;

- if (argv[0][0] == '-' || argv[0][0] == '!')
- return dyn_event_release(argc, argv, NULL);
+ if (raw_command[0] == '-' || raw_command[0] == '!')
+ return dyn_event_release(raw_command, NULL);

mutex_lock(&dyn_event_ops_mutex);
list_for_each_entry(ops, &dyn_event_ops_list, list) {
- ret = ops->create(argc, (const char **)argv);
+ ret = ops->create(raw_command);
if (!ret || ret != -ECANCELED)
break;
}
diff --git a/kernel/trace/trace_dynevent.h b/kernel/trace/trace_dynevent.h
index d6f72dcb7269..7754936b57ee 100644
--- a/kernel/trace/trace_dynevent.h
+++ b/kernel/trace/trace_dynevent.h
@@ -39,7 +39,7 @@ struct dyn_event;
*/
struct dyn_event_operations {
struct list_head list;
- int (*create)(int argc, const char *argv[]);
+ int (*create)(const char *raw_command);
int (*show)(struct seq_file *m, struct dyn_event *ev);
bool (*is_busy)(struct dyn_event *ev);
int (*free)(struct dyn_event *ev);
@@ -97,7 +97,7 @@ void *dyn_event_seq_start(struct seq_file *m, loff_t *pos);
void *dyn_event_seq_next(struct seq_file *m, void *v, loff_t *pos);
void dyn_event_seq_stop(struct seq_file *m, void *v);
int dyn_events_release_all(struct dyn_event_operations *type);
-int dyn_event_release(int argc, char **argv, struct dyn_event_operations *type);
+int dyn_event_release(const char *raw_command, struct dyn_event_operations *type);

/*
* for_each_dyn_event - iterate over the dyn_event list
diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c
index 5a8bc0b421f1..b2588a5650c9 100644
--- a/kernel/trace/trace_events_synth.c
+++ b/kernel/trace/trace_events_synth.c
@@ -62,7 +62,7 @@ static void synth_err(u8 err_type, u8 err_pos)
err_type, err_pos);
}

-static int create_synth_event(int argc, const char **argv);
+static int create_synth_event(const char *raw_command);
static int synth_event_show(struct seq_file *m, struct dyn_event *ev);
static int synth_event_release(struct dyn_event *ev);
static bool synth_event_is_busy(struct dyn_event *ev);
@@ -1383,18 +1383,30 @@ int synth_event_delete(const char *event_name)
}
EXPORT_SYMBOL_GPL(synth_event_delete);

-static int create_or_delete_synth_event(int argc, char **argv)
+static int create_or_delete_synth_event(const char *raw_command)
{
- const char *name = argv[0];
- int ret;
+ char **argv, *name = NULL;
+ int argc = 0, ret = 0;
+
+ argv = argv_split(GFP_KERNEL, raw_command, &argc);
+ if (!argv)
+ return -ENOMEM;
+
+ if (!argc)
+ goto free;
+
+ name = argv[0];

/* trace_run_command() ensures argc != 0 */
if (name[0] == '!') {
ret = synth_event_delete(name + 1);
- return ret;
+ goto free;
}

ret = __create_synth_event(argc - 1, name, (const char **)argv + 1);
+free:
+ argv_free(argv);
+
return ret == -ECANCELED ? -EINVAL : ret;
}

@@ -1403,7 +1415,7 @@ static int synth_event_run_command(struct dynevent_cmd *cmd)
struct synth_event *se;
int ret;

- ret = trace_run_command(cmd->seq.buffer, create_or_delete_synth_event);
+ ret = create_or_delete_synth_event(cmd->seq.buffer);
if (ret)
return ret;

@@ -1939,23 +1951,43 @@ int synth_event_trace_end(struct synth_event_trace_state *trace_state)
}
EXPORT_SYMBOL_GPL(synth_event_trace_end);

-static int create_synth_event(int argc, const char **argv)
+static int create_synth_event(const char *raw_command)
{
- const char *name = argv[0];
- int len;
+ char **argv, *name;
+ int len, argc = 0, ret = 0;
+
+ argv = argv_split(GFP_KERNEL, raw_command, &argc);
+ if (!argv) {
+ ret = -ENOMEM;
+ return ret;
+ }

- if (name[0] != 's' || name[1] != ':')
- return -ECANCELED;
+ if (!argc)
+ goto free;
+
+ name = argv[0];
+
+ if (name[0] != 's' || name[1] != ':') {
+ ret = -ECANCELED;
+ goto free;
+ }
name += 2;

/* This interface accepts group name prefix */
if (strchr(name, '/')) {
len = str_has_prefix(name, SYNTH_SYSTEM "/");
- if (len == 0)
- return -EINVAL;
+ if (len == 0) {
+ ret = -EINVAL;
+ goto free;
+ }
name += len;
}
- return __create_synth_event(argc - 1, name, argv + 1);
+
+ ret = __create_synth_event(argc - 1, name, (const char **)argv + 1);
+free:
+ argv_free(argv);
+
+ return ret;
}

static int synth_event_release(struct dyn_event *ev)
diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
index b29f92c51b1a..1c1f089191e4 100644
--- a/kernel/trace/trace_kprobe.c
+++ b/kernel/trace/trace_kprobe.c
@@ -35,7 +35,7 @@ static int __init set_kprobe_boot_events(char *str)
}
__setup("kprobe_event=", set_kprobe_boot_events);

-static int trace_kprobe_create(int argc, const char **argv);
+static int trace_kprobe_create(const char *raw_command);
static int trace_kprobe_show(struct seq_file *m, struct dyn_event *ev);
static int trace_kprobe_release(struct dyn_event *ev);
static bool trace_kprobe_is_busy(struct dyn_event *ev);
@@ -711,7 +711,7 @@ static inline void sanitize_event_name(char *name)
*name = '_';
}

-static int trace_kprobe_create(int argc, const char *argv[])
+static int __trace_kprobe_create(int argc, const char *argv[])
{
/*
* Argument syntax:
@@ -908,20 +908,25 @@ static int trace_kprobe_create(int argc, const char *argv[])
goto out;
}

-static int create_or_delete_trace_kprobe(int argc, char **argv)
+static int trace_kprobe_create(const char *raw_command)
+{
+ return trace_probe_create(raw_command, __trace_kprobe_create);
+}
+
+static int create_or_delete_trace_kprobe(const char *raw_command)
{
int ret;

- if (argv[0][0] == '-')
- return dyn_event_release(argc, argv, &trace_kprobe_ops);
+ if (raw_command[0] == '-')
+ return dyn_event_release(raw_command, &trace_kprobe_ops);

- ret = trace_kprobe_create(argc, (const char **)argv);
+ ret = trace_kprobe_create(raw_command);
return ret == -ECANCELED ? -EINVAL : ret;
}

static int trace_kprobe_run_command(struct dynevent_cmd *cmd)
{
- return trace_run_command(cmd->seq.buffer, create_or_delete_trace_kprobe);
+ return create_or_delete_trace_kprobe(cmd->seq.buffer);
}

/**
@@ -1082,7 +1087,7 @@ int kprobe_event_delete(const char *name)

snprintf(buf, MAX_EVENT_NAME_LEN, "-:%s", name);

- return trace_run_command(buf, create_or_delete_trace_kprobe);
+ return create_or_delete_trace_kprobe(buf);
}
EXPORT_SYMBOL_GPL(kprobe_event_delete);

@@ -1885,7 +1890,7 @@ static __init void setup_boot_kprobe_events(void)
if (p)
*p++ = '\0';

- ret = trace_run_command(cmd, create_or_delete_trace_kprobe);
+ ret = create_or_delete_trace_kprobe(cmd);
if (ret)
pr_warn("Failed to add event(%d): %s\n", ret, cmd);

@@ -1979,8 +1984,7 @@ static __init int kprobe_trace_self_tests_init(void)

pr_info("Testing kprobe tracing: ");

- ret = trace_run_command("p:testprobe kprobe_trace_selftest_target $stack $stack0 +0($stack)",
- create_or_delete_trace_kprobe);
+ ret = create_or_delete_trace_kprobe("p:testprobe kprobe_trace_selftest_target $stack $stack0 +0($stack)");
if (WARN_ON_ONCE(ret)) {
pr_warn("error on probing function entry.\n");
warn++;
@@ -2001,8 +2005,7 @@ static __init int kprobe_trace_self_tests_init(void)
}
}

- ret = trace_run_command("r:testprobe2 kprobe_trace_selftest_target $retval",
- create_or_delete_trace_kprobe);
+ ret = create_or_delete_trace_kprobe("r:testprobe2 kprobe_trace_selftest_target $retval");
if (WARN_ON_ONCE(ret)) {
pr_warn("error on probing function return.\n");
warn++;
@@ -2075,13 +2078,13 @@ static __init int kprobe_trace_self_tests_init(void)
trace_probe_event_call(&tk->tp), file);
}

- ret = trace_run_command("-:testprobe", create_or_delete_trace_kprobe);
+ ret = create_or_delete_trace_kprobe("-:testprobe");
if (WARN_ON_ONCE(ret)) {
pr_warn("error on deleting a probe.\n");
warn++;
}

- ret = trace_run_command("-:testprobe2", create_or_delete_trace_kprobe);
+ ret = create_or_delete_trace_kprobe("-:testprobe2");
if (WARN_ON_ONCE(ret)) {
pr_warn("error on deleting a probe.\n");
warn++;
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index d2867ccc6aca..ec589a4612df 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -1134,3 +1134,20 @@ bool trace_probe_match_command_args(struct trace_probe *tp,
}
return true;
}
+
+int trace_probe_create(const char *raw_command, int (*createfn)(int, const char **))
+{
+ int argc = 0, ret = 0;
+ char **argv;
+
+ argv = argv_split(GFP_KERNEL, raw_command, &argc);
+ if (!argv)
+ return -ENOMEM;
+
+ if (argc)
+ ret = createfn(argc, (const char **)argv);
+
+ argv_free(argv);
+
+ return ret;
+}
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 2f703a20c724..7ce4027089ee 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -341,6 +341,7 @@ struct event_file_link *trace_probe_get_file_link(struct trace_probe *tp,
int trace_probe_compare_arg_type(struct trace_probe *a, struct trace_probe *b);
bool trace_probe_match_command_args(struct trace_probe *tp,
int argc, const char **argv);
+int trace_probe_create(const char *raw_command, int (*createfn)(int, const char **));

#define trace_probe_for_each_link(pos, tp) \
list_for_each_entry(pos, &(tp)->event->files, list)
diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c
index 3cf7128e1ad3..e6b56a65f80f 100644
--- a/kernel/trace/trace_uprobe.c
+++ b/kernel/trace/trace_uprobe.c
@@ -34,7 +34,7 @@ struct uprobe_trace_entry_head {
#define DATAOF_TRACE_ENTRY(entry, is_return) \
((void*)(entry) + SIZEOF_TRACE_ENTRY(is_return))

-static int trace_uprobe_create(int argc, const char **argv);
+static int trace_uprobe_create(const char *raw_command);
static int trace_uprobe_show(struct seq_file *m, struct dyn_event *ev);
static int trace_uprobe_release(struct dyn_event *ev);
static bool trace_uprobe_is_busy(struct dyn_event *ev);
@@ -530,7 +530,7 @@ static int register_trace_uprobe(struct trace_uprobe *tu)
* Argument syntax:
* - Add uprobe: p|r[:[GRP/]EVENT] PATH:OFFSET[%return][(REF)] [FETCHARGS]
*/
-static int trace_uprobe_create(int argc, const char **argv)
+static int __trace_uprobe_create(int argc, const char **argv)
{
struct trace_uprobe *tu;
const char *event = NULL, *group = UPROBE_EVENT_SYSTEM;
@@ -716,14 +716,19 @@ static int trace_uprobe_create(int argc, const char **argv)
return ret;
}

-static int create_or_delete_trace_uprobe(int argc, char **argv)
+int trace_uprobe_create(const char *raw_command)
+{
+ return trace_probe_create(raw_command, __trace_uprobe_create);
+}
+
+static int create_or_delete_trace_uprobe(const char *raw_command)
{
int ret;

- if (argv[0][0] == '-')
- return dyn_event_release(argc, argv, &trace_uprobe_ops);
+ if (raw_command[0] == '-')
+ return dyn_event_release(raw_command, &trace_uprobe_ops);

- ret = trace_uprobe_create(argc, (const char **)argv);
+ ret = trace_uprobe_create(raw_command);
return ret == -ECANCELED ? -EINVAL : ret;
}

--
2.17.1