Skip to content

Commit

Permalink
perf trace: BTF-based enum pretty printing for syscall args
Browse files Browse the repository at this point in the history
In this patch, BTF is used to turn enum value to the corresponding
name. There is only one system call that uses enum value as its
argument, that is `landlock_add_rule()`.

The vmlinux btf is loaded lazily, when user decided to trace the
`landlock_add_rule` syscall. But if one decide to run `perf trace`
without any arguments, the behaviour is to trace `landlock_add_rule`,
so vmlinux btf will be loaded by default.

The laziest behaviour is to load vmlinux btf when a
`landlock_add_rule` syscall hits. But I think you could lose some
samples when loading vmlinux btf at run time, for it can delay the
handling of other samples. I might need your precious opinions on
this...

before:

```
perf $ ./perf trace -e landlock_add_rule
     0.000 ( 0.008 ms): ldlck-test/438194 landlock_add_rule(rule_type: 2) = -1 EBADFD (File descriptor in bad state)
     0.010 ( 0.001 ms): ldlck-test/438194 landlock_add_rule(rule_type: 1) = -1 EBADFD (File descriptor in bad state)
```

after:

```
perf $ ./perf trace -e landlock_add_rule
     0.000 ( 0.029 ms): ldlck-test/438194 landlock_add_rule(rule_type: LANDLOCK_RULE_NET_PORT)     = -1 EBADFD (File descriptor in bad state)
     0.036 ( 0.004 ms): ldlck-test/438194 landlock_add_rule(rule_type: LANDLOCK_RULE_PATH_BENEATH) = -1 EBADFD (File descriptor in bad state)
```

Committer notes:

Made it build with NO_LIBBPF=1, simplified btf_enum_fprintf(), see [1]
for the discussion.

Signed-off-by: Howard Chu <[email protected]>
Tested-by: Arnaldo Carvalho de Melo <[email protected]>
Cc: Adrian Hunter <[email protected]>
Cc: Alexander Shishkin <[email protected]>
Cc: Günther Noack <[email protected]>
Cc: Ian Rogers <[email protected]>
Cc: Ingo Molnar <[email protected]>
Cc: Jiri Olsa <[email protected]>
Cc: Kan Liang <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: Mickaël Salaün <[email protected]>
Cc: Namhyung Kim <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Link: https://lore.kernel.org/lkml/[email protected]
Link: https://lore.kernel.org/lkml/ZnXAhFflUl_LV1QY@x1 # [1]
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
  • Loading branch information
Sberm authored and acmel committed Jul 31, 2024
1 parent e4fc196 commit 45a0c92
Showing 1 changed file with 106 additions and 4 deletions.
110 changes: 106 additions & 4 deletions tools/perf/builtin-trace.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#ifdef HAVE_LIBBPF_SUPPORT
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <bpf/btf.h>
#ifdef HAVE_BPF_SKEL
#include "bpf_skel/augmented_raw_syscalls.skel.h"
#endif
Expand Down Expand Up @@ -110,6 +111,10 @@ struct syscall_arg_fmt {
const char *name;
u16 nr_entries; // for arrays
bool show_zero;
bool is_enum;
#ifdef HAVE_LIBBPF_SUPPORT
const struct btf_type *type;
#endif
};

struct syscall_fmt {
Expand Down Expand Up @@ -139,6 +144,9 @@ struct trace {
} syscalls;
#ifdef HAVE_BPF_SKEL
struct augmented_raw_syscalls_bpf *skel;
#endif
#ifdef HAVE_LIBBPF_SUPPORT
struct btf *btf;
#endif
struct record_opts opts;
struct evlist *evlist;
Expand Down Expand Up @@ -204,6 +212,20 @@ struct trace {
} oe;
};

static void trace__load_vmlinux_btf(struct trace *trace __maybe_unused)
{
#ifdef HAVE_LIBBPF_SUPPORT
if (trace->btf != NULL)
return;

trace->btf = btf__load_vmlinux_btf();
if (verbose > 0) {
fprintf(trace->output, trace->btf ? "vmlinux BTF loaded\n" :
"Failed to load vmlinux BTF\n");
}
#endif
}

struct tp_field {
int offset;
union {
Expand Down Expand Up @@ -887,6 +909,64 @@ static size_t syscall_arg__scnprintf_getrandom_flags(char *bf, size_t size,

#define SCA_GETRANDOM_FLAGS syscall_arg__scnprintf_getrandom_flags

#ifdef HAVE_LIBBPF_SUPPORT
static int syscall_arg_fmt__cache_btf_enum(struct syscall_arg_fmt *arg_fmt, struct btf *btf, char *type)
{
int id;

// Already cached?
if (arg_fmt->type != NULL)
return 0;

type = strstr(type, "enum ");
if (type == NULL)
return -1;

type += 5; // skip "enum " to get the enumeration name

id = btf__find_by_name(btf, type);
if (id < 0)
return -1;

arg_fmt->type = btf__type_by_id(btf, id);
return arg_fmt->type == NULL ? -1 : 0;
}

static size_t btf_enum_scnprintf(const struct btf_type *type, struct btf *btf, char *bf, size_t size, int val)
{
struct btf_enum *be = btf_enum(type);
const int nr_entries = btf_vlen(type);

for (int i = 0; i < nr_entries; ++i, ++be) {
if (be->val == val) {
return scnprintf(bf, size, "%s",
btf__name_by_offset(btf, be->name_off));
}
}

return 0;
}

static size_t trace__btf_enum_scnprintf(struct trace *trace, struct syscall_arg_fmt *arg_fmt, char *bf,
size_t size, int val, char *type)
{
if (trace->btf == NULL)
return 0;

if (syscall_arg_fmt__cache_btf_enum(arg_fmt, trace->btf, type) < 0)
return 0;

return btf_enum_scnprintf(arg_fmt->type, trace->btf, bf, size, val);
}
#else // HAVE_LIBBPF_SUPPORT
static size_t trace__btf_enum_scnprintf(struct trace *trace __maybe_unused, struct syscall_arg_fmt *arg_fmt __maybe_unused,
char *bf __maybe_unused, size_t size __maybe_unused, int val __maybe_unused,
char *type __maybe_unused)
{
return 0;
}
#endif // HAVE_LIBBPF_SUPPORT

#define STRARRAY(name, array) \
{ .scnprintf = SCA_STRARRAY, \
.strtoul = STUL_STRARRAY, \
Expand Down Expand Up @@ -1238,6 +1318,7 @@ struct syscall {
bool is_exit;
bool is_open;
bool nonexistent;
bool use_btf;
struct tep_format_field *args;
const char *name;
const struct syscall_fmt *fmt;
Expand Down Expand Up @@ -1744,7 +1825,8 @@ static const struct syscall_arg_fmt *syscall_arg_fmt__find_by_name(const char *n
}

static struct tep_format_field *
syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field *field)
syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field *field,
bool *use_btf)
{
struct tep_format_field *last_field = NULL;
int len;
Expand All @@ -1756,6 +1838,7 @@ syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field
continue;

len = strlen(field->name);
arg->is_enum = false;

if (strcmp(field->type, "const char *") == 0 &&
((len >= 4 && strcmp(field->name + len - 4, "name") == 0) ||
Expand All @@ -1782,6 +1865,8 @@ syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field
* 7 unsigned long
*/
arg->scnprintf = SCA_FD;
} else if (strstr(field->type, "enum") && use_btf != NULL) {
*use_btf = arg->is_enum = true;
} else {
const struct syscall_arg_fmt *fmt =
syscall_arg_fmt__find_by_name(field->name);
Expand All @@ -1798,7 +1883,8 @@ syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field

static int syscall__set_arg_fmts(struct syscall *sc)
{
struct tep_format_field *last_field = syscall_arg_fmt__init_array(sc->arg_fmt, sc->args);
struct tep_format_field *last_field = syscall_arg_fmt__init_array(sc->arg_fmt, sc->args,
&sc->use_btf);

if (last_field)
sc->args_size = last_field->offset + last_field->size;
Expand All @@ -1811,6 +1897,7 @@ static int trace__read_syscall_info(struct trace *trace, int id)
char tp_name[128];
struct syscall *sc;
const char *name = syscalltbl__name(trace->sctbl, id);
int err;

#ifdef HAVE_SYSCALL_TABLE_SUPPORT
if (trace->syscalls.table == NULL) {
Expand Down Expand Up @@ -1883,15 +1970,21 @@ static int trace__read_syscall_info(struct trace *trace, int id)
sc->is_exit = !strcmp(name, "exit_group") || !strcmp(name, "exit");
sc->is_open = !strcmp(name, "open") || !strcmp(name, "openat");

return syscall__set_arg_fmts(sc);
err = syscall__set_arg_fmts(sc);

/* after calling syscall__set_arg_fmts() we'll know whether use_btf is true */
if (sc->use_btf)
trace__load_vmlinux_btf(trace);

return err;
}

static int evsel__init_tp_arg_scnprintf(struct evsel *evsel)
{
struct syscall_arg_fmt *fmt = evsel__syscall_arg_fmt(evsel);

if (fmt != NULL) {
syscall_arg_fmt__init_array(fmt, evsel->tp_format->format.fields);
syscall_arg_fmt__init_array(fmt, evsel->tp_format->format.fields, NULL);
return 0;
}

Expand Down Expand Up @@ -2103,6 +2196,15 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size,
if (trace->show_arg_names)
printed += scnprintf(bf + printed, size - printed, "%s: ", field->name);

if (sc->arg_fmt[arg.idx].is_enum) {
size_t p = trace__btf_enum_scnprintf(trace, &sc->arg_fmt[arg.idx], bf + printed,
size - printed, val, field->type);
if (p) {
printed += p;
continue;
}
}

printed += syscall_arg_fmt__scnprintf_val(&sc->arg_fmt[arg.idx],
bf + printed, size - printed, &arg, val);
}
Expand Down

0 comments on commit 45a0c92

Please sign in to comment.