[PATCH v2 10/12] tracing: Add kprobe event command generation functions

From: Tom Zanussi
Date: Fri Jan 10 2020 - 15:35:48 EST


Add functions used to generate kprobe event commands, built on top of
the dynevent_cmd interface.

gen_kprobe_cmd() is used to create a kprobe event command using a
variable arg list. add_probe_fields() can be used to add single
fields one by one or as a group. Once all desired fields are added,
create_dynevent() is used to actually execute the command and create
the event.

gen_kprobe_cmd() can be used to create kretprobe commands as well.

Signed-off-by: Tom Zanussi <zanussi@xxxxxxxxxx>
---
include/linux/trace_events.h | 18 +++++
kernel/trace/trace_kprobe.c | 161 ++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 177 insertions(+), 2 deletions(-)

diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h
index c25b18db84eb..7ae9ad182b0e 100644
--- a/include/linux/trace_events.h
+++ b/include/linux/trace_events.h
@@ -464,6 +464,24 @@ extern int add_synth_val(const char *field_name, u64 val,
struct synth_gen_state *gen_state);
extern int trace_synth_event_end(struct synth_gen_state *gen_state);

+extern void kprobe_dynevent_cmd_init(struct dynevent_cmd *cmd,
+ char *buf, int maxlen);
+
+#define gen_kprobe_cmd(cmd, name, loc, ...) \
+ __gen_kprobe_cmd(cmd, name, loc, false, ## __VA_ARGS__, NULL)
+
+#define gen_kretprobe_cmd(cmd, name, loc, ...) \
+ __gen_kprobe_cmd(cmd, name, loc, true, ## __VA_ARGS__, NULL)
+
+extern int __gen_kprobe_cmd(struct dynevent_cmd *cmd, const char *name,
+ const char *loc, bool kretprobe, ...);
+
+#define add_probe_fields(cmd, ...) \
+ __add_probe_fields(cmd, ## __VA_ARGS__, NULL)
+
+extern int __add_probe_fields(struct dynevent_cmd *cmd, ...);
+extern int delete_kprobe_event(const char *name);
+
/*
* Event file flags:
* ENABLED - The event is enabled
diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
index 25dac3745afb..f911b3d74b46 100644
--- a/kernel/trace/trace_kprobe.c
+++ b/kernel/trace/trace_kprobe.c
@@ -902,11 +902,168 @@ static int create_or_delete_trace_kprobe(int argc, char **argv)
return ret == -ECANCELED ? -EINVAL : ret;
}

-int trace_kprobe_run_command(const char *command)
+static int trace_kprobe_run_command(struct dynevent_cmd *cmd)
{
- return trace_run_command(command, create_or_delete_trace_kprobe);
+ return trace_run_command(cmd->buf, create_or_delete_trace_kprobe);
}

+/**
+ * kprobe_dynevent_cmd_init - Initialize a kprobe event command object
+ * @cmd: A pointer to the dynevent_cmd struct representing the new event
+ * @buf: A pointer to the buffer used to build the command
+ * @maxlen: The length of the buffer passed in @buf
+ *
+ * Initialize a synthetic event command object. Use this before
+ * calling any of the other dyenvent_cmd functions.
+ */
+void kprobe_dynevent_cmd_init(struct dynevent_cmd *cmd, char *buf, int maxlen)
+{
+ dynevent_cmd_init(cmd, buf, maxlen, DYNEVENT_TYPE_KPROBE,
+ trace_kprobe_run_command);
+}
+EXPORT_SYMBOL_GPL(kprobe_dynevent_cmd_init);
+
+/**
+ * __gen_kprobe_cmd - Generate a synthetic event command from arg list
+ * @cmd: A pointer to the dynevent_cmd struct representing the new event
+ * @name: The name of the kprobe event
+ * @loc: The location of the kprobe event
+ * @kretprobe: Is this a return probe?
+ * @args: Variable number of arg (pairs), one pair for each field
+ *
+ * NOTE: Users normally won't want to call this function directly, but
+ * rather use the gen_kprobe_cmd() wrapper, which automatically adds a
+ * NULL to the end of the arg list. If this function is used
+ * directly, make suer he last arg in the variable arg list is NULL.
+ *
+ * Generate a kprobe event command to be executed by
+ * create_dynevent(). This function can be used to generate the
+ * complete command or only the first part of it; in the latter case,
+ * add_probe_fields() can be used to add more fields following this.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+int __gen_kprobe_cmd(struct dynevent_cmd *cmd, const char *name,
+ const char *loc, bool kretprobe, ...)
+{
+ char buf[MAX_EVENT_NAME_LEN];
+ struct dynevent_arg arg;
+ va_list args;
+ int ret;
+
+ if (cmd->type != DYNEVENT_TYPE_KPROBE)
+ return -EINVAL;
+
+ if (kretprobe)
+ snprintf(buf, MAX_EVENT_NAME_LEN, "r:%s", name);
+ else
+ snprintf(buf, MAX_EVENT_NAME_LEN, "p:%s", name);
+
+ dynevent_arg_init(&arg, NULL, 0);
+ arg.str = buf;
+ ret = add_dynevent_arg(cmd, &arg);
+ if (ret)
+ return ret;
+
+ dynevent_arg_init(&arg, NULL, 0);
+ arg.str = loc;
+ ret = add_dynevent_arg(cmd, &arg);
+ if (ret)
+ return ret;
+
+ va_start(args, kretprobe);
+ for (;;) {
+ const char *field;
+
+ field = va_arg(args, const char *);
+ if (!field)
+ break;
+
+ if (++cmd->n_fields > MAX_TRACE_ARGS) {
+ ret = -EINVAL;
+ break;
+ }
+
+ dynevent_arg_init(&arg, NULL, 0);
+ arg.str = field;
+ ret = add_dynevent_arg(cmd, &arg);
+ if (ret)
+ break;
+ }
+ va_end(args);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(__gen_kprobe_cmd);
+
+/**
+ * __add_probe_fields - Add probe fields to a kprobe command from arg list
+ * @cmd: A pointer to the dynevent_cmd struct representing the new event
+ * @args: Variable number of arg (pairs), one pair for each field
+ *
+ * NOTE: Users normally won't want to call this function directly, but
+ * rather use the add_probe_fields() wrapper, which automatically adds a
+ * NULL to the end of the arg list. If this function is used
+ * directly, make suer he last arg in the variable arg list is NULL.
+ *
+ * Add probe fields to an existing kprobe command using a variable
+ * list of args. Fields are added in the same order they're listed.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+int __add_probe_fields(struct dynevent_cmd *cmd, ...)
+{
+ struct dynevent_arg arg;
+ va_list args;
+ int ret;
+
+ if (cmd->type != DYNEVENT_TYPE_KPROBE)
+ return -EINVAL;
+
+ va_start(args, cmd);
+ for (;;) {
+ const char *field;
+
+ field = va_arg(args, const char *);
+ if (!field)
+ break;
+
+ if (++cmd->n_fields > MAX_TRACE_ARGS) {
+ ret = -EINVAL;
+ break;
+ }
+
+ dynevent_arg_init(&arg, NULL, 0);
+ arg.str = field;
+ ret = add_dynevent_arg(cmd, &arg);
+ if (ret)
+ break;
+ }
+ va_end(args);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(__add_probe_fields);
+
+/**
+ * delete_kprobe_event - Delete a kprobe event
+ * @name: The name of the kprobe event to delete
+ *
+ * Delete a kprobe event with the give @name from kernel code rather
+ * than directly from the command line.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+int delete_kprobe_event(const char *name)
+{
+ char buf[MAX_EVENT_NAME_LEN];
+
+ snprintf(buf, MAX_EVENT_NAME_LEN, "-:%s", name);
+
+ return trace_run_command(buf, create_or_delete_trace_kprobe);
+}
+EXPORT_SYMBOL_GPL(delete_kprobe_event);
+
static int trace_kprobe_release(struct dyn_event *ev)
{
struct trace_kprobe *tk = to_trace_kprobe(ev);
--
2.14.1