blob: 38cb0b685926423a96f647497be699cad43ad0bf [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Google Inc, Steven Rostedt <rostedt@goodmis.org>
*/
#include "ktrace.h"
static void create_usage(struct ccli *ccli)
{
ccli_printf(ccli, "usage: create <type> <type-command>\n"
" <type> : kprobe, eprobe, synthetic_event\n");
}
static int add_type_offset(struct ccli *ccli, char *p, char **pvar, char *line)
{
long long offset;
char *var = *pvar;
char *type;
char *tmp;
char *end;
bool neg;
int ret = 0;
for (; *p; p++) {
switch(*p) {
case '.':
p++;
type = p;
for (; *p; p++) {
if (*p == '.' || *p == '-')
break;
}
if (*p == '.') {
ccli_printf(ccli, "# Two types can not be togethe '%s'\n",
line);
return -1;
}
if (strncmp(type, "string", 6) == 0 ||
strncmp(type, "ustring", 7) == 0) {
if (*p) {
ccli_printf(ccli, "# Strings can not be deferenced '%s'\n",
type);
return -1;
}
ret = asprintf(&tmp, "+0(%s):%.*s",
var, (int)(p - type),
type);
} else {
ret = asprintf(&tmp, "%s:%.*s",
var, (int)(p - type),
type);
}
if (ret < 0)
return -1;
free(var);
var = tmp;
/* We need to compare current p again */
p--;
break;
case '-':
p++;
if (*p != '>') {
ccli_printf(ccli, "# Invalid variable '%s'\n",
type);
return -1;
}
p++;
if (*p == '-') {
p++;
neg = true;
}
offset = strtoll(p, &end, 0);
ret = asprintf(&tmp, "%s%llu(%s)",
neg ? "-" : "+", offset, var);
if (ret < 0)
return -1;
free(var);
var = tmp;
break;
}
}
*pvar = var;
return ret;
}
static int add_var(struct ccli *ccli, char **command, char *line)
{
char *sym;
char *var;
char *tmp;
char *p;
char ch;
int ret;
p = strchr(line, '=');
if (!p) {
ccli_printf(ccli, "Invalid variable '%s'\n", line);
return -1;
}
*p = '\0';
sym = p + 1;
for (p++; *p; p++) {
if (*p == '.' || *p == '-')
break;
}
ch = *p;
*p = '\0';
var = strdup(sym);
if (!var)
return -1;
*p = ch;
ret = add_type_offset(ccli, p, &var, line);
if (ret >= 0)
ret = asprintf(&tmp, "%s %s=%s", *command, line, var);
free(var);
if (ret < 0)
return -1;
free(*command);
*command = tmp;
return 0;
}
static int create_kprobe(struct ccli *ccli, void *data,
int argc, char **argv)
{
char *command;
char *sym;
char *name;
int ret;
int i;
if (argc < 3) {
ccli_printf(ccli, "# usage: create kprobe name function/address fields\n");
return 0;
}
name = argv[0];
sym = argv[1];
ret = asprintf(&command, "k:%s %s", name, sym);
if (ret < 0)
return 0;
for (i = 2 ; i < argc; i++ ) {
ret = add_var(ccli, &command, argv[i]);
if (ret < 0)
goto out;
}
ccli_printf(ccli, "# echo '%s' >> %s/dynamic_events\n",
command, tracefs_tracing_dir());
out:
free(command);
return 0;
}
static int add_event_var(struct ccli *ccli, struct tep_handle *tep,
char **command, struct tep_event *event,
char *line)
{
struct tep_format_field *field;
char *fname;
char *var;
char *tmp;
char *p;
char ch;
int ret;
p = strchr(line, '=');
if (!p) {
ccli_printf(ccli, "Invalid variable '%s'\n", line);
return -1;
}
*p = '\0';
fname = p + 1;
for (p++; *p; p++) {
if (*p == '.' || *p == '-')
break;
}
ch = *p;
*p = '\0';
field = tep_find_any_field(event, fname);
if (!field) {
ccli_printf(ccli, "# Cannot find field '%s' for event '%s'\n",
fname, event->name);
return -1;
}
ret = asprintf(&var, "$%s", fname);
if (ret < 0)
return -1;
*p = ch;
ret = add_type_offset(ccli, p, &var, line);
if (ret >= 0)
ret = asprintf(&tmp, "%s %s=%s", *command, line, var);
free(var);
if (ret < 0)
return -1;
free(*command);
*command = tmp;
return 0;
}
static int create_eprobe(struct ccli *ccli, void *data,
int argc, char **argv)
{
struct tep_handle *tep = data;
struct tep_event *event;
char *command;
char *system;
char *ename;
char *name;
char *sav;
int ret;
int i;
if (argc < 3) {
ccli_printf(ccli, "# usage: create eprobe name system/event fields\n");
return 0;
}
name = argv[0];
system = strtok_r(argv[1], "/", &sav);
ename = strtok_r(NULL, "/", &sav);
if (!ename) {
event = tep_find_event_by_name(tep, NULL, system);
if (!event) {
ccli_printf(ccli, "# Event %s not found\n", system);
return 0;
}
system = event->system;
} else {
event = tep_find_event_by_name(tep, system, ename);
if (!event) {
ccli_printf(ccli, "# Event %s/%s not found\n",
system, ename);
return 0;
}
}
ret = asprintf(&command, "e:%s %s/%s", name, system, ename);
if (ret < 0)
return 0;
for (i = 2 ; i < argc; i++ ) {
ret = add_event_var(ccli, tep, &command, event, argv[i]);
if (ret < 0)
goto out;
}
ccli_printf(ccli, "# echo '%s' >> %s/dynamic_events\n",
command, tracefs_tracing_dir());
out:
free(command);
return 0;
}
static int usage_synthetic(struct ccli *ccli)
{
ccli_printf(ccli, "# usage: create synthetic name system/event.field1[,field2,..] system2/event2.field1[,field2,..] field1=systemX/eventX.field[ field2=systemX/eventX.field ...]\n"
"# Where the synthetic event is created when the fields of system/event\n"
"# match system/event2 fields. Then add the fields of the synthetic event\n"
"# on how they will map to the other fields.\n#\n"
"# A synthetic event field may also equal timestamps:\n"
"# start=system/event.TIMESTAMP\n#\n"
"# Or even a delta:\n"
"# delta=system/event.TIMESTAMP-system2/event2.TIMESTAMP\n"
);
return 0;
}
int add_synth_field(struct ccli *ccli, struct tracefs_synth *synth,
char *start_system, char *start_event,
char *end_system, char *end_event, char *field)
{
char *system;
char *event;
char *name;
char *val;
char *sav;
name = strtok_r(field, "=", &sav);
system = strtok_r(NULL, "/", &sav);
if (!system) {
ccli_printf(ccli, "# Missing '=system/event.field' in '%s'\n", name);
return -1;
}
event = strtok_r(NULL, ".", &sav);
if (!event) {
ccli_printf(ccli, "# Missing /event.field in '%s'\n", system);
return -1;
}
val = strtok_r(NULL, ".", &sav);
if (!system) {
ccli_printf(ccli, "# Missing .field in '%s' \n", event);
return -1;
}
if ((strcmp(system, start_system) == 0) &&
(strcmp(event, start_event) == 0)) {
return tracefs_synth_add_start_field(synth, val, name);
}
if ((strcmp(system, end_system) == 0) &&
(strcmp(event, end_event) == 0)) {
return tracefs_synth_add_end_field(synth, val, name);
}
ccli_printf(ccli, "# %s/%s does not match either start or end events\n",
system, event);
return -1;
}
static int create_synthetic(struct ccli *ccli, void *data,
int argc, char **argv)
{
struct tep_handle *tep = data;
struct tracefs_synth *synth;
struct trace_seq seq;
char *system1;
char *event1;
char *system2;
char *event2;
char **fields1 = NULL;
char **fields2 = NULL;
char **svals = NULL;
char **tmp;
char *field;
char *name;
char *sav;
int ret;
int f = 0;
int i;
if (argc < 4)
return usage_synthetic(ccli);
name = argv[0];
system1 = strtok_r(argv[1], "/", &sav);
event1 = strtok_r(NULL, ".", &sav);
if (!system1 || !event1) {
ccli_printf(ccli, "# No slash found in first event %s\n", system1);
return 0;
}
field = strtok_r(NULL, ",", &sav);
if (!field) {
ccli_printf(ccli, "# No field found for %s/%s\n",
system1, event1);
return 0;
}
do {
tmp = realloc(fields1, sizeof(*tmp) * (f + 1));
if (!tmp)
return 0;
tmp[f++] = field;
fields1 = tmp;
field = strtok_r(NULL, ",", &sav);
} while (field);
/* Get the second event */
system2 = strtok_r(argv[2], "/", &sav);
event2 = strtok_r(NULL, ".", &sav);
if (!system2 || !event2) {
ccli_printf(ccli, "# No slash found in second event %s\n", system2);
goto free;
}
fields2 = calloc(f, sizeof(*fields2));
if (!fields2)
goto free;
for (i = 0; i < f; i++) {
field = strtok_r(NULL, ",", &sav);
if (!field)
goto free_fields;
fields2[i] = field;
}
field = strtok_r(NULL, ",", &sav);
if (field)
goto free_fields;
svals = calloc(argc - 3, sizeof(*svals));
if (!svals)
goto free;
synth = tracefs_synth_alloc(tep, name, system1, event1,
system2, event2, fields1[0],
fields2[0], NULL);
if (!synth) {
ccli_printf(ccli, "# Failed to create synthetic event\n");
goto free;
}
for (i = 1; i < f; i++) {
ret = tracefs_synth_add_match_field(synth, fields1[i],
fields2[i], NULL);
if (ret < 0) {
ccli_printf(ccli, "# Failed to match %s with %s\n",
fields1[i], fields2[i]);
goto free_synth;
}
}
for (i = 3; i < argc; i++) {
ret = add_synth_field(ccli, synth, system1, event1,
system2, event2, argv[i]);
if (ret < 0)
goto free_synth;
}
trace_seq_init(&seq);
tracefs_synth_echo_cmd(&seq, synth);
trace_seq_terminate(&seq);
ccli_printf(ccli, "%s\n", seq.buffer);
trace_seq_destroy(&seq);
free_synth:
tracefs_synth_free(synth);
free:
free(fields1);
free(fields2);
free(svals);
return 0;
free_fields:
ccli_printf(ccli, "# End event must have same number of fields as start event\n");
goto free;
}
int cmd_create(struct ccli *ccli, const char *command, const char *line,
void *data, int argc, char **argv)
{
if (argc < 2) {
create_usage(ccli);
return 0;
}
if (strcmp(argv[1], "kprobe") == 0)
return create_kprobe(ccli, data, argc - 2, argv + 2);
if (strcmp(argv[1], "eprobe") == 0)
return create_eprobe(ccli, data, argc - 2, argv + 2);
if (strcmp(argv[1], "synthetic") == 0)
return create_synthetic(ccli, data, argc - 2, argv + 2);
return 0;
}
static int get_mods(char ***mods)
{
FILE *fp;
char **list = NULL;
char **tmp;
char *line = NULL;
char *mod;
char *sav;
size_t len = 0;
size_t mlen;
int cnt = 0;
fp = fopen("/proc/modules", "r");
if (!fp)
return 0;
while (getline(&line, &len, fp) > 0) {
mod = strtok_r(line, " ", &sav);
tmp = realloc(list, sizeof(*list) * (cnt + 1));
if (!tmp) {
for (cnt--; cnt >= 0; cnt--)
free(list[cnt]);
free(list);
free(line);
return 0;
}
tmp[cnt++] = mod;
mlen = strlen(mod);
/* Append the ":" on modules to denote these are modules */
if (mlen + 2 < len) {
mod[mlen++] = ':';
mod[mlen] = '\0';
}
list = tmp;
line = NULL;
len = 0;
}
free(line);
*mods = list;
fclose(fp);
return cnt;
}
static int load_available_filter(char ***list, char *match)
{
char **funcs = NULL;
char **mods = NULL;
char *filter = NULL;
char *mod = NULL;
char *p;
int ret;
int cnt = 0;
int mcnt = 0;
int i;
*list = NULL;
if ((p = strchr(match, ':'))) {
mod = strdup(match);
if (!mod)
return 0;
mod[p - match] = '\0';
match = p + 1;
} else {
mcnt = get_mods(&mods);
}
ret = asprintf(&filter, "%s*", match);
if (ret < 0)
goto out;
ret = tracefs_filter_functions(filter, mod, &funcs);
if (ret < 0)
goto out;
cnt = tracefs_list_size(funcs);
if (cnt < 0)
goto out;
*list = calloc(cnt + mcnt, sizeof(**list));
for (i = 0; i < mcnt; i++) {
(*list)[i] = mods[i];
mods[i] = NULL;
}
for (i = 0; i < cnt; i++) {
if (mod) {
ret = asprintf(&p, "%s:%s", mod, funcs[i]);
if (ret < 0)
p = NULL;
} else
p = strdup(funcs[i]);
(*list)[i + mcnt] = p;
}
out:
tracefs_list_free(funcs);
free(filter);
for (i = 0; i < mcnt; i++)
free(mods[i]);
free(mods);
free(mod);
/* It's possible to get here without allocating list */
return *list ? cnt + mcnt : 0;
}
static int type_completion(char ***list, char *match, int len)
{
static char *types[] = {"string" , "ustring", "x8", "x16", "x32", "x64",
"u8", "u16", "u32", "u64", "s8", "s16", "s32", "s64" };
char **words;
int i, x;
x = ARRAY_SIZE(types);
words = calloc(x, sizeof(char *));
if (!words)
return 0;
for (i = 0; i < x; i++) {
asprintf(&words[i], "%.*s.%s",
len, match, types[i]);
}
*list = words;
match[strlen(match)] = CCLI_NOSPACE;
return x;
}
static int offset_completion(char m, char ***list, char *match, int len)
{
char **words;
if (!m) {
words = calloc(1, sizeof(char *));
if (!words)
return 0;
asprintf(&words[0], "%.*s->",
len, match);
*list = words;
match[strlen(match)] = CCLI_NOSPACE;
return 1;
}
return 0;
}
static int kprobe_completion(struct ccli *ccli, void *data,
int argc, char **argv,
char ***list, int word, char *match)
{
char *p, *m;
int len;
int ret;
switch (word) {
case 0:
ccli_printf(ccli, "\n# Name the kprobe\n");
ccli_line_refresh(ccli);
return 0;
case 1:
if (!strlen(match)) {
ccli_printf(ccli, "\n# [mod:]function-name[+offset] | @memory-address\n");
ccli_line_refresh(ccli);
return 0;
}
if (match[0] == '@') {
ccli_printf(ccli, "\n# @memory-address\n");
ccli_line_refresh(ccli);
return 0;
}
if (strchr(match, '+')) {
ccli_printf(ccli, "\n# [mod:]function-name+offset\n");
ccli_line_refresh(ccli);
return 0;
}
ret = load_available_filter(list, match);
match[strlen(match)] = CCLI_NOSPACE;
return ret;
default:
p = strchr(match, '=');
if (!p) {
ccli_printf(ccli, "\n# var=ARG[.type][->offset[.type]\n");
ccli_line_refresh(ccli);
return 0;
}
m = p;
while (*p) {
if (*p == '.' || *p == '-')
m = p;
p++;
}
len = m - match;
switch (*m) {
case '.':
return type_completion(list, match, len);
case '-':
return offset_completion(m[1], list, match, len);
}
}
return 0;
}
static int event_completion(struct ccli *ccli, struct tep_handle *tep,
char ***list, char *match, char append)
{
char **systems;
char **events;
char **words;
char *system;
char *event;
char *p;
int i;
p = strchr(match, '/');
if (p) {
system = strdup(match);
if (!system)
return 0;
system[p - match] = '\0';
events = tracefs_system_events(NULL, system);
if (!events) {
free(system);
return 0;
}
words = calloc(tracefs_list_size(events), sizeof(char *));
i = 0;
if (words) {
for (; events[i]; i++) {
asprintf(&event, "%s/%s", system, events[i]);
if (append && event && !strcmp(event, match)) {
asprintf(words + i, "%s.", event);
free(event);
} else
words[i] = event;
}
}
tracefs_list_free(events);
} else {
systems = tracefs_event_systems(NULL);
if (!systems)
return 0;
words = calloc(tracefs_list_size(systems), sizeof(char *));
i = 0;
if (words) {
for (; systems[i]; i++)
words[i] = strdup(systems[i]);
}
tracefs_list_free(systems);
/* Use '/' as a delim */
match[strlen(match)] = '/';
}
*list = words;
return i;
}
static struct tep_event *find_event(struct tep_handle *tep, char *ename)
{
struct tep_event *event;
char *system;
char *name;
char *sav;
system = strtok_r(ename, "/", &sav);
name = strtok_r(NULL, "/", &sav);
if (!system || !name)
return NULL;
event = tep_find_event_by_name(tep, system, name);
/* put back ename to how we found it */
sav = system + strlen(system);
*sav = '/';
return event;
}
static int field_completion(struct tep_event *event, char ***list, char *prefix)
{
struct tep_format_field **common_fields;
struct tep_format_field **fields;
char **words;
char **tmp;
int i, x;
common_fields = tep_event_common_fields(event);
fields = tep_event_fields(event);
words = NULL;
x = 0;
for (i = 0; common_fields && common_fields[i]; i++) {
tmp = realloc(words, sizeof(char *) * (x + 2));
if (!tmp) {
ccli_argv_free(words);
return 0;
}
words = tmp;
asprintf(&words[x++], "%s%s", prefix, common_fields[i]->name);
words[x] = NULL;
}
for (i = 0; fields && fields[i]; i++) {
tmp = realloc(words, sizeof(char *) * (x + 2));
if (!tmp) {
ccli_argv_free(words);
return 0;
}
words = tmp;
asprintf(&words[x++], "%s%s", prefix, fields[i]->name);
words[x] = NULL;
}
free(common_fields);
free(fields);
*list = words;
return x;
}
static int eprobe_completion(struct ccli *ccli, void *data,
int argc, char **argv,
char ***list, int word, char *match)
{
struct tep_handle *tep = data;
struct tep_event *event;
char *prefix;
char *p, *m;
int len;
int ret;
switch (word) {
case 0:
ccli_printf(ccli, "\n# Name the event probe\n");
ccli_line_refresh(ccli);
return 0;
case 1:
return event_completion(ccli, tep, list, match, 0);
default:
event = find_event(tep, argv[1]);
if (!event) {
ccli_printf(ccli, "\n# Event %s not found\n", argv[1]);
return 0;
}
p = strchr(match, '=');
if (!p) {
ccli_printf(ccli, "\n# var=field[.type][->offset[.type]\n");
ccli_line_refresh(ccli);
return 0;
}
m = p;
while (*p) {
if (*p == '.' || *p == '-')
m = p;
p++;
}
len = m - match;
switch (*m) {
default:
asprintf(&prefix, "%.*s=", len, match);
if (!prefix)
return 0;
ret = field_completion(event, list, prefix);
free(prefix);
if (ret > 0)
match[strlen(match)] = CCLI_NOSPACE;
return ret;
case '.':
return type_completion(list, match, len);
case '-':
return offset_completion(m[1], list, match, len);
}
}
printf("\neprobe word=%d match=%s\n", word, match);
return 0;
}
static int append_event_field(struct ccli *ccli, struct tep_handle *tep, char ***list,
char *ename, char *match)
{
struct tep_event *event;
char *p;
/* Find the end of the event */
for (p = ename; *p && *p != '.'; p++)
;
if (!*p) // Should not happen!
return 0;
*p = '\0';
event = find_event(tep, ename);
if (!event) {
ccli_printf(ccli, "\n# Event %s not found\n", ename);
return 0;
}
*p = '.';
/* Find the start of the last field in the comma separated list */
for (p += strlen(p) - 1; *p != '.' && *p != ',' ; p--)
;
p++;
*p = '\0';
return field_completion(event, list, ename);
}
static int append_field_ts(struct ccli *ccli, struct tep_handle *tep, char ***list,
char *ename)
{
struct tep_event *event;
char **words;
char *e;
char *p;
int ret;
/* Find start of the event */
e = strchr(ename, '=');
if (!e) // should not happen
return 0;
e++;
/* Find the end of the event */
for (p = e; *p && *p != '.'; p++)
;
if (!*p) // Should not happen!
return 0;
*p = '\0';
event = find_event(tep, e);
if (!event) {
ccli_printf(ccli, "\n# Event %s not found\n", e);
ccli_line_refresh(ccli);
return 0;
}
*p = '.';
p[1] = '\0';
ret = field_completion(event, list, ename);
if (ret > 0) {
words = realloc(*list, sizeof(*words) * (ret + 1));
if (words) {
asprintf(words + ret, "%sTIMESTAMP", ename);
*list = words;
ret++;
}
}
return ret;
}
static int synthetic_completion(struct ccli *ccli, void *data,
int argc, char **argv,
char ***list, int word, char *match)
{
struct tep_handle *tep = data;
struct tep_event *event1, *event2;
char **words;
char *p;
int len;
int ret;
switch (word) {
case 0:
ccli_printf(ccli, "\n# Name the synthetic event\n");
ccli_line_refresh(ccli);
return 0;
case 1:
case 2:
len = strlen(match);
/* See if this already an event */
if (strchr(match, '.'))
return append_event_field(ccli, tep, list, argv[word], match);
ret = event_completion(ccli, tep, list, match, '.');
/* Do not add a space if this is an event */
if (ret > 0 && !match[len])
match[len] = CCLI_NOSPACE;
return ret;
default:
p = strchr(match, '=');
if (!p) {
ccli_printf(ccli, "\n# field=system/event.[field|TIMESTAMP]\n");
ccli_line_refresh(ccli);
return 0;
}
if (strchr(p, '.'))
return append_field_ts(ccli, tep, list, argv[word]);
argv[word][p - match] = '\0';
/* Can only be one of the previous events */
p = strchr(argv[1], '.');
if (!p) {
ccli_printf(ccli, "# %s needs a field\n", argv[1]);
ccli_line_refresh(ccli);
return 0;
}
*p = '\0';
event1 = find_event(tep, argv[1]);
if (!event1) {
ccli_printf(ccli, "\n# Event %s not found\n", argv[1]);
ccli_line_refresh(ccli);
return 0;
}
p = strchr(argv[2], '.');
if (!p) {
ccli_printf(ccli, "\n# %s needs a field\n", argv[2]);
ccli_line_refresh(ccli);
return 0;
}
*p = '\0';
event2 = find_event(tep, argv[2]);
if (!event2) {
ccli_printf(ccli, "\n# Event %s not found\n", argv[2]);
ccli_line_refresh(ccli);
return 0;
}
words = calloc(2, sizeof(*words));
if (!words)
return 0;
asprintf(words, "%s=%s/%s", argv[word],
event1->system, event1->name);
asprintf(words + 1, "%s=%s/%s", argv[word],
event2->system, event2->name);
*list = words;
match[strlen(match)] = '.';
return 2;
}
printf("\neprobe word=%d match=%s\n", word, match);
return 0;
}
int create_completion(struct ccli *ccli, const char *command,
const char *line, int word,
char *match, char ***list, void *data)
{
char *types[] = { "kprobe", "eprobe", "synthetic" };
char **words;
char **argv;
int argc;
int ret = 0;
int i;
if (word == 1) {
words = calloc(ARRAY_SIZE(types), sizeof(char *));
if (!words)
return 0;
for (i = 0; i < ARRAY_SIZE(types); i++)
words[i] = strdup(types[i]);
*list = words;
return i;
}
argc = ccli_line_parse(line, &argv);
if (argc < 0)
return 0;
if (strcmp(argv[1], "kprobe") == 0)
ret = kprobe_completion(ccli, data, argc - 2, argv + 2,
list, word - 2, match);
if (strcmp(argv[1], "eprobe") == 0)
ret = eprobe_completion(ccli, data, argc - 2, argv + 2,
list, word - 2, match);
if (strcmp(argv[1], "synthetic") == 0)
ret = synthetic_completion(ccli, data, argc - 2, argv + 2,
list, word - 2, match);
ccli_argv_free(argv);
return ret;
}