Skip to content

Commit

Permalink
tracing: Add kprobe event command generation functions
Browse files Browse the repository at this point in the history
Add functions used to generate kprobe event commands, built on top of
the dynevent_cmd interface.

kprobe_event_gen_cmd_start() is used to create a kprobe event command
using a variable arg list, and kretprobe_event_gen_cmd_start() does
the same for kretprobe event commands.  kprobe_event_add_fields() can
be used to add single fields one by one or as a group.  Once all
desired fields are added, kprobe_event_gen_cmd_end() or
kretprobe_event_gen_cmd_end() respectively are used to actually
execute the command and create the event.

Link: http://lkml.kernel.org/r/95cc4696502bb6017f9126f306a45ad19b4cc14f.1580323897.git.zanussi@kernel.org

Acked-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Tom Zanussi <zanussi@kernel.org>
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
  • Loading branch information
Tom Zanussi authored and rostedt committed Jan 30, 2020
1 parent 9fe41ef commit 2a588dd
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 0 deletions.
31 changes: 31 additions & 0 deletions include/linux/trace_events.h
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ extern void trace_put_event_file(struct trace_event_file *file);

enum dynevent_type {
DYNEVENT_TYPE_SYNTH = 1,
DYNEVENT_TYPE_KPROBE,
DYNEVENT_TYPE_NONE,
};

Expand Down Expand Up @@ -442,6 +443,36 @@ extern int synth_event_add_val(const char *field_name, u64 val,
struct synth_event_trace_state *trace_state);
extern int synth_event_trace_end(struct synth_event_trace_state *trace_state);

extern int kprobe_event_delete(const char *name);

extern void kprobe_event_cmd_init(struct dynevent_cmd *cmd,
char *buf, int maxlen);

#define kprobe_event_gen_cmd_start(cmd, name, loc, ...) \
__kprobe_event_gen_cmd_start(cmd, false, name, loc, ## __VA_ARGS__, NULL)

#define kretprobe_event_gen_cmd_start(cmd, name, loc, ...) \
__kprobe_event_gen_cmd_start(cmd, true, name, loc, ## __VA_ARGS__, NULL)

extern int __kprobe_event_gen_cmd_start(struct dynevent_cmd *cmd,
bool kretprobe,
const char *name,
const char *loc, ...);

#define kprobe_event_add_fields(cmd, ...) \
__kprobe_event_add_fields(cmd, ## __VA_ARGS__, NULL)

#define kprobe_event_add_field(cmd, field) \
__kprobe_event_add_fields(cmd, field, NULL)

extern int __kprobe_event_add_fields(struct dynevent_cmd *cmd, ...);

#define kprobe_event_gen_cmd_end(cmd) \
dynevent_create(cmd)

#define kretprobe_event_gen_cmd_end(cmd) \
dynevent_create(cmd)

/*
* Event file flags:
* ENABLED - The event is enabled
Expand Down
161 changes: 161 additions & 0 deletions kernel/trace/trace_kprobe.c
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,167 @@ int trace_kprobe_run_command(const char *command)
return trace_run_command(command, create_or_delete_trace_kprobe);
}

static int trace_kprobe_run_cmd(struct dynevent_cmd *cmd)
{
return trace_run_command(cmd->buf, create_or_delete_trace_kprobe);
}

/**
* kprobe_event_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 kprobe_event functions.
*/
void kprobe_event_cmd_init(struct dynevent_cmd *cmd, char *buf, int maxlen)
{
dynevent_cmd_init(cmd, buf, maxlen, DYNEVENT_TYPE_KPROBE,
trace_kprobe_run_cmd);
}
EXPORT_SYMBOL_GPL(kprobe_event_cmd_init);

/**
* __kprobe_event_gen_cmd_start - Generate a kprobe 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 kprobe_event_gen_cmd_start() wrapper, which automatically
* adds a NULL to the end of the arg list. If this function is used
* directly, make sure the last arg in the variable arg list is NULL.
*
* Generate a kprobe event command to be executed by
* kprobe_event_gen_cmd_end(). This function can be used to generate the
* complete command or only the first part of it; in the latter case,
* kprobe_event_add_fields() can be used to add more fields following this.
*
* Return: 0 if successful, error otherwise.
*/
int __kprobe_event_gen_cmd_start(struct dynevent_cmd *cmd, bool kretprobe,
const char *name, const char *loc, ...)
{
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:kprobes/%s", name);
else
snprintf(buf, MAX_EVENT_NAME_LEN, "p:kprobes/%s", name);

ret = dynevent_str_add(cmd, buf);
if (ret)
return ret;

dynevent_arg_init(&arg, NULL, 0);
arg.str = loc;
ret = dynevent_arg_add(cmd, &arg);
if (ret)
return ret;

va_start(args, loc);
for (;;) {
const char *field;

field = va_arg(args, const char *);
if (!field)
break;

if (++cmd->n_fields > MAX_TRACE_ARGS) {
ret = -EINVAL;
break;
}

arg.str = field;
ret = dynevent_arg_add(cmd, &arg);
if (ret)
break;
}
va_end(args);

return ret;
}
EXPORT_SYMBOL_GPL(__kprobe_event_gen_cmd_start);

/**
* __kprobe_event_add_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 kprobe_event_add_fields() wrapper, which
* automatically adds a NULL to the end of the arg list. If this
* function is used directly, make sure the 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 __kprobe_event_add_fields(struct dynevent_cmd *cmd, ...)
{
struct dynevent_arg arg;
va_list args;
int ret;

if (cmd->type != DYNEVENT_TYPE_KPROBE)
return -EINVAL;

dynevent_arg_init(&arg, NULL, 0);

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;
}

arg.str = field;
ret = dynevent_arg_add(cmd, &arg);
if (ret)
break;
}
va_end(args);

return ret;
}
EXPORT_SYMBOL_GPL(__kprobe_event_add_fields);

/**
* kprobe_event_delete - 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 kprobe_event_delete(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(kprobe_event_delete);

static int trace_kprobe_release(struct dyn_event *ev)
{
struct trace_kprobe *tk = to_trace_kprobe(ev);
Expand Down

0 comments on commit 2a588dd

Please sign in to comment.