/*
* 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 <api/fs/tracing_path.h>
#ifdef HAVE_LIBBPF_SUPPORT
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <bpf/btf.h>
#endif
#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/hashmap.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/trace.h"
#include "util/util.h"
#include "trace/beauty/beauty.h"
#include "trace-event.h"
#include "util/parse-events.h"
#include "util/tracepoint.h"
#include "callchain.h"
#include "print_binary.h"
#include "string2.h"
#include "syscalltbl.h"
#include "../perf.h"
#include "trace_augment.h"
#include "dwarf-regs.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/list_sort.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>
#include <tools/libc_compat.h>
#ifdef HAVE_LIBTRACEEVENT
#include <event-parse.h>
#endif
#ifndef O_CLOEXEC
# define O_CLOEXEC 02000000
#endif
#ifndef F_LINUX_SPECIFIC_BASE
# define F_LINUX_SPECIFIC_BASE 1024
#endif
#define RAW_SYSCALL_ARGS_NUM 6
/*
* strtoul: Go from a string to a value, i.e. for msr: MSR_FS_BASE to 0xc0000100
*
* We have to explicitely mark the direction of the flow of data, if from the
* kernel to user space or the other way around, since the BPF collector we
* have so far copies only from user to kernel space, mark the arguments that
* go that direction, so that we don´t end up collecting the previous contents
* for syscall args that goes from kernel to user space.
*/
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 from_user;
bool show_zero;
#ifdef HAVE_LIBBPF_SUPPORT
const struct btf_type *type;
int type_id;
/* used in btf_dump */
#endif
};
struct syscall_fmt {
const char *name;
const char *alias;
struct {
const char *sys_enter,
*sys_exit;
} bpf_prog_name;
struct syscall_arg_fmt arg[RAW_SYSCALL_ARGS_NUM];
u8 nr_args;
bool errpid;
bool timeout;
bool hexret;
};
struct trace {
struct perf_env host_env;
struct perf_tool tool;
struct {
/** Sorted sycall numbers used by the trace. */
struct syscall **table;
/** Size of table. */
size_t table_size;
struct {
struct evsel *sys_enter,
*sys_exit,
*bpf_output;
} events;
} syscalls;
#ifdef HAVE_LIBBPF_SUPPORT
struct btf *btf;
#endif
struct record_opts opts;
struct evlist *evlist;
struct machine *host;
struct thread *current;
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;
/*
* TODO: The map is from an ID (aka system call number) to struct
* syscall_stats. If there is >1 e_machine, such as i386 and x86-64
* processes, then the stats here will gather wrong the statistics for
* the non EM_HOST system calls. A fix would be to add the e_machine
* into the key, but this would make the code inconsistent with the
* per-thread version.
*/
struct hashmap *syscall_stats;
double duration_filter;
double runtime_ms;
unsigned long pfmaj, pfmin;
struct {
u64 vfs_getname,
proc_getname;
} stats;
unsigned int max_stack;
unsigned int min_stack;
enum trace_summary_mode summary_mode;
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;
bool force_btf;
bool summary_bpf;
int trace_pgfaults;
char *perfconfig_events;
struct {
struct ordered_events data;
u64 last;
} oe;
const char *uid_str;
};
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 {
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) {
const struct tep_event *tp_format = evsel__tp_format(evsel);
if (tp_format == NULL)
goto out_delete;
et->fmt = calloc(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
val = strtoul(tok, NULL, 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" , "DUPFD_QUERY" , [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
// 'argname' is just documentational at this point, to remove the previous comment with that info
#define SCA_FILENAME_FROM_USER(argname) \
{ .scnprintf = SCA_FILENAME, \
.from_user = true , }
static size_t syscall_arg__scnprintf_buf(char *bf, size_t size, struct syscall_arg *arg);
#define SCA_BUF syscall_arg__scnprintf_buf
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
#ifdef HAVE_LIBBPF_SUPPORT
static void syscall_arg_fmt__cache_btf_enum(struct syscall_arg_fmt *arg_fmt, struct btf *btf, char *type)
{
int id;
type = strstr(type, "enum " );
if (type == NULL)
return ;
type += 5; // skip "enum " to get the enumeration name
id = btf__find_by_name(btf, type);
if (id < 0)
return ;
arg_fmt->type = btf__type_by_id(btf, id);
}
static bool syscall_arg__strtoul_btf_enum(char *bf, size_t size, struct syscall_arg *arg, u64 *val)
{
const struct btf_type *bt = arg->fmt->type;
struct btf *btf = arg->trace->btf;
struct btf_enum *be = btf_enum(bt);
for (int i = 0; i < btf_vlen(bt); ++i, ++be) {
const char *name = btf__name_by_offset(btf, be->name_off);
int max_len = max(size, strlen(name));
if (strncmp(name, bf, max_len) == 0) {
*val = be->val;
return true ;
}
}
return false ;
}
static bool syscall_arg__strtoul_btf_type(char *bf, size_t size, struct syscall_arg *arg, u64 *val)
{
const struct btf_type *bt;
char *type = arg->type_name;
struct btf *btf;
trace__load_vmlinux_btf(arg->trace);
btf = arg->trace->btf;
if (btf == NULL)
return false ;
if (arg->fmt->type == NULL) {
// See if this is an enum
syscall_arg_fmt__cache_btf_enum(arg->fmt, btf, type);
}
// Now let's see if we have a BTF type resolved
bt = arg->fmt->type;
if (bt == NULL)
return false ;
// If it is an enum:
if (btf_is_enum(arg->fmt->type))
return syscall_arg__strtoul_btf_enum(bf, size, arg, val);
return false ;
}
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;
}
struct trace_btf_dump_snprintf_ctx {
char *bf;
size_t printed, size;
};
static void trace__btf_dump_snprintf(void *vctx, const char *fmt, va_list args)
{
struct trace_btf_dump_snprintf_ctx *ctx = vctx;
ctx->printed += vscnprintf(ctx->bf + ctx->printed, ctx->size - ctx->printed, fmt, args);
}
static size_t btf_struct_scnprintf(const struct btf_type *type, struct btf *btf, char *bf, size_t size, struct syscall_arg *arg)
{
struct trace_btf_dump_snprintf_ctx ctx = {
.bf = bf,
.size = size,
};
struct augmented_arg *augmented_arg = arg->augmented.args;
int type_id = arg->fmt->type_id, consumed;
struct btf_dump *btf_dump;
LIBBPF_OPTS(btf_dump_opts, dump_opts);
LIBBPF_OPTS(btf_dump_type_data_opts, dump_data_opts);
if (arg == NULL || arg->augmented.args == NULL)
return 0;
dump_data_opts.compact = true ;
dump_data_opts.skip_names = !arg->trace->show_arg_names;
btf_dump = btf_dump__new(btf, trace__btf_dump_snprintf, &ctx, &dump_opts);
if (btf_dump == NULL)
return 0;
/* pretty print the struct data here */
if (btf_dump__dump_type_data(btf_dump, type_id, arg->augmented.args->value, type->size, &dump_data_opts) == 0)
return 0;
consumed = sizeof (*augmented_arg) + augmented_arg->size;
arg->augmented.args = ((void *)arg->augmented.args) + consumed;
arg->augmented.size -= consumed;
btf_dump__free(btf_dump);
return ctx.printed;
}
static size_t trace__btf_scnprintf(struct trace *trace, struct syscall_arg *arg, char *bf,
size_t size, int val, char *type)
{
struct syscall_arg_fmt *arg_fmt = arg->fmt;
if (trace->btf == NULL)
return 0;
if (arg_fmt->type == NULL) {
// Check if this is an enum and if we have the BTF type for it.
syscall_arg_fmt__cache_btf_enum(arg_fmt, trace->btf, type);
}
// Did we manage to find a BTF type for the syscall/tracepoint argument?
if (arg_fmt->type == NULL)
return 0;
if (btf_is_enum(arg_fmt->type))
return btf_enum_scnprintf(arg_fmt->type, trace->btf, bf, size, val);
else if (btf_is_struct(arg_fmt->type) || btf_is_union(arg_fmt->type))
return btf_struct_scnprintf(arg_fmt->type, trace->btf, bf, size, arg);
return 0;
}
#else // HAVE_LIBBPF_SUPPORT
static size_t trace__btf_scnprintf(struct trace *trace __maybe_unused, struct syscall_arg *arg __maybe_unused,
char *bf __maybe_unused, size_t size __maybe_unused, int val __maybe_unused,
char *type __maybe_unused)
{
return 0;
}
static bool syscall_arg__strtoul_btf_type(char *bf __maybe_unused, size_t size __maybe_unused,
struct syscall_arg *arg __maybe_unused, u64 *val __maybe_unused)
{
return false ;
}
#endif // HAVE_LIBBPF_SUPPORT
#define STUL_BTF_TYPE syscall_arg__strtoul_btf_type
#define STRARRAY(name, array) \
{ .scnprintf = SCA_STRARRAY, \
.strtoul = STUL_STRARRAY, \
.parm = &strarray__## array, \
.show_zero = true , }
#define STRARRAY_FLAGS(name, array) \
{ .scnprintf = SCA_STRARRAY_FLAGS, \
.strtoul = STUL_STRARRAY_FLAGS, \
.parm = &strarray__## array, \
.show_zero = true , }
#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 const 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] = SCA_SOCKADDR_FROM_USER(umyaddr),
[2] = { .scnprintf = SCA_INT, /* addrlen */ }, }, },
{ .name = "bpf" ,
.arg = { [0] = STRARRAY(cmd, bpf_cmd),
[1] = { .from_user = true /* attr */, }, } },
{ .name = "brk" , .hexret = true ,
.arg = { [0] = { .scnprintf = SCA_PTR, /* brk */ }, }, },
{ .name = "clock_gettime" ,
.arg = { [0] = STRARRAY(clk_id, clockid), }, },
{ .name = "clock_nanosleep" ,
.arg = { [2] = SCA_TIMESPEC_FROM_USER(req), }, },
{ .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] = SCA_SOCKADDR_FROM_USER(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 = "faccessat" ,
.arg = { [0] = { .scnprintf = SCA_FDAT, /* dirfd */ },
[1] = SCA_FILENAME_FROM_USER(pathname),
[2] = { .scnprintf = SCA_ACCMODE, /* mode */ }, }, },
{ .name = "faccessat2" ,
.arg = { [0] = { .scnprintf = SCA_FDAT, /* dirfd */ },
[1] = SCA_FILENAME_FROM_USER(pathname),
[2] = { .scnprintf = SCA_ACCMODE, /* mode */ },
[3] = { .scnprintf = SCA_FACCESSAT2_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] = SCA_FILENAME_FROM_USER(path),
[2] = { .scnprintf = SCA_FSPICK_FLAGS, /* flags */ }, }, },
{ .name = "fstat" , .alias = "newfstat" , },
{ .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, .show_zero = true , /* prot */ },
[3] = { .scnprintf = SCA_MMAP_FLAGS, /* flags */
.strtoul = STUL_STRARRAY_FLAGS,
.parm = &strarray__mmap_flags, },
[5] = { .scnprintf = SCA_HEX, /* offset */ }, }, },
{ .name = "mount" ,
.arg = { [0] = SCA_FILENAME_FROM_USER(devname),
[3] = { .scnprintf = SCA_MOUNT_FLAGS, /* flags */
.mask_val = SCAMV_MOUNT_FLAGS, /* flags */ }, }, },
{ .name = "move_mount" ,
.arg = { [0] = { .scnprintf = SCA_FDAT, /* from_dfd */ },
[1] = SCA_FILENAME_FROM_USER(pathname),
[2] = { .scnprintf = SCA_FDAT, /* to_dfd */ },
[3] = SCA_FILENAME_FROM_USER(pathname),
[4] = { .scnprintf = SCA_MOVE_MOUNT_FLAGS, /* flags */ }, }, },
{ .name = "mprotect" ,
.arg = { [0] = { .scnprintf = SCA_HEX, /* start */ },
[2] = { .scnprintf = SCA_MMAP_PROT, .show_zero = true , /* prot */ }, }, },
{ .name = "mq_unlink" ,
.arg = { [0] = SCA_FILENAME_FROM_USER(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 = "nanosleep" ,
.arg = { [0] = SCA_TIMESPEC_FROM_USER(req), }, },
{ .name = "newfstatat" , .alias = "fstatat" ,
.arg = { [0] = { .scnprintf = SCA_FDAT, /* dirfd */ },
[1] = SCA_FILENAME_FROM_USER(pathname),
[3] = { .scnprintf = SCA_FS_AT_FLAGS, /* flags */ }, }, },
{ .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 = { [0] = SCA_PERF_ATTR_FROM_USER(attr),
[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, .show_zero = true , /* 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),
[2] = { .from_user = true /* new_rlim */, }, }, },
{ .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 = "rseq" ,
.arg = { [0] = { .from_user = true /* rseq */, }, }, },
{ .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] = SCA_SOCKADDR_FROM_USER(addr), }, },
{ .name = "set_robust_list" ,
.arg = { [0] = { .from_user = true /* head */, }, }, },
{ .name = "set_tid_address" , .errpid = true , },
{ .name = "setitimer" ,
.arg = { [0] = STRARRAY(which, itimers), }, },
{ .name = "setrlimit" ,
.arg = { [0] = STRARRAY(resource, rlimit_resources),
[1] = { .from_user = true /* rlim */, }, }, },
{ .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_FS_AT_FLAGS, /* flags */ } ,
[3] = { .scnprintf = SCA_STATX_MASK, /* mask */ }, }, },
{ .name = "swapoff" ,
.arg = { [0] = SCA_FILENAME_FROM_USER(specialfile), }, },
{ .name = "swapon" ,
.arg = { [0] = SCA_FILENAME_FROM_USER(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] = SCA_FILENAME_FROM_USER(name), }, },
{ .name = "uname" , .alias = "newuname" , },
{ .name = "unlinkat" ,
.arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ },
[1] = SCA_FILENAME_FROM_USER(pathname),
[2] = { .scnprintf = SCA_FS_AT_FLAGS, /* flags */ }, }, },
{ .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 */ }, }, },
{ .name = "write" ,
.arg = { [1] = { .scnprintf = SCA_BUF /* buf */, .from_user = true, }, }, },
};
static int syscall_fmt__cmp(const void *name, const void *fmtp)
{
const struct syscall_fmt *fmt = fmtp;
return strcmp(name, fmt->name);
}
static const struct syscall_fmt *__syscall_fmt__find(const struct syscall_fmt *fmts,
const int nmemb,
const char *name)
{
return bsearch(name, fmts, nmemb, sizeof (struct syscall_fmt), syscall_fmt__cmp);
}
static const 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 const struct syscall_fmt *__syscall_fmt__find_by_alias(const 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 const 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);
}
/**
* struct syscall
*/
struct syscall {
/** @e_machine: The ELF machine associated with the entry. */
int e_machine;
/** @id: id value from the tracepoint, the system call number. */
int id;
struct tep_event *tp_format;
int nr_args;
/**
* @args_size: sum of the sizes of the syscall arguments, anything
* after that is augmented stuff: pathname for openat, etc.
*/
int args_size;
struct {
struct bpf_program *sys_enter,
*sys_exit;
} bpf_prog;
/** @is_exit: is this "exit" or "exit_group"? */
bool is_exit;
/**
* @is_open: is this "open" or "openat"? To associate the fd returned in
* sys_exit with the pathname in sys_enter.
*/
bool is_open;
/**
* @nonexistent: Name lookup failed. Just a hole in the syscall table,
* syscall id not allocated.
*/
bool nonexistent;
bool use_btf;
struct tep_format_field *args;
const char *name;
const struct syscall_fmt *fmt;
struct syscall_arg_fmt *arg_fmt;
};
/*
* 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 hashmap *syscall_stats;
};
static size_t syscall_id_hash(long key, void *ctx __maybe_unused)
{
return key;
}
static bool syscall_id_equal(long key1, long key2, void *ctx __maybe_unused)
{
return key1 == key2;
}
static struct hashmap *alloc_syscall_stats(void )
{
return hashmap__new(syscall_id_hash, syscall_id_equal, NULL);
}
static void delete_syscall_stats(struct hashmap *syscall_stats)
{
struct hashmap_entry *pos;
size_t bkt;
if (syscall_stats == NULL)
return ;
hashmap__for_each_entry(syscall_stats, pos, bkt)
zfree(&pos->pvalue);
hashmap__free(syscall_stats);
}
static struct thread_trace *thread_trace__new(struct trace *trace)
{
struct thread_trace *ttrace = zalloc(sizeof (struct thread_trace));
if (ttrace) {
ttrace->files.max = -1;
if (trace->summary) {
ttrace->syscall_stats = alloc_syscall_stats();
if (IS_ERR(ttrace->syscall_stats))
zfree(&ttrace);
}
}
return ttrace;
}
static void thread_trace__free_files(struct thread_trace *ttrace);
static void thread_trace__delete(void *pttrace)
{
struct thread_trace *ttrace = pttrace;
if (!ttrace)
return ;
delete_syscall_stats(ttrace->syscall_stats);
ttrace->syscall_stats = NULL;
thread_trace__free_files(ttrace);
zfree(&ttrace->entry_str);
free(ttrace);
}
static struct thread_trace *thread__trace(struct thread *thread, struct trace *trace)
{
struct thread_trace *ttrace;
if (thread == NULL)
goto fail;
if (thread__priv(thread) == NULL)
thread__set_priv(thread, thread_trace__new(trace));
if (thread__priv(thread) == NULL)
goto fail;
ttrace = thread__priv(thread);
++ttrace->nr_events;
return ttrace;
fail:
color_fprintf(trace->output, 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 void thread_trace__free_files(struct thread_trace *ttrace)
{
for (int i = 0; i <= ttrace->files.max; ++i) {
struct file *file = ttrace->files.table + i;
zfree(&file->pathname);
}
zfree(&ttrace->files.table);
ttrace->files.max = -1;
}
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) == thread__tid(thread)) {
scnprintf(linkname, sizeof (linkname),
"/proc/%d/fd/%d" , thread__pid(thread), fd);
} else {
scnprintf(linkname, sizeof (linkname),
"/proc/%d/task/%d/fd/%d" ,
thread__pid(thread), thread__tid(thread), 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;
}
#define MAX_CONTROL_CHAR 31
#define MAX_ASCII 127
static size_t syscall_arg__scnprintf_buf(char *bf, size_t size, struct syscall_arg *arg)
{
struct augmented_arg *augmented_arg = arg->augmented.args;
unsigned char *orig = (unsigned char *)augmented_arg->value;
size_t printed = 0;
int consumed;
if (augmented_arg == NULL)
return 0;
for (int j = 0; j < augmented_arg->size; ++j) {
bool control_char = orig[j] <= MAX_CONTROL_CHAR || orig[j] >= MAX_ASCII;
/* print control characters (0~31 and 127), and non-ascii characters in \(digits) */
printed += scnprintf(bf + printed, size - printed, control_char ? "\\%d" : "%c" , (int )orig[j]);
}
consumed = sizeof (*augmented_arg) + augmented_arg->size;
arg->augmented.args = ((void *)arg->augmented.args) + consumed;
arg->augmented.size -= consumed;
return printed;
}
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 pid_t workload_pid = -1;
static volatile sig_atomic_t done = false ;
static volatile sig_atomic_t interrupted = false ;
static void sighandler_interrupt(int sig __maybe_unused)
{
done = interrupted = true ;
}
static void sighandler_chld(int sig __maybe_unused, siginfo_t *info,
void *context __maybe_unused)
{
if (info->si_pid == workload_pid)
done = true ;
}
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(thread));
}
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" , (u64)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(const 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, int argc, const char **argv,
struct evlist *evlist)
{
int err = symbol__init(NULL);
if (err)
return err;
perf_env__init(&trace->host_env);
err = perf_env__set_cmdline(&trace->host_env, argc, argv);
if (err)
goto out;
trace->host = machine__new_host(&trace->host_env);
if (trace->host == NULL) {
err = -ENOMEM;
goto out;
}
thread__set_priv_destructor(thread_trace__delete);
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) {
perf_env__exit(&trace->host_env);
symbol__exit();
}
return err;
}
static void trace__symbols__exit(struct trace *trace)
{
machine__exit(trace->host);
trace->host = NULL;
perf_env__exit(&trace->host_env);
symbol__exit();
}
static int syscall__alloc_arg_fmts(struct syscall *sc, int nr_args)
{
int idx;
if (nr_args == RAW_SYSCALL_ARGS_NUM && 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 const 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 const struct syscall_arg_fmt *
__syscall_arg_fmt__find_by_name(const 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 const 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,
bool *use_btf)
{
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);
// As far as heuristics (or intention) goes this seems to hold true, and makes sense!
if ((field->flags & TEP_FIELD_IS_POINTER) && strstarts(field->type, "const " ))
arg->from_user = true ;
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*
* grep -E '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 if (strstr(field->type, "enum" ) && use_btf != NULL) {
*use_btf = true ;
arg->strtoul = STUL_BTF_TYPE;
} else {
const 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,
&sc->use_btf);
if (last_field)
sc->args_size = last_field->offset + last_field->size;
return 0;
}
static int syscall__read_info(struct syscall *sc, struct trace *trace)
{
char tp_name[128];
const char *name;
int err;
if (sc->nonexistent)
return -EEXIST;
if (sc->name) {
/* Info already read. */
return 0;
}
name = syscalltbl__name(sc->e_machine, sc->id);
if (name == NULL) {
sc->nonexistent = true ;
return -EEXIST;
}
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);
}
/*
* Fails to read trace point format via sysfs node, so the trace point
* doesn't exist. Set the 'nonexistent' flag as true.
*/
if (IS_ERR(sc->tp_format)) {
sc->nonexistent = true ;
err = PTR_ERR(sc->tp_format);
sc->tp_format = NULL;
return err;
}
/*
* The tracepoint format contains __syscall_nr field, so it's one more
* than the actual number of syscall arguments.
*/
if (syscall__alloc_arg_fmts(sc, sc->tp_format->format.nr_fields - 1))
return -ENOMEM;
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" );
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, bool *use_btf)
{
struct syscall_arg_fmt *fmt = evsel__syscall_arg_fmt(evsel);
if (fmt != NULL) {
const struct tep_event *tp_format = evsel__tp_format(evsel);
if (tp_format) {
syscall_arg_fmt__init_array(fmt, tp_format->format.fields, use_btf);
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;
/*
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=94 H=95 G=94
¤ Dauer der Verarbeitung: 0.40 Sekunden
¤
*© Formatika GbR, Deutschland