| /* |
| * builtin-trace.c |
| * |
| * Builtin 'trace' command: |
| * |
| * Display a continuously updated trace of any workload, CPU, specific PID, |
| * system wide, etc. Default format is loosely strace like, but any other |
| * event may be specified using --event. |
| * |
| * Copyright (C) 2012, 2013, 2014, 2015 Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com> |
| * |
| * Initially based on the 'trace' prototype by Thomas Gleixner: |
| * |
| * http://lwn.net/Articles/415728/ ("Announcing a new utility: 'trace'") |
| */ |
| |
| #include "util/record.h" |
| #include <traceevent/event-parse.h> |
| #include <api/fs/tracing_path.h> |
| #include <bpf/bpf.h> |
| #include "util/bpf_map.h" |
| #include "util/rlimit.h" |
| #include "builtin.h" |
| #include "util/cgroup.h" |
| #include "util/color.h" |
| #include "util/config.h" |
| #include "util/debug.h" |
| #include "util/dso.h" |
| #include "util/env.h" |
| #include "util/event.h" |
| #include "util/evsel.h" |
| #include "util/evsel_fprintf.h" |
| #include "util/synthetic-events.h" |
| #include "util/evlist.h" |
| #include "util/evswitch.h" |
| #include "util/mmap.h" |
| #include <subcmd/pager.h> |
| #include <subcmd/exec-cmd.h> |
| #include "util/machine.h" |
| #include "util/map.h" |
| #include "util/symbol.h" |
| #include "util/path.h" |
| #include "util/session.h" |
| #include "util/thread.h" |
| #include <subcmd/parse-options.h> |
| #include "util/strlist.h" |
| #include "util/intlist.h" |
| #include "util/thread_map.h" |
| #include "util/stat.h" |
| #include "util/tool.h" |
| #include "util/util.h" |
| #include "trace/beauty/beauty.h" |
| #include "trace-event.h" |
| #include "util/parse-events.h" |
| #include "util/bpf-loader.h" |
| #include "callchain.h" |
| #include "print_binary.h" |
| #include "string2.h" |
| #include "syscalltbl.h" |
| #include "rb_resort.h" |
| #include "../perf.h" |
| |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <poll.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <linux/err.h> |
| #include <linux/filter.h> |
| #include <linux/kernel.h> |
| #include <linux/random.h> |
| #include <linux/stringify.h> |
| #include <linux/time64.h> |
| #include <linux/zalloc.h> |
| #include <fcntl.h> |
| #include <sys/sysmacros.h> |
| |
| #include <linux/ctype.h> |
| #include <perf/mmap.h> |
| |
| #ifndef O_CLOEXEC |
| # define O_CLOEXEC 02000000 |
| #endif |
| |
| #ifndef F_LINUX_SPECIFIC_BASE |
| # define F_LINUX_SPECIFIC_BASE 1024 |
| #endif |
| |
| /* |
| * strtoul: Go from a string to a value, i.e. for msr: MSR_FS_BASE to 0xc0000100 |
| */ |
| struct syscall_arg_fmt { |
| size_t (*scnprintf)(char *bf, size_t size, struct syscall_arg *arg); |
| bool (*strtoul)(char *bf, size_t size, struct syscall_arg *arg, u64 *val); |
| unsigned long (*mask_val)(struct syscall_arg *arg, unsigned long val); |
| void *parm; |
| const char *name; |
| u16 nr_entries; // for arrays |
| bool show_zero; |
| }; |
| |
| struct syscall_fmt { |
| const char *name; |
| const char *alias; |
| struct { |
| const char *sys_enter, |
| *sys_exit; |
| } bpf_prog_name; |
| struct syscall_arg_fmt arg[6]; |
| u8 nr_args; |
| bool errpid; |
| bool timeout; |
| bool hexret; |
| }; |
| |
| struct trace { |
| struct perf_tool tool; |
| struct syscalltbl *sctbl; |
| struct { |
| struct syscall *table; |
| struct bpf_map *map; |
| struct { // per syscall BPF_MAP_TYPE_PROG_ARRAY |
| struct bpf_map *sys_enter, |
| *sys_exit; |
| } prog_array; |
| struct { |
| struct evsel *sys_enter, |
| *sys_exit, |
| *augmented; |
| } events; |
| struct bpf_program *unaugmented_prog; |
| } syscalls; |
| struct { |
| struct bpf_map *map; |
| } dump; |
| struct record_opts opts; |
| struct evlist *evlist; |
| struct machine *host; |
| struct thread *current; |
| struct bpf_object *bpf_obj; |
| struct cgroup *cgroup; |
| u64 base_time; |
| FILE *output; |
| unsigned long nr_events; |
| unsigned long nr_events_printed; |
| unsigned long max_events; |
| struct evswitch evswitch; |
| struct strlist *ev_qualifier; |
| struct { |
| size_t nr; |
| int *entries; |
| } ev_qualifier_ids; |
| struct { |
| size_t nr; |
| pid_t *entries; |
| struct bpf_map *map; |
| } filter_pids; |
| double duration_filter; |
| double runtime_ms; |
| struct { |
| u64 vfs_getname, |
| proc_getname; |
| } stats; |
| unsigned int max_stack; |
| unsigned int min_stack; |
| int raw_augmented_syscalls_args_size; |
| bool raw_augmented_syscalls; |
| bool fd_path_disabled; |
| bool sort_events; |
| bool not_ev_qualifier; |
| bool live; |
| bool full_time; |
| bool sched; |
| bool multiple_threads; |
| bool summary; |
| bool summary_only; |
| bool errno_summary; |
| bool failure_only; |
| bool show_comm; |
| bool print_sample; |
| bool show_tool_stats; |
| bool trace_syscalls; |
| bool libtraceevent_print; |
| bool kernel_syscallchains; |
| s16 args_alignment; |
| bool show_tstamp; |
| bool show_duration; |
| bool show_zeros; |
| bool show_arg_names; |
| bool show_string_prefix; |
| bool force; |
| bool vfs_getname; |
| int trace_pgfaults; |
| char *perfconfig_events; |
| struct { |
| struct ordered_events data; |
| u64 last; |
| } oe; |
| }; |
| |
| struct tp_field { |
| int offset; |
| union { |
| u64 (*integer)(struct tp_field *field, struct perf_sample *sample); |
| void *(*pointer)(struct tp_field *field, struct perf_sample *sample); |
| }; |
| }; |
| |
| #define TP_UINT_FIELD(bits) \ |
| static u64 tp_field__u##bits(struct tp_field *field, struct perf_sample *sample) \ |
| { \ |
| u##bits value; \ |
| memcpy(&value, sample->raw_data + field->offset, sizeof(value)); \ |
| return value; \ |
| } |
| |
| TP_UINT_FIELD(8); |
| TP_UINT_FIELD(16); |
| TP_UINT_FIELD(32); |
| TP_UINT_FIELD(64); |
| |
| #define TP_UINT_FIELD__SWAPPED(bits) \ |
| static u64 tp_field__swapped_u##bits(struct tp_field *field, struct perf_sample *sample) \ |
| { \ |
| u##bits value; \ |
| memcpy(&value, sample->raw_data + field->offset, sizeof(value)); \ |
| return bswap_##bits(value);\ |
| } |
| |
| TP_UINT_FIELD__SWAPPED(16); |
| TP_UINT_FIELD__SWAPPED(32); |
| TP_UINT_FIELD__SWAPPED(64); |
| |
| static int __tp_field__init_uint(struct tp_field *field, int size, int offset, bool needs_swap) |
| { |
| field->offset = offset; |
| |
| switch (size) { |
| case 1: |
| field->integer = tp_field__u8; |
| break; |
| case 2: |
| field->integer = needs_swap ? tp_field__swapped_u16 : tp_field__u16; |
| break; |
| case 4: |
| field->integer = needs_swap ? tp_field__swapped_u32 : tp_field__u32; |
| break; |
| case 8: |
| field->integer = needs_swap ? tp_field__swapped_u64 : tp_field__u64; |
| break; |
| default: |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int tp_field__init_uint(struct tp_field *field, struct tep_format_field *format_field, bool needs_swap) |
| { |
| return __tp_field__init_uint(field, format_field->size, format_field->offset, needs_swap); |
| } |
| |
| static void *tp_field__ptr(struct tp_field *field, struct perf_sample *sample) |
| { |
| return sample->raw_data + field->offset; |
| } |
| |
| static int __tp_field__init_ptr(struct tp_field *field, int offset) |
| { |
| field->offset = offset; |
| field->pointer = tp_field__ptr; |
| return 0; |
| } |
| |
| static int tp_field__init_ptr(struct tp_field *field, struct tep_format_field *format_field) |
| { |
| return __tp_field__init_ptr(field, format_field->offset); |
| } |
| |
| struct syscall_tp { |
| struct tp_field id; |
| union { |
| struct tp_field args, ret; |
| }; |
| }; |
| |
| /* |
| * The evsel->priv as used by 'perf trace' |
| * sc: for raw_syscalls:sys_{enter,exit} and syscalls:sys_{enter,exit}_SYSCALLNAME |
| * fmt: for all the other tracepoints |
| */ |
| struct evsel_trace { |
| struct syscall_tp sc; |
| struct syscall_arg_fmt *fmt; |
| }; |
| |
| static struct evsel_trace *evsel_trace__new(void) |
| { |
| return zalloc(sizeof(struct evsel_trace)); |
| } |
| |
| static void evsel_trace__delete(struct evsel_trace *et) |
| { |
| if (et == NULL) |
| return; |
| |
| zfree(&et->fmt); |
| free(et); |
| } |
| |
| /* |
| * Used with raw_syscalls:sys_{enter,exit} and with the |
| * syscalls:sys_{enter,exit}_SYSCALL tracepoints |
| */ |
| static inline struct syscall_tp *__evsel__syscall_tp(struct evsel *evsel) |
| { |
| struct evsel_trace *et = evsel->priv; |
| |
| return &et->sc; |
| } |
| |
| static struct syscall_tp *evsel__syscall_tp(struct evsel *evsel) |
| { |
| if (evsel->priv == NULL) { |
| evsel->priv = evsel_trace__new(); |
| if (evsel->priv == NULL) |
| return NULL; |
| } |
| |
| return __evsel__syscall_tp(evsel); |
| } |
| |
| /* |
| * Used with all the other tracepoints. |
| */ |
| static inline struct syscall_arg_fmt *__evsel__syscall_arg_fmt(struct evsel *evsel) |
| { |
| struct evsel_trace *et = evsel->priv; |
| |
| return et->fmt; |
| } |
| |
| static struct syscall_arg_fmt *evsel__syscall_arg_fmt(struct evsel *evsel) |
| { |
| struct evsel_trace *et = evsel->priv; |
| |
| if (evsel->priv == NULL) { |
| et = evsel->priv = evsel_trace__new(); |
| |
| if (et == NULL) |
| return NULL; |
| } |
| |
| if (et->fmt == NULL) { |
| et->fmt = calloc(evsel->tp_format->format.nr_fields, sizeof(struct syscall_arg_fmt)); |
| if (et->fmt == NULL) |
| goto out_delete; |
| } |
| |
| return __evsel__syscall_arg_fmt(evsel); |
| |
| out_delete: |
| evsel_trace__delete(evsel->priv); |
| evsel->priv = NULL; |
| return NULL; |
| } |
| |
| static int evsel__init_tp_uint_field(struct evsel *evsel, struct tp_field *field, const char *name) |
| { |
| struct tep_format_field *format_field = evsel__field(evsel, name); |
| |
| if (format_field == NULL) |
| return -1; |
| |
| return tp_field__init_uint(field, format_field, evsel->needs_swap); |
| } |
| |
| #define perf_evsel__init_sc_tp_uint_field(evsel, name) \ |
| ({ struct syscall_tp *sc = __evsel__syscall_tp(evsel);\ |
| evsel__init_tp_uint_field(evsel, &sc->name, #name); }) |
| |
| static int evsel__init_tp_ptr_field(struct evsel *evsel, struct tp_field *field, const char *name) |
| { |
| struct tep_format_field *format_field = evsel__field(evsel, name); |
| |
| if (format_field == NULL) |
| return -1; |
| |
| return tp_field__init_ptr(field, format_field); |
| } |
| |
| #define perf_evsel__init_sc_tp_ptr_field(evsel, name) \ |
| ({ struct syscall_tp *sc = __evsel__syscall_tp(evsel);\ |
| evsel__init_tp_ptr_field(evsel, &sc->name, #name); }) |
| |
| static void evsel__delete_priv(struct evsel *evsel) |
| { |
| zfree(&evsel->priv); |
| evsel__delete(evsel); |
| } |
| |
| static int evsel__init_syscall_tp(struct evsel *evsel) |
| { |
| struct syscall_tp *sc = evsel__syscall_tp(evsel); |
| |
| if (sc != NULL) { |
| if (evsel__init_tp_uint_field(evsel, &sc->id, "__syscall_nr") && |
| evsel__init_tp_uint_field(evsel, &sc->id, "nr")) |
| return -ENOENT; |
| return 0; |
| } |
| |
| return -ENOMEM; |
| } |
| |
| static int evsel__init_augmented_syscall_tp(struct evsel *evsel, struct evsel *tp) |
| { |
| struct syscall_tp *sc = evsel__syscall_tp(evsel); |
| |
| if (sc != NULL) { |
| struct tep_format_field *syscall_id = evsel__field(tp, "id"); |
| if (syscall_id == NULL) |
| syscall_id = evsel__field(tp, "__syscall_nr"); |
| if (syscall_id == NULL || |
| __tp_field__init_uint(&sc->id, syscall_id->size, syscall_id->offset, evsel->needs_swap)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| return -ENOMEM; |
| } |
| |
| static int evsel__init_augmented_syscall_tp_args(struct evsel *evsel) |
| { |
| struct syscall_tp *sc = __evsel__syscall_tp(evsel); |
| |
| return __tp_field__init_ptr(&sc->args, sc->id.offset + sizeof(u64)); |
| } |
| |
| static int evsel__init_augmented_syscall_tp_ret(struct evsel *evsel) |
| { |
| struct syscall_tp *sc = __evsel__syscall_tp(evsel); |
| |
| return __tp_field__init_uint(&sc->ret, sizeof(u64), sc->id.offset + sizeof(u64), evsel->needs_swap); |
| } |
| |
| static int evsel__init_raw_syscall_tp(struct evsel *evsel, void *handler) |
| { |
| if (evsel__syscall_tp(evsel) != NULL) { |
| if (perf_evsel__init_sc_tp_uint_field(evsel, id)) |
| return -ENOENT; |
| |
| evsel->handler = handler; |
| return 0; |
| } |
| |
| return -ENOMEM; |
| } |
| |
| static struct evsel *perf_evsel__raw_syscall_newtp(const char *direction, void *handler) |
| { |
| struct evsel *evsel = evsel__newtp("raw_syscalls", direction); |
| |
| /* older kernel (e.g., RHEL6) use syscalls:{enter,exit} */ |
| if (IS_ERR(evsel)) |
| evsel = evsel__newtp("syscalls", direction); |
| |
| if (IS_ERR(evsel)) |
| return NULL; |
| |
| if (evsel__init_raw_syscall_tp(evsel, handler)) |
| goto out_delete; |
| |
| return evsel; |
| |
| out_delete: |
| evsel__delete_priv(evsel); |
| return NULL; |
| } |
| |
| #define perf_evsel__sc_tp_uint(evsel, name, sample) \ |
| ({ struct syscall_tp *fields = __evsel__syscall_tp(evsel); \ |
| fields->name.integer(&fields->name, sample); }) |
| |
| #define perf_evsel__sc_tp_ptr(evsel, name, sample) \ |
| ({ struct syscall_tp *fields = __evsel__syscall_tp(evsel); \ |
| fields->name.pointer(&fields->name, sample); }) |
| |
| size_t strarray__scnprintf_suffix(struct strarray *sa, char *bf, size_t size, const char *intfmt, bool show_suffix, int val) |
| { |
| int idx = val - sa->offset; |
| |
| if (idx < 0 || idx >= sa->nr_entries || sa->entries[idx] == NULL) { |
| size_t printed = scnprintf(bf, size, intfmt, val); |
| if (show_suffix) |
| printed += scnprintf(bf + printed, size - printed, " /* %s??? */", sa->prefix); |
| return printed; |
| } |
| |
| return scnprintf(bf, size, "%s%s", sa->entries[idx], show_suffix ? sa->prefix : ""); |
| } |
| |
| size_t strarray__scnprintf(struct strarray *sa, char *bf, size_t size, const char *intfmt, bool show_prefix, int val) |
| { |
| int idx = val - sa->offset; |
| |
| if (idx < 0 || idx >= sa->nr_entries || sa->entries[idx] == NULL) { |
| size_t printed = scnprintf(bf, size, intfmt, val); |
| if (show_prefix) |
| printed += scnprintf(bf + printed, size - printed, " /* %s??? */", sa->prefix); |
| return printed; |
| } |
| |
| return scnprintf(bf, size, "%s%s", show_prefix ? sa->prefix : "", sa->entries[idx]); |
| } |
| |
| static size_t __syscall_arg__scnprintf_strarray(char *bf, size_t size, |
| const char *intfmt, |
| struct syscall_arg *arg) |
| { |
| return strarray__scnprintf(arg->parm, bf, size, intfmt, arg->show_string_prefix, arg->val); |
| } |
| |
| static size_t syscall_arg__scnprintf_strarray(char *bf, size_t size, |
| struct syscall_arg *arg) |
| { |
| return __syscall_arg__scnprintf_strarray(bf, size, "%d", arg); |
| } |
| |
| #define SCA_STRARRAY syscall_arg__scnprintf_strarray |
| |
| bool syscall_arg__strtoul_strarray(char *bf, size_t size, struct syscall_arg *arg, u64 *ret) |
| { |
| return strarray__strtoul(arg->parm, bf, size, ret); |
| } |
| |
| bool syscall_arg__strtoul_strarray_flags(char *bf, size_t size, struct syscall_arg *arg, u64 *ret) |
| { |
| return strarray__strtoul_flags(arg->parm, bf, size, ret); |
| } |
| |
| bool syscall_arg__strtoul_strarrays(char *bf, size_t size, struct syscall_arg *arg, u64 *ret) |
| { |
| return strarrays__strtoul(arg->parm, bf, size, ret); |
| } |
| |
| size_t syscall_arg__scnprintf_strarray_flags(char *bf, size_t size, struct syscall_arg *arg) |
| { |
| return strarray__scnprintf_flags(arg->parm, bf, size, arg->show_string_prefix, arg->val); |
| } |
| |
| size_t strarrays__scnprintf(struct strarrays *sas, char *bf, size_t size, const char *intfmt, bool show_prefix, int val) |
| { |
| size_t printed; |
| int i; |
| |
| for (i = 0; i < sas->nr_entries; ++i) { |
| struct strarray *sa = sas->entries[i]; |
| int idx = val - sa->offset; |
| |
| if (idx >= 0 && idx < sa->nr_entries) { |
| if (sa->entries[idx] == NULL) |
| break; |
| return scnprintf(bf, size, "%s%s", show_prefix ? sa->prefix : "", sa->entries[idx]); |
| } |
| } |
| |
| printed = scnprintf(bf, size, intfmt, val); |
| if (show_prefix) |
| printed += scnprintf(bf + printed, size - printed, " /* %s??? */", sas->entries[0]->prefix); |
| return printed; |
| } |
| |
| bool strarray__strtoul(struct strarray *sa, char *bf, size_t size, u64 *ret) |
| { |
| int i; |
| |
| for (i = 0; i < sa->nr_entries; ++i) { |
| if (sa->entries[i] && strncmp(sa->entries[i], bf, size) == 0 && sa->entries[i][size] == '\0') { |
| *ret = sa->offset + i; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool strarray__strtoul_flags(struct strarray *sa, char *bf, size_t size, u64 *ret) |
| { |
| u64 val = 0; |
| char *tok = bf, *sep, *end; |
| |
| *ret = 0; |
| |
| while (size != 0) { |
| int toklen = size; |
| |
| sep = memchr(tok, '|', size); |
| if (sep != NULL) { |
| size -= sep - tok + 1; |
| |
| end = sep - 1; |
| while (end > tok && isspace(*end)) |
| --end; |
| |
| toklen = end - tok + 1; |
| } |
| |
| while (isspace(*tok)) |
| ++tok; |
| |
| if (isalpha(*tok) || *tok == '_') { |
| if (!strarray__strtoul(sa, tok, toklen, &val)) |
| return false; |
| } else { |
| bool is_hexa = tok[0] == 0 && (tok[1] = 'x' || tok[1] == 'X'); |
| |
| val = strtoul(tok, NULL, is_hexa ? 16 : 0); |
| } |
| |
| *ret |= (1 << (val - 1)); |
| |
| if (sep == NULL) |
| break; |
| tok = sep + 1; |
| } |
| |
| return true; |
| } |
| |
| bool strarrays__strtoul(struct strarrays *sas, char *bf, size_t size, u64 *ret) |
| { |
| int i; |
| |
| for (i = 0; i < sas->nr_entries; ++i) { |
| struct strarray *sa = sas->entries[i]; |
| |
| if (strarray__strtoul(sa, bf, size, ret)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| size_t syscall_arg__scnprintf_strarrays(char *bf, size_t size, |
| struct syscall_arg *arg) |
| { |
| return strarrays__scnprintf(arg->parm, bf, size, "%d", arg->show_string_prefix, arg->val); |
| } |
| |
| #ifndef AT_FDCWD |
| #define AT_FDCWD -100 |
| #endif |
| |
| static size_t syscall_arg__scnprintf_fd_at(char *bf, size_t size, |
| struct syscall_arg *arg) |
| { |
| int fd = arg->val; |
| const char *prefix = "AT_FD"; |
| |
| if (fd == AT_FDCWD) |
| return scnprintf(bf, size, "%s%s", arg->show_string_prefix ? prefix : "", "CWD"); |
| |
| return syscall_arg__scnprintf_fd(bf, size, arg); |
| } |
| |
| #define SCA_FDAT syscall_arg__scnprintf_fd_at |
| |
| static size_t syscall_arg__scnprintf_close_fd(char *bf, size_t size, |
| struct syscall_arg *arg); |
| |
| #define SCA_CLOSE_FD syscall_arg__scnprintf_close_fd |
| |
| size_t syscall_arg__scnprintf_hex(char *bf, size_t size, struct syscall_arg *arg) |
| { |
| return scnprintf(bf, size, "%#lx", arg->val); |
| } |
| |
| size_t syscall_arg__scnprintf_ptr(char *bf, size_t size, struct syscall_arg *arg) |
| { |
| if (arg->val == 0) |
| return scnprintf(bf, size, "NULL"); |
| return syscall_arg__scnprintf_hex(bf, size, arg); |
| } |
| |
| size_t syscall_arg__scnprintf_int(char *bf, size_t size, struct syscall_arg *arg) |
| { |
| return scnprintf(bf, size, "%d", arg->val); |
| } |
| |
| size_t syscall_arg__scnprintf_long(char *bf, size_t size, struct syscall_arg *arg) |
| { |
| return scnprintf(bf, size, "%ld", arg->val); |
| } |
| |
| static size_t syscall_arg__scnprintf_char_array(char *bf, size_t size, struct syscall_arg *arg) |
| { |
| // XXX Hey, maybe for sched:sched_switch prev/next comm fields we can |
| // fill missing comms using thread__set_comm()... |
| // here or in a special syscall_arg__scnprintf_pid_sched_tp... |
| return scnprintf(bf, size, "\"%-.*s\"", arg->fmt->nr_entries ?: arg->len, arg->val); |
| } |
| |
| #define SCA_CHAR_ARRAY syscall_arg__scnprintf_char_array |
| |
| static const char *bpf_cmd[] = { |
| "MAP_CREATE", "MAP_LOOKUP_ELEM", "MAP_UPDATE_ELEM", "MAP_DELETE_ELEM", |
| "MAP_GET_NEXT_KEY", "PROG_LOAD", "OBJ_PIN", "OBJ_GET", "PROG_ATTACH", |
| "PROG_DETACH", "PROG_TEST_RUN", "PROG_GET_NEXT_ID", "MAP_GET_NEXT_ID", |
| "PROG_GET_FD_BY_ID", "MAP_GET_FD_BY_ID", "OBJ_GET_INFO_BY_FD", |
| "PROG_QUERY", "RAW_TRACEPOINT_OPEN", "BTF_LOAD", "BTF_GET_FD_BY_ID", |
| "TASK_FD_QUERY", "MAP_LOOKUP_AND_DELETE_ELEM", "MAP_FREEZE", |
| "BTF_GET_NEXT_ID", "MAP_LOOKUP_BATCH", "MAP_LOOKUP_AND_DELETE_BATCH", |
| "MAP_UPDATE_BATCH", "MAP_DELETE_BATCH", "LINK_CREATE", "LINK_UPDATE", |
| "LINK_GET_FD_BY_ID", "LINK_GET_NEXT_ID", "ENABLE_STATS", "ITER_CREATE", |
| "LINK_DETACH", "PROG_BIND_MAP", |
| }; |
| static DEFINE_STRARRAY(bpf_cmd, "BPF_"); |
| |
| static const char *fsmount_flags[] = { |
| [1] = "CLOEXEC", |
| }; |
| static DEFINE_STRARRAY(fsmount_flags, "FSMOUNT_"); |
| |
| #include "trace/beauty/generated/fsconfig_arrays.c" |
| |
| static DEFINE_STRARRAY(fsconfig_cmds, "FSCONFIG_"); |
| |
| static const char *epoll_ctl_ops[] = { "ADD", "DEL", "MOD", }; |
| static DEFINE_STRARRAY_OFFSET(epoll_ctl_ops, "EPOLL_CTL_", 1); |
| |
| static const char *itimers[] = { "REAL", "VIRTUAL", "PROF", }; |
| static DEFINE_STRARRAY(itimers, "ITIMER_"); |
| |
| static const char *keyctl_options[] = { |
| "GET_KEYRING_ID", "JOIN_SESSION_KEYRING", "UPDATE", "REVOKE", "CHOWN", |
| "SETPERM", "DESCRIBE", "CLEAR", "LINK", "UNLINK", "SEARCH", "READ", |
| "INSTANTIATE", "NEGATE", "SET_REQKEY_KEYRING", "SET_TIMEOUT", |
| "ASSUME_AUTHORITY", "GET_SECURITY", "SESSION_TO_PARENT", "REJECT", |
| "INSTANTIATE_IOV", "INVALIDATE", "GET_PERSISTENT", |
| }; |
| static DEFINE_STRARRAY(keyctl_options, "KEYCTL_"); |
| |
| static const char *whences[] = { "SET", "CUR", "END", |
| #ifdef SEEK_DATA |
| "DATA", |
| #endif |
| #ifdef SEEK_HOLE |
| "HOLE", |
| #endif |
| }; |
| static DEFINE_STRARRAY(whences, "SEEK_"); |
| |
| static const char *fcntl_cmds[] = { |
| "DUPFD", "GETFD", "SETFD", "GETFL", "SETFL", "GETLK", "SETLK", |
| "SETLKW", "SETOWN", "GETOWN", "SETSIG", "GETSIG", "GETLK64", |
| "SETLK64", "SETLKW64", "SETOWN_EX", "GETOWN_EX", |
| "GETOWNER_UIDS", |
| }; |
| static DEFINE_STRARRAY(fcntl_cmds, "F_"); |
| |
| static const char *fcntl_linux_specific_cmds[] = { |
| "SETLEASE", "GETLEASE", "NOTIFY", [5] = "CANCELLK", "DUPFD_CLOEXEC", |
| "SETPIPE_SZ", "GETPIPE_SZ", "ADD_SEALS", "GET_SEALS", |
| "GET_RW_HINT", "SET_RW_HINT", "GET_FILE_RW_HINT", "SET_FILE_RW_HINT", |
| }; |
| |
| static DEFINE_STRARRAY_OFFSET(fcntl_linux_specific_cmds, "F_", F_LINUX_SPECIFIC_BASE); |
| |
| static struct strarray *fcntl_cmds_arrays[] = { |
| &strarray__fcntl_cmds, |
| &strarray__fcntl_linux_specific_cmds, |
| }; |
| |
| static DEFINE_STRARRAYS(fcntl_cmds_arrays); |
| |
| static const char *rlimit_resources[] = { |
| "CPU", "FSIZE", "DATA", "STACK", "CORE", "RSS", "NPROC", "NOFILE", |
| "MEMLOCK", "AS", "LOCKS", "SIGPENDING", "MSGQUEUE", "NICE", "RTPRIO", |
| "RTTIME", |
| }; |
| static DEFINE_STRARRAY(rlimit_resources, "RLIMIT_"); |
| |
| static const char *sighow[] = { "BLOCK", "UNBLOCK", "SETMASK", }; |
| static DEFINE_STRARRAY(sighow, "SIG_"); |
| |
| static const char *clockid[] = { |
| "REALTIME", "MONOTONIC", "PROCESS_CPUTIME_ID", "THREAD_CPUTIME_ID", |
| "MONOTONIC_RAW", "REALTIME_COARSE", "MONOTONIC_COARSE", "BOOTTIME", |
| "REALTIME_ALARM", "BOOTTIME_ALARM", "SGI_CYCLE", "TAI" |
| }; |
| static DEFINE_STRARRAY(clockid, "CLOCK_"); |
| |
| static size_t syscall_arg__scnprintf_access_mode(char *bf, size_t size, |
| struct syscall_arg *arg) |
| { |
| bool show_prefix = arg->show_string_prefix; |
| const char *suffix = "_OK"; |
| size_t printed = 0; |
| int mode = arg->val; |
| |
| if (mode == F_OK) /* 0 */ |
| return scnprintf(bf, size, "F%s", show_prefix ? suffix : ""); |
| #define P_MODE(n) \ |
| if (mode & n##_OK) { \ |
| printed += scnprintf(bf + printed, size - printed, "%s%s", #n, show_prefix ? suffix : ""); \ |
| mode &= ~n##_OK; \ |
| } |
| |
| P_MODE(R); |
| P_MODE(W); |
| P_MODE(X); |
| #undef P_MODE |
| |
| if (mode) |
| printed += scnprintf(bf + printed, size - printed, "|%#x", mode); |
| |
| return printed; |
| } |
| |
| #define SCA_ACCMODE syscall_arg__scnprintf_access_mode |
| |
| static size_t syscall_arg__scnprintf_filename(char *bf, size_t size, |
| struct syscall_arg *arg); |
| |
| #define SCA_FILENAME syscall_arg__scnprintf_filename |
| |
| static size_t syscall_arg__scnprintf_pipe_flags(char *bf, size_t size, |
| struct syscall_arg *arg) |
| { |
| bool show_prefix = arg->show_string_prefix; |
| const char *prefix = "O_"; |
| int printed = 0, flags = arg->val; |
| |
| #define P_FLAG(n) \ |
| if (flags & O_##n) { \ |
| printed += scnprintf(bf + printed, size - printed, "%s%s%s", printed ? "|" : "", show_prefix ? prefix : "", #n); \ |
| flags &= ~O_##n; \ |
| } |
| |
| P_FLAG(CLOEXEC); |
| P_FLAG(NONBLOCK); |
| #undef P_FLAG |
| |
| if (flags) |
| printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", flags); |
| |
| return printed; |
| } |
| |
| #define SCA_PIPE_FLAGS syscall_arg__scnprintf_pipe_flags |
| |
| #ifndef GRND_NONBLOCK |
| #define GRND_NONBLOCK 0x0001 |
| #endif |
| #ifndef GRND_RANDOM |
| #define GRND_RANDOM 0x0002 |
| #endif |
| |
| static size_t syscall_arg__scnprintf_getrandom_flags(char *bf, size_t size, |
| struct syscall_arg *arg) |
| { |
| bool show_prefix = arg->show_string_prefix; |
| const char *prefix = "GRND_"; |
| int printed = 0, flags = arg->val; |
| |
| #define P_FLAG(n) \ |
| if (flags & GRND_##n) { \ |
| printed += scnprintf(bf + printed, size - printed, "%s%s%s", printed ? "|" : "", show_prefix ? prefix : "", #n); \ |
| flags &= ~GRND_##n; \ |
| } |
| |
| P_FLAG(RANDOM); |
| P_FLAG(NONBLOCK); |
| #undef P_FLAG |
| |
| if (flags) |
| printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", flags); |
| |
| return printed; |
| } |
| |
| #define SCA_GETRANDOM_FLAGS syscall_arg__scnprintf_getrandom_flags |
| |
| #define STRARRAY(name, array) \ |
| { .scnprintf = SCA_STRARRAY, \ |
| .strtoul = STUL_STRARRAY, \ |
| .parm = &strarray__##array, } |
| |
| #define STRARRAY_FLAGS(name, array) \ |
| { .scnprintf = SCA_STRARRAY_FLAGS, \ |
| .strtoul = STUL_STRARRAY_FLAGS, \ |
| .parm = &strarray__##array, } |
| |
| #include "trace/beauty/arch_errno_names.c" |
| #include "trace/beauty/eventfd.c" |
| #include "trace/beauty/futex_op.c" |
| #include "trace/beauty/futex_val3.c" |
| #include "trace/beauty/mmap.c" |
| #include "trace/beauty/mode_t.c" |
| #include "trace/beauty/msg_flags.c" |
| #include "trace/beauty/open_flags.c" |
| #include "trace/beauty/perf_event_open.c" |
| #include "trace/beauty/pid.c" |
| #include "trace/beauty/sched_policy.c" |
| #include "trace/beauty/seccomp.c" |
| #include "trace/beauty/signum.c" |
| #include "trace/beauty/socket_type.c" |
| #include "trace/beauty/waitid_options.c" |
| |
| static struct syscall_fmt syscall_fmts[] = { |
| { .name = "access", |
| .arg = { [1] = { .scnprintf = SCA_ACCMODE, /* mode */ }, }, }, |
| { .name = "arch_prctl", |
| .arg = { [0] = { .scnprintf = SCA_X86_ARCH_PRCTL_CODE, /* code */ }, |
| [1] = { .scnprintf = SCA_PTR, /* arg2 */ }, }, }, |
| { .name = "bind", |
| .arg = { [0] = { .scnprintf = SCA_INT, /* fd */ }, |
| [1] = { .scnprintf = SCA_SOCKADDR, /* umyaddr */ }, |
| [2] = { .scnprintf = SCA_INT, /* addrlen */ }, }, }, |
| { .name = "bpf", |
| .arg = { [0] = STRARRAY(cmd, bpf_cmd), }, }, |
| { .name = "brk", .hexret = true, |
| .arg = { [0] = { .scnprintf = SCA_PTR, /* brk */ }, }, }, |
| { .name = "clock_gettime", |
| .arg = { [0] = STRARRAY(clk_id, clockid), }, }, |
| { .name = "clone", .errpid = true, .nr_args = 5, |
| .arg = { [0] = { .name = "flags", .scnprintf = SCA_CLONE_FLAGS, }, |
| [1] = { .name = "child_stack", .scnprintf = SCA_HEX, }, |
| [2] = { .name = "parent_tidptr", .scnprintf = SCA_HEX, }, |
| [3] = { .name = "child_tidptr", .scnprintf = SCA_HEX, }, |
| [4] = { .name = "tls", .scnprintf = SCA_HEX, }, }, }, |
| { .name = "close", |
| .arg = { [0] = { .scnprintf = SCA_CLOSE_FD, /* fd */ }, }, }, |
| { .name = "connect", |
| .arg = { [0] = { .scnprintf = SCA_INT, /* fd */ }, |
| [1] = { .scnprintf = SCA_SOCKADDR, /* servaddr */ }, |
| [2] = { .scnprintf = SCA_INT, /* addrlen */ }, }, }, |
| { .name = "epoll_ctl", |
| .arg = { [1] = STRARRAY(op, epoll_ctl_ops), }, }, |
| { .name = "eventfd2", |
| .arg = { [1] = { .scnprintf = SCA_EFD_FLAGS, /* flags */ }, }, }, |
| { .name = "fchmodat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* fd */ }, }, }, |
| { .name = "fchownat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* fd */ }, }, }, |
| { .name = "fcntl", |
| .arg = { [1] = { .scnprintf = SCA_FCNTL_CMD, /* cmd */ |
| .strtoul = STUL_STRARRAYS, |
| .parm = &strarrays__fcntl_cmds_arrays, |
| .show_zero = true, }, |
| [2] = { .scnprintf = SCA_FCNTL_ARG, /* arg */ }, }, }, |
| { .name = "flock", |
| .arg = { [1] = { .scnprintf = SCA_FLOCK, /* cmd */ }, }, }, |
| { .name = "fsconfig", |
| .arg = { [1] = STRARRAY(cmd, fsconfig_cmds), }, }, |
| { .name = "fsmount", |
| .arg = { [1] = STRARRAY_FLAGS(flags, fsmount_flags), |
| [2] = { .scnprintf = SCA_FSMOUNT_ATTR_FLAGS, /* attr_flags */ }, }, }, |
| { .name = "fspick", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, |
| [1] = { .scnprintf = SCA_FILENAME, /* path */ }, |
| [2] = { .scnprintf = SCA_FSPICK_FLAGS, /* flags */ }, }, }, |
| { .name = "fstat", .alias = "newfstat", }, |
| { .name = "fstatat", .alias = "newfstatat", }, |
| { .name = "futex", |
| .arg = { [1] = { .scnprintf = SCA_FUTEX_OP, /* op */ }, |
| [5] = { .scnprintf = SCA_FUTEX_VAL3, /* val3 */ }, }, }, |
| { .name = "futimesat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* fd */ }, }, }, |
| { .name = "getitimer", |
| .arg = { [0] = STRARRAY(which, itimers), }, }, |
| { .name = "getpid", .errpid = true, }, |
| { .name = "getpgid", .errpid = true, }, |
| { .name = "getppid", .errpid = true, }, |
| { .name = "getrandom", |
| .arg = { [2] = { .scnprintf = SCA_GETRANDOM_FLAGS, /* flags */ }, }, }, |
| { .name = "getrlimit", |
| .arg = { [0] = STRARRAY(resource, rlimit_resources), }, }, |
| { .name = "getsockopt", |
| .arg = { [1] = STRARRAY(level, socket_level), }, }, |
| { .name = "gettid", .errpid = true, }, |
| { .name = "ioctl", |
| .arg = { |
| #if defined(__i386__) || defined(__x86_64__) |
| /* |
| * FIXME: Make this available to all arches. |
| */ |
| [1] = { .scnprintf = SCA_IOCTL_CMD, /* cmd */ }, |
| [2] = { .scnprintf = SCA_HEX, /* arg */ }, }, }, |
| #else |
| [2] = { .scnprintf = SCA_HEX, /* arg */ }, }, }, |
| #endif |
| { .name = "kcmp", .nr_args = 5, |
| .arg = { [0] = { .name = "pid1", .scnprintf = SCA_PID, }, |
| [1] = { .name = "pid2", .scnprintf = SCA_PID, }, |
| [2] = { .name = "type", .scnprintf = SCA_KCMP_TYPE, }, |
| [3] = { .name = "idx1", .scnprintf = SCA_KCMP_IDX, }, |
| [4] = { .name = "idx2", .scnprintf = SCA_KCMP_IDX, }, }, }, |
| { .name = "keyctl", |
| .arg = { [0] = STRARRAY(option, keyctl_options), }, }, |
| { .name = "kill", |
| .arg = { [1] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, |
| { .name = "linkat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* fd */ }, }, }, |
| { .name = "lseek", |
| .arg = { [2] = STRARRAY(whence, whences), }, }, |
| { .name = "lstat", .alias = "newlstat", }, |
| { .name = "madvise", |
| .arg = { [0] = { .scnprintf = SCA_HEX, /* start */ }, |
| [2] = { .scnprintf = SCA_MADV_BHV, /* behavior */ }, }, }, |
| { .name = "mkdirat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* fd */ }, }, }, |
| { .name = "mknodat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* fd */ }, }, }, |
| { .name = "mmap", .hexret = true, |
| /* The standard mmap maps to old_mmap on s390x */ |
| #if defined(__s390x__) |
| .alias = "old_mmap", |
| #endif |
| .arg = { [2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ }, |
| [3] = { .scnprintf = SCA_MMAP_FLAGS, /* flags */ |
| .strtoul = STUL_STRARRAY_FLAGS, |
| .parm = &strarray__mmap_flags, }, |
| [5] = { .scnprintf = SCA_HEX, /* offset */ }, }, }, |
| { .name = "mount", |
| .arg = { [0] = { .scnprintf = SCA_FILENAME, /* dev_name */ }, |
| [3] = { .scnprintf = SCA_MOUNT_FLAGS, /* flags */ |
| .mask_val = SCAMV_MOUNT_FLAGS, /* flags */ }, }, }, |
| { .name = "move_mount", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* from_dfd */ }, |
| [1] = { .scnprintf = SCA_FILENAME, /* from_pathname */ }, |
| [2] = { .scnprintf = SCA_FDAT, /* to_dfd */ }, |
| [3] = { .scnprintf = SCA_FILENAME, /* to_pathname */ }, |
| [4] = { .scnprintf = SCA_MOVE_MOUNT_FLAGS, /* flags */ }, }, }, |
| { .name = "mprotect", |
| .arg = { [0] = { .scnprintf = SCA_HEX, /* start */ }, |
| [2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ }, }, }, |
| { .name = "mq_unlink", |
| .arg = { [0] = { .scnprintf = SCA_FILENAME, /* u_name */ }, }, }, |
| { .name = "mremap", .hexret = true, |
| .arg = { [3] = { .scnprintf = SCA_MREMAP_FLAGS, /* flags */ }, }, }, |
| { .name = "name_to_handle_at", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, }, }, |
| { .name = "newfstatat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, }, }, |
| { .name = "open", |
| .arg = { [1] = { .scnprintf = SCA_OPEN_FLAGS, /* flags */ }, }, }, |
| { .name = "open_by_handle_at", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, |
| [2] = { .scnprintf = SCA_OPEN_FLAGS, /* flags */ }, }, }, |
| { .name = "openat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, |
| [2] = { .scnprintf = SCA_OPEN_FLAGS, /* flags */ }, }, }, |
| { .name = "perf_event_open", |
| .arg = { [2] = { .scnprintf = SCA_INT, /* cpu */ }, |
| [3] = { .scnprintf = SCA_FD, /* group_fd */ }, |
| [4] = { .scnprintf = SCA_PERF_FLAGS, /* flags */ }, }, }, |
| { .name = "pipe2", |
| .arg = { [1] = { .scnprintf = SCA_PIPE_FLAGS, /* flags */ }, }, }, |
| { .name = "pkey_alloc", |
| .arg = { [1] = { .scnprintf = SCA_PKEY_ALLOC_ACCESS_RIGHTS, /* access_rights */ }, }, }, |
| { .name = "pkey_free", |
| .arg = { [0] = { .scnprintf = SCA_INT, /* key */ }, }, }, |
| { .name = "pkey_mprotect", |
| .arg = { [0] = { .scnprintf = SCA_HEX, /* start */ }, |
| [2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ }, |
| [3] = { .scnprintf = SCA_INT, /* pkey */ }, }, }, |
| { .name = "poll", .timeout = true, }, |
| { .name = "ppoll", .timeout = true, }, |
| { .name = "prctl", |
| .arg = { [0] = { .scnprintf = SCA_PRCTL_OPTION, /* option */ |
| .strtoul = STUL_STRARRAY, |
| .parm = &strarray__prctl_options, }, |
| [1] = { .scnprintf = SCA_PRCTL_ARG2, /* arg2 */ }, |
| [2] = { .scnprintf = SCA_PRCTL_ARG3, /* arg3 */ }, }, }, |
| { .name = "pread", .alias = "pread64", }, |
| { .name = "preadv", .alias = "pread", }, |
| { .name = "prlimit64", |
| .arg = { [1] = STRARRAY(resource, rlimit_resources), }, }, |
| { .name = "pwrite", .alias = "pwrite64", }, |
| { .name = "readlinkat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, }, }, |
| { .name = "recvfrom", |
| .arg = { [3] = { .scnprintf = SCA_MSG_FLAGS, /* flags */ }, }, }, |
| { .name = "recvmmsg", |
| .arg = { [3] = { .scnprintf = SCA_MSG_FLAGS, /* flags */ }, }, }, |
| { .name = "recvmsg", |
| .arg = { [2] = { .scnprintf = SCA_MSG_FLAGS, /* flags */ }, }, }, |
| { .name = "renameat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* olddirfd */ }, |
| [2] = { .scnprintf = SCA_FDAT, /* newdirfd */ }, }, }, |
| { .name = "renameat2", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* olddirfd */ }, |
| [2] = { .scnprintf = SCA_FDAT, /* newdirfd */ }, |
| [4] = { .scnprintf = SCA_RENAMEAT2_FLAGS, /* flags */ }, }, }, |
| { .name = "rt_sigaction", |
| .arg = { [0] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, |
| { .name = "rt_sigprocmask", |
| .arg = { [0] = STRARRAY(how, sighow), }, }, |
| { .name = "rt_sigqueueinfo", |
| .arg = { [1] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, |
| { .name = "rt_tgsigqueueinfo", |
| .arg = { [2] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, |
| { .name = "sched_setscheduler", |
| .arg = { [1] = { .scnprintf = SCA_SCHED_POLICY, /* policy */ }, }, }, |
| { .name = "seccomp", |
| .arg = { [0] = { .scnprintf = SCA_SECCOMP_OP, /* op */ }, |
| [1] = { .scnprintf = SCA_SECCOMP_FLAGS, /* flags */ }, }, }, |
| { .name = "select", .timeout = true, }, |
| { .name = "sendfile", .alias = "sendfile64", }, |
| { .name = "sendmmsg", |
| .arg = { [3] = { .scnprintf = SCA_MSG_FLAGS, /* flags */ }, }, }, |
| { .name = "sendmsg", |
| .arg = { [2] = { .scnprintf = SCA_MSG_FLAGS, /* flags */ }, }, }, |
| { .name = "sendto", |
| .arg = { [3] = { .scnprintf = SCA_MSG_FLAGS, /* flags */ }, |
| [4] = { .scnprintf = SCA_SOCKADDR, /* addr */ }, }, }, |
| { .name = "set_tid_address", .errpid = true, }, |
| { .name = "setitimer", |
| .arg = { [0] = STRARRAY(which, itimers), }, }, |
| { .name = "setrlimit", |
| .arg = { [0] = STRARRAY(resource, rlimit_resources), }, }, |
| { .name = "setsockopt", |
| .arg = { [1] = STRARRAY(level, socket_level), }, }, |
| { .name = "socket", |
| .arg = { [0] = STRARRAY(family, socket_families), |
| [1] = { .scnprintf = SCA_SK_TYPE, /* type */ }, |
| [2] = { .scnprintf = SCA_SK_PROTO, /* protocol */ }, }, }, |
| { .name = "socketpair", |
| .arg = { [0] = STRARRAY(family, socket_families), |
| [1] = { .scnprintf = SCA_SK_TYPE, /* type */ }, |
| [2] = { .scnprintf = SCA_SK_PROTO, /* protocol */ }, }, }, |
| { .name = "stat", .alias = "newstat", }, |
| { .name = "statx", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* fdat */ }, |
| [2] = { .scnprintf = SCA_STATX_FLAGS, /* flags */ } , |
| [3] = { .scnprintf = SCA_STATX_MASK, /* mask */ }, }, }, |
| { .name = "swapoff", |
| .arg = { [0] = { .scnprintf = SCA_FILENAME, /* specialfile */ }, }, }, |
| { .name = "swapon", |
| .arg = { [0] = { .scnprintf = SCA_FILENAME, /* specialfile */ }, }, }, |
| { .name = "symlinkat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, }, }, |
| { .name = "sync_file_range", |
| .arg = { [3] = { .scnprintf = SCA_SYNC_FILE_RANGE_FLAGS, /* flags */ }, }, }, |
| { .name = "tgkill", |
| .arg = { [2] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, |
| { .name = "tkill", |
| .arg = { [1] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, |
| { .name = "umount2", .alias = "umount", |
| .arg = { [0] = { .scnprintf = SCA_FILENAME, /* name */ }, }, }, |
| { .name = "uname", .alias = "newuname", }, |
| { .name = "unlinkat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, }, }, |
| { .name = "utimensat", |
| .arg = { [0] = { .scnprintf = SCA_FDAT, /* dirfd */ }, }, }, |
| { .name = "wait4", .errpid = true, |
| .arg = { [2] = { .scnprintf = SCA_WAITID_OPTIONS, /* options */ }, }, }, |
| { .name = "waitid", .errpid = true, |
| .arg = { [3] = { .scnprintf = SCA_WAITID_OPTIONS, /* options */ }, }, }, |
| }; |
| |
| static int syscall_fmt__cmp(const void *name, const void *fmtp) |
| { |
| const struct syscall_fmt *fmt = fmtp; |
| return strcmp(name, fmt->name); |
| } |
| |
| static struct syscall_fmt *__syscall_fmt__find(struct syscall_fmt *fmts, const int nmemb, const char *name) |
| { |
| return bsearch(name, fmts, nmemb, sizeof(struct syscall_fmt), syscall_fmt__cmp); |
| } |
| |
| static struct syscall_fmt *syscall_fmt__find(const char *name) |
| { |
| const int nmemb = ARRAY_SIZE(syscall_fmts); |
| return __syscall_fmt__find(syscall_fmts, nmemb, name); |
| } |
| |
| static struct syscall_fmt *__syscall_fmt__find_by_alias(struct syscall_fmt *fmts, const int nmemb, const char *alias) |
| { |
| int i; |
| |
| for (i = 0; i < nmemb; ++i) { |
| if (fmts[i].alias && strcmp(fmts[i].alias, alias) == 0) |
| return &fmts[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static struct syscall_fmt *syscall_fmt__find_by_alias(const char *alias) |
| { |
| const int nmemb = ARRAY_SIZE(syscall_fmts); |
| return __syscall_fmt__find_by_alias(syscall_fmts, nmemb, alias); |
| } |
| |
| /* |
| * is_exit: is this "exit" or "exit_group"? |
| * is_open: is this "open" or "openat"? To associate the fd returned in sys_exit with the pathname in sys_enter. |
| * args_size: sum of the sizes of the syscall arguments, anything after that is augmented stuff: pathname for openat, etc. |
| * nonexistent: Just a hole in the syscall table, syscall id not allocated |
| */ |
| struct syscall { |
| struct tep_event *tp_format; |
| int nr_args; |
| int args_size; |
| struct { |
| struct bpf_program *sys_enter, |
| *sys_exit; |
| } bpf_prog; |
| bool is_exit; |
| bool is_open; |
| bool nonexistent; |
| struct tep_format_field *args; |
| const char *name; |
| struct syscall_fmt *fmt; |
| struct syscall_arg_fmt *arg_fmt; |
| }; |
| |
| /* |
| * Must match what is in the BPF program: |
| * |
| * tools/perf/examples/bpf/augmented_raw_syscalls.c |
| */ |
| struct bpf_map_syscall_entry { |
| bool enabled; |
| u16 string_args_len[6]; |
| }; |
| |
| /* |
| * We need to have this 'calculated' boolean because in some cases we really |
| * don't know what is the duration of a syscall, for instance, when we start |
| * a session and some threads are waiting for a syscall to finish, say 'poll', |
| * in which case all we can do is to print "( ? ) for duration and for the |
| * start timestamp. |
| */ |
| static size_t fprintf_duration(unsigned long t, bool calculated, FILE *fp) |
| { |
| double duration = (double)t / NSEC_PER_MSEC; |
| size_t printed = fprintf(fp, "("); |
| |
| if (!calculated) |
| printed += fprintf(fp, " "); |
| else if (duration >= 1.0) |
| printed += color_fprintf(fp, PERF_COLOR_RED, "%6.3f ms", duration); |
| else if (duration >= 0.01) |
| printed += color_fprintf(fp, PERF_COLOR_YELLOW, "%6.3f ms", duration); |
| else |
| printed += color_fprintf(fp, PERF_COLOR_NORMAL, "%6.3f ms", duration); |
| return printed + fprintf(fp, "): "); |
| } |
| |
| /** |
| * filename.ptr: The filename char pointer that will be vfs_getname'd |
| * filename.entry_str_pos: Where to insert the string translated from |
| * filename.ptr by the vfs_getname tracepoint/kprobe. |
| * ret_scnprintf: syscall args may set this to a different syscall return |
| * formatter, for instance, fcntl may return fds, file flags, etc. |
| */ |
| struct thread_trace { |
| u64 entry_time; |
| bool entry_pending; |
| unsigned long nr_events; |
| unsigned long pfmaj, pfmin; |
| char *entry_str; |
| double runtime_ms; |
| size_t (*ret_scnprintf)(char *bf, size_t size, struct syscall_arg *arg); |
| struct { |
| unsigned long ptr; |
| short int entry_str_pos; |
| bool pending_open; |
| unsigned int namelen; |
| char *name; |
| } filename; |
| struct { |
| int max; |
| struct file *table; |
| } files; |
| |
| struct intlist *syscall_stats; |
| }; |
| |
| static struct thread_trace *thread_trace__new(void) |
| { |
| struct thread_trace *ttrace = zalloc(sizeof(struct thread_trace)); |
| |
| if (ttrace) { |
| ttrace->files.max = -1; |
| ttrace->syscall_stats = intlist__new(NULL); |
| } |
| |
| return ttrace; |
| } |
| |
| static struct thread_trace *thread__trace(struct thread *thread, FILE *fp) |
| { |
| struct thread_trace *ttrace; |
| |
| if (thread == NULL) |
| goto fail; |
| |
| if (thread__priv(thread) == NULL) |
| thread__set_priv(thread, thread_trace__new()); |
| |
| if (thread__priv(thread) == NULL) |
| goto fail; |
| |
| ttrace = thread__priv(thread); |
| ++ttrace->nr_events; |
| |
| return ttrace; |
| fail: |
| color_fprintf(fp, PERF_COLOR_RED, |
| "WARNING: not enough memory, dropping samples!\n"); |
| return NULL; |
| } |
| |
| |
| void syscall_arg__set_ret_scnprintf(struct syscall_arg *arg, |
| size_t (*ret_scnprintf)(char *bf, size_t size, struct syscall_arg *arg)) |
| { |
| struct thread_trace *ttrace = thread__priv(arg->thread); |
| |
| ttrace->ret_scnprintf = ret_scnprintf; |
| } |
| |
| #define TRACE_PFMAJ (1 << 0) |
| #define TRACE_PFMIN (1 << 1) |
| |
| static const size_t trace__entry_str_size = 2048; |
| |
| static struct file *thread_trace__files_entry(struct thread_trace *ttrace, int fd) |
| { |
| if (fd < 0) |
| return NULL; |
| |
| if (fd > ttrace->files.max) { |
| struct file *nfiles = realloc(ttrace->files.table, (fd + 1) * sizeof(struct file)); |
| |
| if (nfiles == NULL) |
| return NULL; |
| |
| if (ttrace->files.max != -1) { |
| memset(nfiles + ttrace->files.max + 1, 0, |
| (fd - ttrace->files.max) * sizeof(struct file)); |
| } else { |
| memset(nfiles, 0, (fd + 1) * sizeof(struct file)); |
| } |
| |
| ttrace->files.table = nfiles; |
| ttrace->files.max = fd; |
| } |
| |
| return ttrace->files.table + fd; |
| } |
| |
| struct file *thread__files_entry(struct thread *thread, int fd) |
| { |
| return thread_trace__files_entry(thread__priv(thread), fd); |
| } |
| |
| static int trace__set_fd_pathname(struct thread *thread, int fd, const char *pathname) |
| { |
| struct thread_trace *ttrace = thread__priv(thread); |
| struct file *file = thread_trace__files_entry(ttrace, fd); |
| |
| if (file != NULL) { |
| struct stat st; |
| if (stat(pathname, &st) == 0) |
| file->dev_maj = major(st.st_rdev); |
| file->pathname = strdup(pathname); |
| if (file->pathname) |
| return 0; |
| } |
| |
| return -1; |
| } |
| |
| static int thread__read_fd_path(struct thread *thread, int fd) |
| { |
| char linkname[PATH_MAX], pathname[PATH_MAX]; |
| struct stat st; |
| int ret; |
| |
| if (thread->pid_ == thread->tid) { |
| scnprintf(linkname, sizeof(linkname), |
| "/proc/%d/fd/%d", thread->pid_, fd); |
| } else { |
| scnprintf(linkname, sizeof(linkname), |
| "/proc/%d/task/%d/fd/%d", thread->pid_, thread->tid, fd); |
| } |
| |
| if (lstat(linkname, &st) < 0 || st.st_size + 1 > (off_t)sizeof(pathname)) |
| return -1; |
| |
| ret = readlink(linkname, pathname, sizeof(pathname)); |
| |
| if (ret < 0 || ret > st.st_size) |
| return -1; |
| |
| pathname[ret] = '\0'; |
| return trace__set_fd_pathname(thread, fd, pathname); |
| } |
| |
| static const char *thread__fd_path(struct thread *thread, int fd, |
| struct trace *trace) |
| { |
| struct thread_trace *ttrace = thread__priv(thread); |
| |
| if (ttrace == NULL || trace->fd_path_disabled) |
| return NULL; |
| |
| if (fd < 0) |
| return NULL; |
| |
| if ((fd > ttrace->files.max || ttrace->files.table[fd].pathname == NULL)) { |
| if (!trace->live) |
| return NULL; |
| ++trace->stats.proc_getname; |
| if (thread__read_fd_path(thread, fd)) |
| return NULL; |
| } |
| |
| return ttrace->files.table[fd].pathname; |
| } |
| |
| size_t syscall_arg__scnprintf_fd(char *bf, size_t size, struct syscall_arg *arg) |
| { |
| int fd = arg->val; |
| size_t printed = scnprintf(bf, size, "%d", fd); |
| const char *path = thread__fd_path(arg->thread, fd, arg->trace); |
| |
| if (path) |
| printed += scnprintf(bf + printed, size - printed, "<%s>", path); |
| |
| return printed; |
| } |
| |
| size_t pid__scnprintf_fd(struct trace *trace, pid_t pid, int fd, char *bf, size_t size) |
| { |
| size_t printed = scnprintf(bf, size, "%d", fd); |
| struct thread *thread = machine__find_thread(trace->host, pid, pid); |
| |
| if (thread) { |
| const char *path = thread__fd_path(thread, fd, trace); |
| |
| if (path) |
| printed += scnprintf(bf + printed, size - printed, "<%s>", path); |
| |
| thread__put(thread); |
| } |
| |
| return printed; |
| } |
| |
| static size_t syscall_arg__scnprintf_close_fd(char *bf, size_t size, |
| struct syscall_arg *arg) |
| { |
| int fd = arg->val; |
| size_t printed = syscall_arg__scnprintf_fd(bf, size, arg); |
| struct thread_trace *ttrace = thread__priv(arg->thread); |
| |
| if (ttrace && fd >= 0 && fd <= ttrace->files.max) |
| zfree(&ttrace->files.table[fd].pathname); |
| |
| return printed; |
| } |
| |
| static void thread__set_filename_pos(struct thread *thread, const char *bf, |
| unsigned long ptr) |
| { |
| struct thread_trace *ttrace = thread__priv(thread); |
| |
| ttrace->filename.ptr = ptr; |
| ttrace->filename.entry_str_pos = bf - ttrace->entry_str; |
| } |
| |
| static size_t syscall_arg__scnprintf_augmented_string(struct syscall_arg *arg, char *bf, size_t size) |
| { |
| struct augmented_arg *augmented_arg = arg->augmented.args; |
| size_t printed = scnprintf(bf, size, "\"%.*s\"", augmented_arg->size, augmented_arg->value); |
| /* |
| * So that the next arg with a payload can consume its augmented arg, i.e. for rename* syscalls |
| * we would have two strings, each prefixed by its size. |
| */ |
| int consumed = sizeof(*augmented_arg) + augmented_arg->size; |
| |
| arg->augmented.args = ((void *)arg->augmented.args) + consumed; |
| arg->augmented.size -= consumed; |
| |
| return printed; |
| } |
| |
| static size_t syscall_arg__scnprintf_filename(char *bf, size_t size, |
| struct syscall_arg *arg) |
| { |
| unsigned long ptr = arg->val; |
| |
| if (arg->augmented.args) |
| return syscall_arg__scnprintf_augmented_string(arg, bf, size); |
| |
| if (!arg->trace->vfs_getname) |
| return scnprintf(bf, size, "%#x", ptr); |
| |
| thread__set_filename_pos(arg->thread, bf, ptr); |
| return 0; |
| } |
| |
| static bool trace__filter_duration(struct trace *trace, double t) |
| { |
| return t < (trace->duration_filter * NSEC_PER_MSEC); |
| } |
| |
| static size_t __trace__fprintf_tstamp(struct trace *trace, u64 tstamp, FILE *fp) |
| { |
| double ts = (double)(tstamp - trace->base_time) / NSEC_PER_MSEC; |
| |
| return fprintf(fp, "%10.3f ", ts); |
| } |
| |
| /* |
| * We're handling tstamp=0 as an undefined tstamp, i.e. like when we are |
| * using ttrace->entry_time for a thread that receives a sys_exit without |
| * first having received a sys_enter ("poll" issued before tracing session |
| * starts, lost sys_enter exit due to ring buffer overflow). |
| */ |
| static size_t trace__fprintf_tstamp(struct trace *trace, u64 tstamp, FILE *fp) |
| { |
| if (tstamp > 0) |
| return __trace__fprintf_tstamp(trace, tstamp, fp); |
| |
| return fprintf(fp, " ? "); |
| } |
| |
| static bool done = false; |
| static bool interrupted = false; |
| |
| static void sig_handler(int sig) |
| { |
| done = true; |
| interrupted = sig == SIGINT; |
| } |
| |
| static size_t trace__fprintf_comm_tid(struct trace *trace, struct thread *thread, FILE *fp) |
| { |
| size_t printed = 0; |
| |
| if (trace->multiple_threads) { |
| if (trace->show_comm) |
| printed += fprintf(fp, "%.14s/", thread__comm_str(thread)); |
| printed += fprintf(fp, "%d ", thread->tid); |
| } |
| |
| return printed; |
| } |
| |
| static size_t trace__fprintf_entry_head(struct trace *trace, struct thread *thread, |
| u64 duration, bool duration_calculated, u64 tstamp, FILE *fp) |
| { |
| size_t printed = 0; |
| |
| if (trace->show_tstamp) |
| printed = trace__fprintf_tstamp(trace, tstamp, fp); |
| if (trace->show_duration) |
| printed += fprintf_duration(duration, duration_calculated, fp); |
| return printed + trace__fprintf_comm_tid(trace, thread, fp); |
| } |
| |
| static int trace__process_event(struct trace *trace, struct machine *machine, |
| union perf_event *event, struct perf_sample *sample) |
| { |
| int ret = 0; |
| |
| switch (event->header.type) { |
| case PERF_RECORD_LOST: |
| color_fprintf(trace->output, PERF_COLOR_RED, |
| "LOST %" PRIu64 " events!\n", event->lost.lost); |
| ret = machine__process_lost_event(machine, event, sample); |
| break; |
| default: |
| ret = machine__process_event(machine, event, sample); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int trace__tool_process(struct perf_tool *tool, |
| union perf_event *event, |
| struct perf_sample *sample, |
| struct machine *machine) |
| { |
| struct trace *trace = container_of(tool, struct trace, tool); |
| return trace__process_event(trace, machine, event, sample); |
| } |
| |
| static char *trace__machine__resolve_kernel_addr(void *vmachine, unsigned long long *addrp, char **modp) |
| { |
| struct machine *machine = vmachine; |
| |
| if (machine->kptr_restrict_warned) |
| return NULL; |
| |
| if (symbol_conf.kptr_restrict) { |
| pr_warning("Kernel address maps (/proc/{kallsyms,modules}) are restricted.\n\n" |
| "Check /proc/sys/kernel/kptr_restrict and /proc/sys/kernel/perf_event_paranoid.\n\n" |
| "Kernel samples will not be resolved.\n"); |
| machine->kptr_restrict_warned = true; |
| return NULL; |
| } |
| |
| return machine__resolve_kernel_addr(vmachine, addrp, modp); |
| } |
| |
| static int trace__symbols_init(struct trace *trace, struct evlist *evlist) |
| { |
| int err = symbol__init(NULL); |
| |
| if (err) |
| return err; |
| |
| trace->host = machine__new_host(); |
| if (trace->host == NULL) |
| return -ENOMEM; |
| |
| err = trace_event__register_resolver(trace->host, trace__machine__resolve_kernel_addr); |
| if (err < 0) |
| goto out; |
| |
| err = __machine__synthesize_threads(trace->host, &trace->tool, &trace->opts.target, |
| evlist->core.threads, trace__tool_process, |
| true, false, 1); |
| out: |
| if (err) |
| symbol__exit(); |
| |
| return err; |
| } |
| |
| static void trace__symbols__exit(struct trace *trace) |
| { |
| machine__exit(trace->host); |
| trace->host = NULL; |
| |
| symbol__exit(); |
| } |
| |
| static int syscall__alloc_arg_fmts(struct syscall *sc, int nr_args) |
| { |
| int idx; |
| |
| if (nr_args == 6 && sc->fmt && sc->fmt->nr_args != 0) |
| nr_args = sc->fmt->nr_args; |
| |
| sc->arg_fmt = calloc(nr_args, sizeof(*sc->arg_fmt)); |
| if (sc->arg_fmt == NULL) |
| return -1; |
| |
| for (idx = 0; idx < nr_args; ++idx) { |
| if (sc->fmt) |
| sc->arg_fmt[idx] = sc->fmt->arg[idx]; |
| } |
| |
| sc->nr_args = nr_args; |
| return 0; |
| } |
| |
| static struct syscall_arg_fmt syscall_arg_fmts__by_name[] = { |
| { .name = "msr", .scnprintf = SCA_X86_MSR, .strtoul = STUL_X86_MSR, }, |
| { .name = "vector", .scnprintf = SCA_X86_IRQ_VECTORS, .strtoul = STUL_X86_IRQ_VECTORS, }, |
| }; |
| |
| static int syscall_arg_fmt__cmp(const void *name, const void *fmtp) |
| { |
| const struct syscall_arg_fmt *fmt = fmtp; |
| return strcmp(name, fmt->name); |
| } |
| |
| static struct syscall_arg_fmt * |
| __syscall_arg_fmt__find_by_name(struct syscall_arg_fmt *fmts, const int nmemb, const char *name) |
| { |
| return bsearch(name, fmts, nmemb, sizeof(struct syscall_arg_fmt), syscall_arg_fmt__cmp); |
| } |
| |
| static struct syscall_arg_fmt *syscall_arg_fmt__find_by_name(const char *name) |
| { |
| const int nmemb = ARRAY_SIZE(syscall_arg_fmts__by_name); |
| return __syscall_arg_fmt__find_by_name(syscall_arg_fmts__by_name, nmemb, name); |
| } |
| |
| static struct tep_format_field * |
| syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field *field) |
| { |
| struct tep_format_field *last_field = NULL; |
| int len; |
| |
| for (; field; field = field->next, ++arg) { |
| last_field = field; |
| |
| if (arg->scnprintf) |
| continue; |
| |
| len = strlen(field->name); |
| |
| if (strcmp(field->type, "const char *") == 0 && |
| ((len >= 4 && strcmp(field->name + len - 4, "name") == 0) || |
| strstr(field->name, "path") != NULL)) |
| arg->scnprintf = SCA_FILENAME; |
| else if ((field->flags & TEP_FIELD_IS_POINTER) || strstr(field->name, "addr")) |
| arg->scnprintf = SCA_PTR; |
| else if (strcmp(field->type, "pid_t") == 0) |
| arg->scnprintf = SCA_PID; |
| else if (strcmp(field->type, "umode_t") == 0) |
| arg->scnprintf = SCA_MODE_T; |
| else if ((field->flags & TEP_FIELD_IS_ARRAY) && strstr(field->type, "char")) { |
| arg->scnprintf = SCA_CHAR_ARRAY; |
| arg->nr_entries = field->arraylen; |
| } else if ((strcmp(field->type, "int") == 0 || |
| strcmp(field->type, "unsigned int") == 0 || |
| strcmp(field->type, "long") == 0) && |
| len >= 2 && strcmp(field->name + len - 2, "fd") == 0) { |
| /* |
| * /sys/kernel/tracing/events/syscalls/sys_enter* |
| * egrep 'field:.*fd;' .../format|sed -r 's/.*field:([a-z ]+) [a-z_]*fd.+/\1/g'|sort|uniq -c |
| * 65 int |
| * 23 unsigned int |
| * 7 unsigned long |
| */ |
| arg->scnprintf = SCA_FD; |
| } else { |
| struct syscall_arg_fmt *fmt = syscall_arg_fmt__find_by_name(field->name); |
| |
| if (fmt) { |
| arg->scnprintf = fmt->scnprintf; |
| arg->strtoul = fmt->strtoul; |
| } |
| } |
| } |
| |
| return last_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); |
| |
| if (last_field) |
| sc->args_size = last_field->offset + last_field->size; |
| |
| return 0; |
| } |
| |
| 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); |
| |
| #ifdef HAVE_SYSCALL_TABLE_SUPPORT |
| if (trace->syscalls.table == NULL) { |
| trace->syscalls.table = calloc(trace->sctbl->syscalls.max_id + 1, sizeof(*sc)); |
| if (trace->syscalls.table == NULL) |
| return -ENOMEM; |
| } |
| #else |
| if (id > trace->sctbl->syscalls.max_id || (id == 0 && trace->syscalls.table == NULL)) { |
| // When using libaudit we don't know beforehand what is the max syscall id |
| struct syscall *table = realloc(trace->syscalls.table, (id + 1) * sizeof(*sc)); |
| |
| if (table == NULL) |
| return -ENOMEM; |
| |
| // Need to memset from offset 0 and +1 members if brand new |
| if (trace->syscalls.table == NULL) |
| memset(table, 0, (id + 1) * sizeof(*sc)); |
| else |
| memset(table + trace->sctbl->syscalls.max_id + 1, 0, (id - trace->sctbl->syscalls.max_id) * sizeof(*sc)); |
| |
| trace->syscalls.table = table; |
| trace->sctbl->syscalls.max_id = id; |
| } |
| #endif |
| sc = trace->syscalls.table + id; |
| if (sc->nonexistent) |
| return 0; |
| |
| if (name == NULL) { |
| sc->nonexistent = true; |
| return 0; |
| } |
| |
| sc->name = name; |
| sc->fmt = syscall_fmt__find(sc->name); |
| |
| snprintf(tp_name, sizeof(tp_name), "sys_enter_%s", sc->name); |
| sc->tp_format = trace_event__tp_format("syscalls", tp_name); |
| |
| if (IS_ERR(sc->tp_format) && sc->fmt && sc->fmt->alias) { |
| snprintf(tp_name, sizeof(tp_name), "sys_enter_%s", sc->fmt->alias); |
| sc->tp_format = trace_event__tp_format("syscalls", tp_name); |
| } |
| |
| if (syscall__alloc_arg_fmts(sc, IS_ERR(sc->tp_format) ? 6 : sc->tp_format->format.nr_fields)) |
| return -ENOMEM; |
| |
| if (IS_ERR(sc->tp_format)) |
| return PTR_ERR(sc->tp_format); |
| |
| sc->args = sc->tp_format->format.fields; |
| /* |
| * We need to check and discard the first variable '__syscall_nr' |
| * or 'nr' that mean the syscall number. It is needless here. |
| * So drop '__syscall_nr' or 'nr' field but does not exist on older kernels. |
| */ |
| if (sc->args && (!strcmp(sc->args->name, "__syscall_nr") || !strcmp(sc->args->name, "nr"))) { |
| sc->args = sc->args->next; |
| --sc->nr_args; |
| } |
| |
| 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); |
| } |
| |
| 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); |
| return 0; |
| } |
| |
| return -ENOMEM; |
| } |
| |
| static int intcmp(const void *a, const void *b) |
| { |
| const int *one = a, *another = b; |
| |
| return *one - *another; |
| } |
| |
| static int trace__validate_ev_qualifier(struct trace *trace) |
| { |
| int err = 0; |
| bool printed_invalid_prefix = false; |
| struct str_node *pos; |
| size_t nr_used = 0, nr_allocated = strlist__nr_entries(trace->ev_qualifier); |
| |
| trace->ev_qualifier_ids.entries = malloc(nr_allocated * |
| sizeof(trace->ev_qualifier_ids.entries[0])); |
| |
| if (trace->ev_qualifier_ids.entries == NULL) { |
| fputs("Error:\tNot enough memory for allocating events qualifier ids\n", |
| trace->output); |
| err = -EINVAL; |
| goto out; |
| } |
| |
| strlist__for_each_entry(pos, trace->ev_qualifier) { |
| const char *sc = pos->s; |
| int id = syscalltbl__id(trace->sctbl, sc), match_next = -1; |
| |
| if (id < 0) { |
| id = syscalltbl__strglobmatch_first(trace->sctbl, sc, &match_next); |
| if (id >= 0) |
| goto matches; |
| |
| if (!printed_invalid_prefix) { |
| pr_debug("Skipping unknown syscalls: "); |
| printed_invalid_prefix = true; |
| } else { |
| pr_debug(", "); |
| } |
| |
| pr_debug("%s", sc); |
| continue; |
| } |
| matches: |
| trace->ev_qualifier_ids.entries[nr_used++] = id; |
| if (match_next == -1) |
| continue; |
| |
| while (1) { |
| id = syscalltbl__strglobmatch_next(trace->sctbl, sc, &match_next); |
| if (id < 0) |
| break; |
| if (nr_allocated == nr_used) { |
| void *entries; |
| |
| nr_allocated += 8; |
| entries = realloc(trace->ev_qualifier_ids.entries, |
| nr_allocated * sizeof(trace->ev_qualifier_ids.entries[0])); |
| if (entries == NULL) { |
| err = -ENOMEM; |
| fputs("\nError:\t Not enough memory for parsing\n", trace->output); |
| goto out_free; |
| } |
| trace->ev_qualifier_ids.entries = entries; |
| } |
| trace->ev_qualifier_ids.entries[nr_used++] = id; |
| } |
| } |
| |
| trace->ev_qualifier_ids.nr = nr_used; |
| qsort(trace->ev_qualifier_ids.entries, nr_used, sizeof(int), intcmp); |
| out: |
| if (printed_invalid_prefix) |
| pr_debug("\n"); |
| return err; |
| out_free: |
| zfree(&trace->ev_qualifier_ids.entries); |
| trace->ev_qualifier_ids.nr = 0; |
| goto out; |
| } |
| |
| static __maybe_unused bool trace__syscall_enabled(struct trace *trace, int id) |
| { |
| bool in_ev_qualifier; |
| |
| if (trace->ev_qualifier_ids.nr == 0) |
| return true; |
| |
| in_ev_qualifier = bsearch(&id, trace->ev_qualifier_ids.entries, |
| trace->ev_qualifier_ids.nr, sizeof(int), intcmp) != NULL; |
| |
| if (in_ev_qualifier) |
| return !trace->not_ev_qualifier; |
| |
| return trace->not_ev_qualifier; |
| } |
| |
| /* |
| * args is to be interpreted as a series of longs but we need to handle |
| * 8-byte unaligned accesses. args points to raw_data within the event |
| * and raw_data is guaranteed to be 8-byte unaligned because it is |
| * preceded by raw_size which is a u32. So we need to copy args to a temp |
| * variable to read it. Most notably this avoids extended load instructions |
| * on unaligned addresses |
| */ |
| unsigned long syscall_arg__val(struct syscall_arg *arg, u8 idx) |
| { |
| unsigned long val; |
| unsigned char *p = arg->args + sizeof(unsigned long) * idx; |
| |
| memcpy(&val, p, sizeof(val)); |
| return val; |
| } |
| |
| static size_t syscall__scnprintf_name(struct syscall *sc, char *bf, size_t size, |
| struct syscall_arg *arg) |
| { |
| if (sc->arg_fmt && sc->arg_fmt[arg->idx].name) |
| return scnprintf(bf, size, "%s: ", sc->arg_fmt[arg->idx].name); |
| |
| return scnprintf(bf, size, "arg%d: ", arg->idx); |
| } |
| |
| /* |
| * Check if the value is in fact zero, i.e. mask whatever needs masking, such |
| * as mount 'flags' argument that needs ignoring some magic flag, see comment |
| * in tools/perf/trace/beauty/mount_flags.c |
| */ |
| static unsigned long syscall_arg_fmt__mask_val(struct syscall_arg_fmt *fmt, struct syscall_arg *arg, unsigned long val) |
| { |
| if (fmt && fmt->mask_val) |
| return fmt->mask_val(arg, val); |
| |
| return val; |
| } |
| |
| static size_t syscall_arg_fmt__scnprintf_val(struct syscall_arg_fmt *fmt, char *bf, size_t size, |
| struct syscall_arg *arg, unsigned long val) |
| { |
| if (fmt && fmt->scnprintf) { |
| arg->val = val; |
| if (fmt->parm) |
| arg->parm = fmt->parm; |
| return fmt->scnprintf(bf, size, arg); |
| } |
| return scnprintf(bf, size, "%ld", val); |
| } |
| |
| static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, |
| unsigned char *args, void *augmented_args, int augmented_args_size, |
| struct trace *trace, struct thread *thread) |
| { |
| size_t printed = 0; |
| unsigned long val; |
| u8 bit = 1; |
| struct syscall_arg arg = { |
| .args = args, |
| .augmented = { |
| .size = augmented_args_size, |
| .args = augmented_args, |
| }, |
| .idx = 0, |
| .mask = 0, |
| .trace = trace, |
| .thread = thread, |
| .show_string_prefix = trace->show_string_prefix, |
| }; |
| struct thread_trace *ttrace = thread__priv(thread); |
| |
| /* |
| * Things like fcntl will set this in its 'cmd' formatter to pick the |
| * right formatter for the return value (an fd? file flags?), which is |
| * not needed for syscalls that always return a given type, say an fd. |
| */ |
| ttrace->ret_scnprintf = NULL; |
| |
| if (sc->args != NULL) { |
| struct tep_format_field *field; |
| |
| for (field = sc->args; field; |
| field = field->next, ++arg.idx, bit <<= 1) { |
| if (arg.mask & bit) |
| continue; |
| |
| arg.fmt = &sc->arg_fmt[arg.idx]; |
| val = syscall_arg__val(&arg, arg.idx); |
| /* |
| * Some syscall args need some mask, most don't and |
| * return val untouched. |
| */ |
| val = syscall_arg_fmt__mask_val(&sc->arg_fmt[arg.idx], &arg, val); |
| |
| /* |
| * Suppress this argument if its value is zero and |
| * and we don't have a string associated in an |
| * strarray for it. |
| */ |
| if (val == 0 && |
| !trace->show_zeros && |
| !(sc->arg_fmt && |
| (sc->arg_fmt[arg.idx].show_zero || |
| sc->arg_fmt[arg.idx].scnprintf == SCA_STRARRAY || |
| sc->arg_fmt[arg.idx].scnprintf == SCA_STRARRAYS) && |
| sc->arg_fmt[arg.idx].parm)) |
| continue; |
| |
| printed += scnprintf(bf + printed, size - printed, "%s", printed ? ", " : ""); |
| |
| if (trace->show_arg_names) |
| printed += scnprintf(bf + printed, size - printed, "%s: ", field->name); |
| |
| printed += syscall_arg_fmt__scnprintf_val(&sc->arg_fmt[arg.idx], |
| bf + printed, size - printed, &arg, val); |
| } |
| } else if (IS_ERR(sc->tp_format)) { |
| /* |
| * If we managed to read the tracepoint /format file, then we |
| * may end up not having any args, like with gettid(), so only |
| * print the raw args when we didn't manage to read it. |
| */ |
| while (arg.idx < sc->nr_args) { |
| if (arg.mask & bit) |
| goto next_arg; |
| val = syscall_arg__val(&arg, arg.idx); |
| if (printed) |
| printed += scnprintf(bf + printed, size - printed, ", "); |
| printed += syscall__scnprintf_name(sc, bf + printed, size - printed, &arg); |
| printed += syscall_arg_fmt__scnprintf_val(&sc->arg_fmt[arg.idx], bf + printed, size - printed, &arg, val); |
| next_arg: |
| ++arg.idx; |
| bit <<= 1; |
| } |
| } |
| |
| return printed; |
| } |
| |
| typedef int (*tracepoint_handler)(struct trace *trace, struct evsel *evsel, |
| union perf_event *event, |
| struct perf_sample *sample); |
| |
| static struct syscall *trace__syscall_info(struct trace *trace, |
| struct evsel *evsel, int id) |
| { |
| int err = 0; |
| |
| if (id < 0) { |
| |
| /* |
| * XXX: Noticed on x86_64, reproduced as far back as 3.0.36, haven't tried |
| * before that, leaving at a higher verbosity level till that is |
| * explained. Reproduced with plain ftrace with: |
| * |
| * echo 1 > /t/events/raw_syscalls/sys_exit/enable |
| * grep "NR -1 " /t/trace_pipe |
| * |
| * After generating some load on the machine. |
| */ |
| if (verbose > 1) { |
| static u64 n; |
| fprintf(trace->output, "Invalid syscall %d id, skipping (%s, %" PRIu64 ") ...\n", |
| id, evsel__name(evsel), ++n); |
| } |
| return NULL; |
| } |
| |
| err = -EINVAL; |
| |
| #ifdef HAVE_SYSCALL_TABLE_SUPPORT |
| if (id > trace->sctbl->syscalls.max_id) { |
| #else |
| if (id >= trace->sctbl->syscalls.max_id) { |
| /* |
| * With libaudit we don't know beforehand what is the max_id, |
| * so we let trace__read_syscall_info() figure that out as we |
| * go on reading syscalls. |
| */ |
| err = trace__read_syscall_info(trace, id); |
| if (err) |
| #endif |
| goto out_cant_read; |
| } |
| |
| if ((trace->syscalls.table == NULL || trace->syscalls.table[id].name == NULL) && |
| (err = trace__read_syscall_info(trace, id)) != 0) |
| goto out_cant_read; |
| |
| if (trace->syscalls.table[id].name == NULL) { |
| if (trace->syscalls.table[id].nonexistent) |
| return NULL; |
| goto out_cant_read; |
| } |
| |
| return &trace->syscalls.table[id]; |
| |
| out_cant_read: |
| if (verbose > 0) { |
| char sbuf[STRERR_BUFSIZE]; |
| fprintf(trace->output, "Problems reading syscall %d: %d (%s)", id, -err, str_error_r(-err, sbuf, sizeof(sbuf))); |
| if (id <= trace->sctbl->syscalls.max_id && trace->syscalls.table[id].name != NULL) |
| fprintf(trace->output, "(%s)", trace->syscalls.table[id].name); |
| fputs(" information\n", trace->output); |
| } |
| return NULL; |
| } |
| |
| struct syscall_stats { |
| struct stats stats; |
| u64 nr_failures; |
| int max_errno; |
| u32 *errnos; |
| }; |
| |
| static void thread__update_stats(struct thread *thread, struct thread_trace *ttrace, |
| int id, struct perf_sample *sample, long err, bool errno_summary) |
| { |
| struct int_node *inode; |
| struct syscall_stats *stats; |
| u64 duration = 0; |
| |
| inode = intlist__findnew(ttrace->syscall_stats, id); |
| if (inode == NULL) |
| return; |
| |
| stats = inode->priv; |
| if (stats == NULL) { |
| stats = malloc(sizeof(*stats)); |
| if (stats == NULL) |
| return; |
| |
| stats->nr_failures = 0; |
| stats->max_errno = 0; |
| stats->errnos = NULL; |
| init_stats(&stats->stats); |
| inode->priv = stats; |
| } |
| |
| if (ttrace->entry_time && sample->time > ttrace->entry_time) |
| duration = sample->time - ttrace->entry_time; |
| |
| update_stats(&stats->stats, duration); |
| |
| if (err < 0) { |
| ++stats->nr_failures; |
| |
| if (!errno_summary) |
| return; |
| |
| err = -err; |
| if (err > stats->max_errno) { |
| u32 *new_errnos = realloc(stats->errnos, err * sizeof(u32)); |
| |
| if (new_errnos) { |
| memset(new_errnos + stats->max_errno, 0, (err - stats->max_errno) * sizeof(u32)); |
| } else { |
| pr_debug("Not enough memory for errno stats for thread \"%s\"(%d/%d), results will be incomplete\n", |
| thread__comm_str(thread), thread->pid_, thread->tid); |
| return; |
| } |
| |
| stats->errnos = new_errnos; |
| stats->max_errno = err; |
| } |
| |
| ++stats->errnos[err - 1]; |
| } |
| } |
| |
| static int trace__printf_interrupted_entry(struct trace *trace) |
| { |
| struct thread_trace *ttrace; |
| size_t printed; |
| int len; |
| |
| if (trace->failure_only || trace->current == NULL) |
| return 0; |
| |
| ttrace = thread__priv(trace->current); |
| |
| if (!ttrace->entry_pending) |
| return 0; |
| |
| printed = trace__fprintf_entry_head(trace, trace->current, 0, false, ttrace->entry_time, trace->output); |
| printed += len = fprintf(trace->output, "%s)", ttrace->entry_str); |
| |
| if (len < trace->args_alignment - 4) |
| printed += fprintf(trace->output, "%-*s", trace->args_alignment - 4 - len, " "); |
| |
| printed += fprintf(trace->output, " ...\n"); |
| |
| ttrace->entry_pending = false; |
| ++trace->nr_events_printed; |
| |
| return printed; |
| } |
| |
| static int trace__fprintf_sample(struct trace *trace, struct evsel *evsel, |
| struct perf_sample *sample, struct thread *thread) |
| { |
| int printed = 0; |
| |
| if (trace->print_sample) { |
| double ts = (double)sample->time / NSEC_PER_MSEC; |
| |
| printed += fprintf(trace->output, "%22s %10.3f %s %d/%d [%d]\n", |
| evsel__name(evsel), ts, |
| thread__comm_str(thread), |
| sample->pid, sample->tid, sample->cpu); |
| } |
| |
| return printed; |
| } |
| |
| static void *syscall__augmented_args(struct syscall *sc, struct perf_sample *sample, int *augmented_args_size, int raw_augmented_args_size) |
| { |
| void *augmented_args = NULL; |
| /* |
| * For now with BPF raw_augmented we hook into raw_syscalls:sys_enter |
| * and there we get all 6 syscall args plus the tracepoint common fields |
| * that gets calculated at the start and the syscall_nr (another long). |
| * So we check if that is the case and if so don't look after the |
| * sc->args_size but always after the full raw_syscalls:sys_enter payload, |
| * which is fixed. |
| * |
| * We'll revisit this later to pass s->args_size to the BPF augmenter |
| * (now tools/perf/examples/bpf/augmented_raw_syscalls.c, so that it |
| * copies only what we need for each syscall, like what happens when we |
| * use syscalls:sys_enter_NAME, so that we reduce the kernel/userspace |
| * traffic to just what is needed for each syscall. |
| */ |
| int args_size = raw_augmented_args_size ?: sc->args_size; |
| |
| *augmented_args_size = sample->raw_size - args_size; |
| if (*augmented_args_size > 0) |
| augmented_args = sample->raw_data + args_size; |
| |
| return augmented_args; |
| } |
| |
| static void syscall__exit(struct syscall *sc) |
| { |
| if (!sc) |
| return; |
| |
| free(sc->arg_fmt); |
| } |
| |
| static int trace__sys_enter(struct trace *trace, struct evsel *evsel, |
| union perf_event *event __maybe_unused, |
| struct perf_sample *sample) |
| { |
| char *msg; |
| void *args; |
| int printed = 0; |
| struct thread *thread; |
| int id = perf_evsel__sc_tp_uint(evsel, id, sample), err = -1; |
| int augmented_args_size = 0; |
| void *augmented_args = NULL; |
| struct syscall *sc = trace__syscall_info(trace, evsel, id); |
| struct thread_trace *ttrace; |
| |
| if (sc == NULL) |
| return -1; |
| |
| thread = machine__findnew_thread(trace->host, sample->pid, sample->tid); |
| ttrace = thread__trace(thread, trace->output); |
| if (ttrace == NULL) |
| goto out_put; |
| |
| trace__fprintf_sample(trace, evsel, sample, thread); |
| |
| args = perf_evsel__sc_tp_ptr(evsel, args, sample); |
| |
| if (ttrace->entry_str == NULL) { |
| ttrace->entry_str = malloc(trace__entry_str_size); |
| if (!ttrace->entry_str) |
| goto out_put; |
| } |
| |
| if (!(trace->duration_filter || trace->summary_only || trace->min_stack)) |
| trace__printf_interrupted_entry(trace); |
| /* |
| * If this is raw_syscalls.sys_enter, then it always comes with the 6 possible |
| * arguments, even if the syscall being handled, say "openat", uses only 4 arguments |
| * this breaks syscall__augmented_args() check for augmented args, as we calculate |
| * syscall->args_size using each syscalls:sys_enter_NAME tracefs format file, |
| * so when handling, say the openat syscall, we end up getting 6 args for the |
| * raw_syscalls:sys_enter event, when we expected just 4, we end up mistakenly |
| * thinking that the extra 2 u64 args are the augmented filename, so just check |
| * here and avoid using augmented syscalls when the evsel is the raw_syscalls one. |
| */ |
| if (evsel != trace->syscalls.events.sys_enter) |
| augmented_args = syscall__augmented_args(sc, sample, &augmented_args_size, trace->raw_augmented_syscalls_args_size); |
| ttrace->entry_time = sample->time; |
| msg = ttrace->entry_str; |
| printed += scnprintf(msg + printed, trace__entry_str_size - printed, "%s(", sc->name); |
| |
| printed += syscall__scnprintf_args(sc, msg + printed, trace__entry_str_size - printed, |
| args, augmented_args, augmented_args_size, trace, thread); |
| |
| if (sc->is_exit) { |
| if (!(trace->duration_filter || trace->summary_only || trace->failure_only || trace->min_stack)) { |
| int alignment = 0; |
| |
| trace__fprintf_entry_head(trace, thread, 0, false, ttrace->entry_time, trace->output); |
| printed = fprintf(trace->output, "%s)", ttrace->entry_str); |
| if (trace->args_alignment > printed) |
| alignment = trace->args_alignment - printed; |
| fprintf(trace->output, "%*s= ?\n", alignment, " "); |
| } |
| } else { |
| ttrace->entry_pending = true; |
| /* See trace__vfs_getname & trace__sys_exit */ |
| ttrace->filename.pending_open = false; |
| } |
| |
| if (trace->current != thread) { |
| thread__put(trace->current); |
| trace->current = thread__get(thread); |
| } |
| err = 0; |
| out_put: |
| thread__put(thread); |
| return err; |
| } |
| |
| static int trace__fprintf_sys_enter(struct trace *trace, struct evsel *evsel, |
| struct perf_sample *sample) |
| { |
| struct thread_trace *ttrace; |
| struct thread *thread; |
| int id = perf_evsel__sc_tp_uint(evsel, id, sample), err = -1; |
| struct syscall *sc = trace__syscall_info(trace, evsel, id); |
| char msg[1024]; |
| void *args, *augmented_args = NULL; |
| int augmented_args_size; |
| |
| if (sc == NULL) |
| return -1; |
| |
| thread = machine__findnew_thread(trace->host, sample->pid, sample->tid); |
| ttrace = thread__trace(thread, trace->output); |
| /* |
| * We need to get ttrace just to make sure it is there when syscall__scnprintf_args() |
| * and the rest of the beautifiers accessing it via struct syscall_arg touches it. |
| */ |
| if (ttrace == NULL) |
| goto out_put; |
| |
| args = perf_evsel__sc_tp_ptr(evsel, args, sample); |
| augmented_args = syscall__augmented_args(sc, sample, &augmented_args_size, trace->raw_augmented_syscalls_args_size); |
| syscall__scnprintf_args(sc, msg, sizeof(msg), args, augmented_args, augmented_args_size, trace, thread); |
| fprintf(trace->output, "%s", msg); |
| err = 0; |
| out_put: |
| thread__put(thread); |
| return err; |
| } |
| |
| static int trace__resolve_callchain(struct trace *trace, struct evsel *evsel, |
| struct perf_sample *sample, |
| struct callchain_cursor *cursor) |
| { |
| struct addr_location al; |
| int max_stack = evsel->core.attr.sample_max_stack ? |
| evsel->core.attr.sample_max_stack : |
| trace->max_stack; |
| int err; |
| |
| if (machine__resolve(trace->host, &al, sample) < 0) |
| return -1; |
| |
| err = thread__resolve_callchain(al.thread, cursor, evsel, sample, NULL, NULL, max_stack); |
| addr_location__put(&al); |
| return err; |
| } |
| |
| static int trace__fprintf_callchain(struct trace *trace, struct perf_sample *sample) |
| { |
| /* TODO: user-configurable print_opts */ |
| const unsigned int print_opts = EVSEL__PRINT_SYM | |
| EVSEL__PRINT_DSO | |
| EVSEL__PRINT_UNKNOWN_AS_ADDR; |
| |
| return sample__fprintf_callchain(sample, 38, print_opts, &callchain_cursor, symbol_conf.bt_stop_list, trace->output); |
| } |
| |
| static const char *errno_to_name(struct evsel *evsel, int err) |
| { |
| struct perf_env *env = evsel__env(evsel); |
| const char *arch_name = perf_env__arch(env); |
| |
| return arch_syscalls__strerrno(arch_name, err); |
| } |
| |
| static int trace__sys_exit(struct trace *trace, struct evsel *evsel, |
| union perf_event *event __maybe_unused, |
| struct perf_sample *sample) |
| { |
| long ret; |
| u64 duration = 0; |
| bool duration_calculated = false; |
| struct thread *thread; |
| int id = perf_evsel__sc_tp_uint(evsel, id, sample), err = -1, callchain_ret = 0, printed = 0; |
| int alignment = trace->args_alignment; |
| struct syscall *sc = trace__syscall_info(trace, evsel, id); |
| struct thread_trace *ttrace; |
| |
| if (sc == NULL) |
| return -1; |
| |
| thread = machine__findnew_thread(trace->host, sample->pid, sample->tid); |
| ttrace = thread__trace(thread, trace->output); |
| if (ttrace == NULL) |
| goto out_put; |
| |
| trace__fprintf_sample(trace, evsel, sample, thread); |
| |
| ret = perf_evsel__sc_tp_uint(evsel, ret, sample); |
| |
| if (trace->summary) |
| thread__update_stats(thread, ttrace, id, sample, ret, trace->errno_summary); |
| |
| if (!trace->fd_path_disabled && sc->is_open && ret >= 0 && ttrace->filename.pending_open) { |
| trace__set_fd_pathname(thread, ret, ttrace->filename.name); |
| ttrace->filename.pending_open = false; |
| ++trace->stats.vfs_getname; |
| } |
| |
| if (ttrace->entry_time) { |
| duration = sample->time - ttrace->entry_time; |
| if (trace__filter_duration(trace, duration)) |
| goto out; |
| duration_calculated = true; |
| } else if (trace->duration_filter) |
| goto out; |
| |
| if (sample->callchain) { |
| callchain_ret = trace__resolve_callchain(trace, evsel, sample, &callchain_cursor); |
| if (callchain_ret == 0) { |
| if (callchain_cursor.nr < trace->min_stack) |
| goto out; |
| callchain_ret = 1; |
| } |
| } |
| |
| if (trace->summary_only || (ret >= 0 && trace->failure_only)) |
| goto out; |
| |
| trace__fprintf_entry_head(trace, thread, duration, duration_calculated, ttrace->entry_time, trace->output); |
| |
| if (ttrace->entry_pending) { |
| printed = fprintf(trace->output, "%s", ttrace->entry_str); |
| } else { |
| printed += fprintf(trace->output, " ... ["); |
| color_fprintf(trace->output, PERF_COLOR_YELLOW, "continued"); |
| printed += 9; |
| printed += fprintf(trace->output, "]: %s()", sc->name); |
| } |
| |
| printed++; /* the closing ')' */ |
| |
| if (alignment > printed) |
| alignment -= printed; |
| else |
| alignment = 0; |
| |
| fprintf(trace->output, ")%*s= ", alignment, " "); |
| |
| if (sc->fmt == NULL) { |
| if (ret < 0) |
| goto errno_print; |
| signed_print: |
| fprintf(trace->output, "%ld", ret); |
| } else if (ret < 0) { |
| errno_print: { |
| char bf[STRERR_BUFSIZE]; |
| const char *emsg = str_error_r(-ret, bf, sizeof(bf)), |
| *e = errno_to_name(evsel, -ret); |
| |
| fprintf(trace->output, "-1 %s (%s)", e, emsg); |
| } |
| } else if (ret == 0 && sc->fmt->timeout) |
| fprintf(trace->output, "0 (Timeout)"); |
| else if (ttrace->ret_scnprintf) { |
| char bf[1024]; |
| struct syscall_arg arg = { |
| .val = ret, |
| .thread = thread, |
| .trace = trace, |
| }; |
| ttrace->ret_scnprintf(bf, sizeof(bf), &arg); |
| ttrace->ret_scnprintf = NULL; |
| fprintf(trace->output, "%s", bf); |
| } else if (sc->fmt->hexret) |
| fprintf(trace->output, "%#lx", ret); |
| else if (sc->fmt->errpid) { |
| struct thread *child = machine__find_thread(trace->host, ret, ret); |
| |
| if (child != NULL) { |
| fprintf(trace->output, "%ld", ret); |
| if (child->comm_set) |
| fprintf(trace->output, " (%s)", thread__comm_str(child)); |
| thread__put(child); |
| } |
| } else |
| goto signed_print; |
| |
| fputc('\n', trace->output); |
| |
| /* |
| * We only consider an 'event' for the sake of --max-events a non-filtered |
| * sys_enter + sys_exit and other tracepoint events. |
| */ |
| if (++trace->nr_events_printed == trace->max_events && trace->max_events != ULONG_MAX) |
| interrupted = true; |
| |
| if (callchain_ret > 0) |
| trace__fprintf_callchain(trace, sample); |
| else if (callchain_ret < 0) |
| pr_err("Problem processing %s callchain, skipping...\n", evsel__name(evsel)); |
| out: |
| ttrace->entry_pending = false; |
| err = 0; |
| out_put: |
| thread__put(thread); |
| return err; |
| } |
| |
| static int trace__vfs_getname(struct trace *trace, struct evsel *evsel, |
| union perf_event *event __maybe_unused, |
| struct perf_sample *sample) |
| { |
| struct thread *thread = machine__findnew_thread(trace->host, sample->pid, sample->tid); |
| struct thread_trace *ttrace; |
| size_t filename_len, entry_str_len, to_move; |
| ssize_t remaining_space; |
| char *pos; |
| const char *filename = evsel__rawptr(evsel, sample, "pathname"); |
| |
| if (!thread) |
| goto out; |
| |
| ttrace = thread__priv(thread); |
| if (!ttrace) |
| goto out_put; |
| |
| filename_len = strlen(filename); |
| if (filename_len == 0) |
| goto out_put; |
| |
| if (ttrace->filename.namelen < filename_len) { |
| char *f = realloc(ttrace->filename.name, filename_len + 1); |
| |
| if (f == NULL) |
| goto out_put; |
| |
| ttrace->filename.namelen = filename_len; |
| ttrace->filename.name = f; |
| } |
| |
| strcpy(ttrace->filename.name, filename); |
| ttrace->filename.pending_open = true; |
| |
| if (!ttrace->filename.ptr) |
| goto out_put; |
| |
| entry_str_len = strlen(ttrace->entry_str); |
| remaining_space = trace__entry_str_size - entry_str_len - 1; /* \0 */ |
| if (remaining_space <= 0) |
| goto out_put; |
| |
| if (filename_len > (size_t)remaining_space) { |
| filename += filename_len - remaining_space; |
| filename_len = remaining_space; |
| } |
| |
| to_move = entry_str_len - ttrace->filename.entry_str_pos + 1; /* \0 */ |
| pos = ttrace->entry_str + ttrace->filename.entry_str_pos; |
| memmove(pos + filename_len, pos, to_move); |
| memcpy(pos, filename, filename_len); |
| |
| ttrace->filename.ptr = 0; |
| ttrace->filename.entry_str_pos = 0; |
| out_put: |
| thread__put(thread); |
| out: |
| return 0; |
| } |
| |
| static int trace__sched_stat_runtime(struct trace *trace, struct evsel *evsel, |
| union perf_event *event __maybe_unused, |
| struct perf_sample *sample) |
| { |
| u64 runtime = evsel__intval(evsel, sample, "runtime"); |
| double runtime_ms = (double)runtime / NSEC_PER_MSEC; |
| struct thread *thread = machine__findnew_thread(trace->host, |
| sample->pid, |
| sample->tid); |
| struct thread_trace *ttrace = thread__trace(thread, trace->output); |
| |
| if (ttrace == NULL) |
| goto out_dump |