diff --git a/libbpf-tools/.gitignore b/libbpf-tools/.gitignore index 49766f33802e..242306ac1803 100644 --- a/libbpf-tools/.gitignore +++ b/libbpf-tools/.gitignore @@ -24,13 +24,14 @@ /funclatency /gethostlatency /hardirqs +/killsnoop /klockstat /ksnoop /llcstat -/nfsdist -/nfsslower /mdflush /mountsnoop +/nfsdist +/nfsslower /numamove /offcputime /oomkill @@ -39,6 +40,7 @@ /runqlat /runqlen /runqslower +/sigsnoop /slabratetop /softirqs /solisten diff --git a/libbpf-tools/Makefile b/libbpf-tools/Makefile index a6a0bf922b1a..7c8b5f8e83a5 100644 --- a/libbpf-tools/Makefile +++ b/libbpf-tools/Makefile @@ -52,6 +52,7 @@ APPS = \ runqlat \ runqlen \ runqslower \ + sigsnoop \ slabratetop \ softirqs \ solisten \ @@ -71,7 +72,8 @@ export OUTPUT BPFTOOL ARCH BTFHUB_ARCHIVE APPS FSDIST_ALIASES = btrfsdist ext4dist nfsdist xfsdist FSSLOWER_ALIASES = btrfsslower ext4slower nfsslower xfsslower -APP_ALIASES = $(FSDIST_ALIASES) $(FSSLOWER_ALIASES) +SIGSNOOP_ALIAS = killsnoop +APP_ALIASES = $(FSDIST_ALIASES) $(FSSLOWER_ALIASES) ${SIGSNOOP_ALIAS} COMMON_OBJ = \ $(OUTPUT)/trace_helpers.o \ @@ -171,6 +173,10 @@ $(FSDIST_ALIASES): fsdist $(call msg,SYMLINK,$@) $(Q)ln -f -s $^ $@ +$(SIGSNOOP_ALIAS): sigsnoop + $(call msg,SYMLINK,$@) + $(Q)ln -f -s $^ $@ + install: $(APPS) $(APP_ALIASES) $(call msg, INSTALL libbpf-tools) $(Q)$(INSTALL) -m 0755 -d $(DESTDIR)$(prefix)/bin diff --git a/libbpf-tools/sigsnoop.bpf.c b/libbpf-tools/sigsnoop.bpf.c new file mode 100644 index 000000000000..e03981fc1d6a --- /dev/null +++ b/libbpf-tools/sigsnoop.bpf.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2021~2022 Hengqi Chen */ +#include +#include +#include "sigsnoop.h" + +#define MAX_ENTRIES 10240 + +const volatile pid_t filtered_pid = 0; +const volatile int target_signal = 0; +const volatile bool failed_only = false; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_ENTRIES); + __type(key, __u32); + __type(value, struct event); +} values SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} events SEC(".maps"); + +static int probe_entry(pid_t tpid, int sig) +{ + struct event event = {}; + __u64 pid_tgid; + __u32 pid, tid; + + if (target_signal && sig != target_signal) + return 0; + + pid_tgid = bpf_get_current_pid_tgid(); + pid = pid_tgid >> 32; + tid = (__u32)pid_tgid; + if (filtered_pid && pid != filtered_pid) + return 0; + + event.pid = pid; + event.tpid = tpid; + event.sig = sig; + bpf_get_current_comm(event.comm, sizeof(event.comm)); + bpf_map_update_elem(&values, &tid, &event, BPF_ANY); + return 0; +} + +static int probe_exit(void *ctx, int ret) +{ + __u64 pid_tgid = bpf_get_current_pid_tgid(); + __u32 tid = (__u32)pid_tgid; + struct event *eventp; + + eventp = bpf_map_lookup_elem(&values, &tid); + if (!eventp) + return 0; + + if (failed_only && ret >= 0) + goto cleanup; + + eventp->ret = ret; + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, eventp, sizeof(*eventp)); + +cleanup: + bpf_map_delete_elem(&values, &tid); + return 0; +} + +SEC("tracepoint/syscalls/sys_enter_kill") +int kill_entry(struct trace_event_raw_sys_enter *ctx) +{ + pid_t tpid = (pid_t)ctx->args[0]; + int sig = (int)ctx->args[1]; + + return probe_entry(tpid, sig); +} + +SEC("tracepoint/syscalls/sys_exit_kill") +int kill_exit(struct trace_event_raw_sys_exit *ctx) +{ + return probe_exit(ctx, ctx->ret); +} + +SEC("tracepoint/syscalls/sys_enter_tkill") +int tkill_entry(struct trace_event_raw_sys_enter *ctx) +{ + pid_t tpid = (pid_t)ctx->args[0]; + int sig = (int)ctx->args[1]; + + return probe_entry(tpid, sig); +} + +SEC("tracepoint/syscalls/sys_exit_tkill") +int tkill_exit(struct trace_event_raw_sys_exit *ctx) +{ + return probe_exit(ctx, ctx->ret); +} + +SEC("tracepoint/syscalls/sys_enter_tgkill") +int tgkill_entry(struct trace_event_raw_sys_enter *ctx) +{ + pid_t tpid = (pid_t)ctx->args[1]; + int sig = (int)ctx->args[2]; + + return probe_entry(tpid, sig); +} + +SEC("tracepoint/syscalls/sys_exit_tgkill") +int tgkill_exit(struct trace_event_raw_sys_exit *ctx) +{ + return probe_exit(ctx, ctx->ret); +} + +SEC("tracepoint/signal/signal_generate") +int sig_trace(struct trace_event_raw_signal_generate *ctx) +{ + struct event event = {}; + pid_t tpid = ctx->pid; + int ret = ctx->errno; + int sig = ctx->sig; + __u64 pid_tgid; + __u32 pid; + + if (failed_only && ret == 0) + return 0; + + if (target_signal && sig != target_signal) + return 0; + + pid_tgid = bpf_get_current_pid_tgid(); + pid = pid_tgid >> 32; + if (filtered_pid && pid != filtered_pid) + return 0; + + event.pid = pid; + event.tpid = tpid; + event.sig = sig; + event.ret = ret; + bpf_get_current_comm(event.comm, sizeof(event.comm)); + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); + return 0; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/libbpf-tools/sigsnoop.c b/libbpf-tools/sigsnoop.c new file mode 100644 index 000000000000..f2ec392a26fb --- /dev/null +++ b/libbpf-tools/sigsnoop.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) + +/* + * sigsnoop Trace standard and real-time signals. + * + * Copyright (c) 2021~2022 Hengqi Chen + * + * 08-Aug-2021 Hengqi Chen Created this. + */ +#include +#include +#include +#include + +#include +#include "sigsnoop.h" +#include "sigsnoop.skel.h" + +#define PERF_BUFFER_PAGES 16 +#define PERF_POLL_TIMEOUT_MS 100 +#define warn(...) fprintf(stderr, __VA_ARGS__) + +static volatile sig_atomic_t exiting = 0; + +static pid_t target_pid = 0; +static int target_signal = 0; +static bool failed_only = false; +static bool kill_only = false; +static bool signal_name = false; +static bool verbose = false; + +static const char *sig_name[] = { + [0] = "N/A", + [1] = "SIGHUP", + [2] = "SIGINT", + [3] = "SIGQUIT", + [4] = "SIGILL", + [5] = "SIGTRAP", + [6] = "SIGABRT", + [6] = "SIGIOT", + [7] = "SIGBUS", + [8] = "SIGFPE", + [9] = "SIGKILL", + [10] = "SIGUSR1", + [11] = "SIGSEGV", + [12] = "SIGUSR2", + [13] = "SIGPIPE", + [14] = "SIGALRM", + [15] = "SIGTERM", + [16] = "SIGSTKFLT", + [17] = "SIGCHLD", + [18] = "SIGCONT", + [19] = "SIGSTOP", + [20] = "SIGTSTP", + [21] = "SIGTTIN", + [22] = "SIGTTOU", + [23] = "SIGURG", + [24] = "SIGXCPU", + [25] = "SIGXFSZ", + [26] = "SIGVTALRM", + [27] = "SIGPROF", + [28] = "SIGWINCH", + [29] = "SIGIO", + [30] = "SIGPWR", + [31] = "SIGSYS", +}; + +const char *argp_program_version = "sigsnoop 0.1"; +const char *argp_program_bug_address = + "https://github.com/iovisor/bcc/tree/master/libbpf-tools"; +const char argp_program_doc[] = +"Trace standard and real-time signals.\n" +"\n" +"USAGE: sigsnoop [-h] [-x] [-k] [-n] [-p PID] [-s SIGNAL]\n" +"\n" +"EXAMPLES:\n" +" sigsnoop # trace signals system-wide\n" +" sigsnoop -k # trace signals issued by kill syscall only\n" +" sigsnoop -x # trace failed signals only\n" +" sigsnoop -p 1216 # only trace PID 1216\n" +" sigsnoop -s 9 # only trace signal 9\n"; + +static const struct argp_option opts[] = { + { "failed", 'x', NULL, 0, "Trace failed signals only." }, + { "kill", 'k', NULL, 0, "Trace signals issued by kill syscall only." }, + { "pid", 'p', "PID", 0, "Process ID to trace" }, + { "signal", 's', "SIGNAL", 0, "Signal to trace." }, + { "name", 'n', NULL, 0, "Output signal name instead of signal number." }, + { "verbose", 'v', NULL, 0, "Verbose debug output" }, + { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" }, + {}, +}; + +static error_t parse_arg(int key, char *arg, struct argp_state *state) +{ + long pid, sig; + + switch (key) { + case 'p': + errno = 0; + pid = strtol(arg, NULL, 10); + if (errno || pid <= 0) { + warn("Invalid PID: %s\n", arg); + argp_usage(state); + } + target_pid = pid; + break; + case 's': + errno = 0; + sig = strtol(arg, NULL, 10); + if (errno || sig <= 0) { + warn("Invalid SIGNAL: %s\n", arg); + argp_usage(state); + } + target_signal = sig; + break; + case 'n': + signal_name = true; + break; + case 'x': + failed_only = true; + break; + case 'k': + kill_only = true; + break; + case 'v': + verbose = true; + break; + case 'h': + argp_state_help(state, stderr, ARGP_HELP_STD_HELP); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) +{ + if (level == LIBBPF_DEBUG && !verbose) + return 0; + return vfprintf(stderr, format, args); +} + +static void alias_parse(char *prog) +{ + char *name = basename(prog); + + if (!strcmp(name, "killsnoop")) { + kill_only = true; + } +} + +static void sig_int(int signo) +{ + exiting = 1; +} + +static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) +{ + struct event *e = data; + struct tm *tm; + char ts[32]; + time_t t; + + time(&t); + tm = localtime(&t); + strftime(ts, sizeof(ts), "%H:%M:%S", tm); + if (signal_name) + printf("%-8s %-7d %-16s %-9s %-7d %-6d\n", + ts, e->pid, e->comm, sig_name[e->sig], e->tpid, e->ret); + else + printf("%-8s %-7d %-16s %-9d %-7d %-6d\n", + ts, e->pid, e->comm, e->sig, e->tpid, e->ret); +} + +static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) +{ + warn("lost %llu events on CPU #%d\n", lost_cnt, cpu); +} + +int main(int argc, char **argv) +{ + static const struct argp argp = { + .options = opts, + .parser = parse_arg, + .doc = argp_program_doc, + }; + struct perf_buffer *pb = NULL; + struct sigsnoop_bpf *obj; + int err; + + alias_parse(argv[0]); + err = argp_parse(&argp, argc, argv, 0, NULL, NULL); + if (err) + return err; + + libbpf_set_strict_mode(LIBBPF_STRICT_ALL); + libbpf_set_print(libbpf_print_fn); + + obj = sigsnoop_bpf__open(); + if (!obj) { + warn("failed to open BPF object\n"); + return 1; + } + + obj->rodata->filtered_pid = target_pid; + obj->rodata->target_signal = target_signal; + obj->rodata->failed_only = failed_only; + + if (kill_only) { + bpf_program__set_autoload(obj->progs.sig_trace, false); + } else { + bpf_program__set_autoload(obj->progs.kill_entry, false); + bpf_program__set_autoload(obj->progs.kill_exit, false); + bpf_program__set_autoload(obj->progs.tkill_entry, false); + bpf_program__set_autoload(obj->progs.tkill_exit, false); + bpf_program__set_autoload(obj->progs.tgkill_entry, false); + bpf_program__set_autoload(obj->progs.tgkill_exit, false); + } + + err = sigsnoop_bpf__load(obj); + if (err) { + warn("failed to load BPF object: %d\n", err); + goto cleanup; + } + + err = sigsnoop_bpf__attach(obj); + if (err) { + warn("failed to attach BPF programs: %d\n", err); + goto cleanup; + } + + pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES, + handle_event, handle_lost_events, NULL, NULL); + if (!pb) { + err = -errno; + warn("failed to open perf buffer: %d\n", err); + goto cleanup; + } + + if (signal(SIGINT, sig_int) == SIG_ERR) { + warn("can't set signal handler: %s\n", strerror(errno)); + goto cleanup; + } + + printf("%-8s %-7s %-16s %-9s %-7s %-6s\n", + "TIME", "PID", "COMM", "SIG", "TPID", "RESULT"); + + while (!exiting) { + err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS); + if (err < 0 && err != -EINTR) { + warn("error polling perf buffer: %s\n", strerror(-err)); + goto cleanup; + } + /* reset err to return 0 if exiting */ + err = 0; + } + +cleanup: + perf_buffer__free(pb); + sigsnoop_bpf__destroy(obj); + + return err != 0; +} diff --git a/libbpf-tools/sigsnoop.h b/libbpf-tools/sigsnoop.h new file mode 100644 index 000000000000..cc2c5b2f8350 --- /dev/null +++ b/libbpf-tools/sigsnoop.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2021~2022 Hengqi Chen */ +#ifndef __SIGSNOOP_H +#define __SIGSNOOP_H + +#define TASK_COMM_LEN 16 + +struct event { + __u32 pid; + __u32 tpid; + int sig; + int ret; + char comm[TASK_COMM_LEN]; +}; + +#endif /* __SIGSNOOP_H */ diff --git a/libbpf-tools/sigsnoop_example.txt b/libbpf-tools/sigsnoop_example.txt new file mode 100644 index 000000000000..737568b9cc4c --- /dev/null +++ b/libbpf-tools/sigsnoop_example.txt @@ -0,0 +1,45 @@ +Demonstrations of sigsnoop. + + +This traces signals generated system wide. For example: + +# ./sigsnoop -n +TIME PID COMM SIG TPID RESULT +19:56:14 3204808 a.out SIGSEGV 3204808 0 +19:56:14 3204808 a.out SIGPIPE 3204808 0 +19:56:14 3204808 a.out SIGCHLD 3204722 0 + +The first line showed that a.out (a test program) deliver a SIGSEGV signal. +The result, 0, means success. + +The second and third lines showed that a.out also deliver SIGPIPE/SIGCHLD +signals successively. + +USAGE message: + +# ./sigsnoop -h +Usage: sigsnoop [OPTION...] +Trace standard and real-time signals. + +USAGE: sigsnoop [-h] [-x] [-k] [-n] [-p PID] [-s SIGNAL] + +EXAMPLES: + sigsnoop # trace signals system-wide + sigsnoop -k # trace signals issued by kill syscall only + sigsnoop -x # trace failed signals only + sigsnoop -p 1216 # only trace PID 1216 + sigsnoop -s 9 # only trace signal 9 + + -k, --kill Trace signals issued by kill syscall only. + -n, --name Output signal name instead of signal number. + -p, --pid=PID Process ID to trace + -s, --signal=SIGNAL Signal to trace. + -x, --failed Trace failed signals only. + -?, --help Give this help list + --usage Give a short usage message + -V, --version Print program version + +Mandatory or optional arguments to long options are also mandatory or optional +for any corresponding short options. + +Report bugs to https://github.com/iovisor/bcc/tree/master/libbpf-tools.