diff --git a/README.md b/README.md index 65d33f7e..1fc9a5ad 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,22 @@ TIME EVENT COMM PID PPID FILENAME/EXIT CODE ... ``` +## bootstrap_legacy + +`bootstrap_legacy` is a version of `bootstrap` modified to run on older kernels that do not support BPF ring buffer maps (BPF_MAP_TYPE_RINGBUF) introduced in kernel 5.8. `bootstrap_legacy` replaces the ring buffer maps with perf event array (BPF_MAP_TYPE_PERF_EVENT_ARRAY) for compatibility with older kernel versions. + +```shell +$ cd examples/c +$ make bootstrap_legacy +$ sudo ./bootstrap_legacy +TIME EVENT COMM PID PPID FILENAME/EXIT CODE +10:26:32 EXEC sh 4101543 4054526 /bin/sh +10:26:32 EXEC python 4101545 4101543 /usr/bin/python +10:26:32 EXIT python 4101545 4101543 [0] (9ms) +10:26:32 EXIT sh 4101543 4054526 [0] (10ms) +... +``` + ## uprobe `uprobe` is an example of dealing with user-space entry and exit (return) probes, diff --git a/examples/c/Makefile b/examples/c/Makefile index 6a82f267..912b4e5e 100644 --- a/examples/c/Makefile +++ b/examples/c/Makefile @@ -24,7 +24,7 @@ INCLUDES := -I$(OUTPUT) -I../../libbpf/include/uapi -I$(dir $(VMLINUX)) -I$(LIBB CFLAGS := -g -Wall ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) -APPS = minimal minimal_legacy minimal_ns bootstrap uprobe kprobe fentry \ +APPS = minimal minimal_legacy minimal_ns bootstrap bootstrap_legacy uprobe kprobe fentry \ usdt sockfilter tc ksyscall task_iter lsm CARGO ?= $(shell which cargo) diff --git a/examples/c/bootstrap_legacy.bpf.c b/examples/c/bootstrap_legacy.bpf.c new file mode 100644 index 00000000..eba5daef --- /dev/null +++ b/examples/c/bootstrap_legacy.bpf.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* Copyright (c) 2020 Facebook */ +#include "vmlinux.h" +#include +#include +#include +#include "bootstrap.h" + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 8192); + __type(key, pid_t); + __type(value, u64); +} exec_start SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(u32)); + __uint(value_size, sizeof(u32)); +} pe SEC(".maps"); + +const volatile unsigned long long min_duration_ns = 0; + +SEC("tp/sched/sched_process_exec") +int handle_exec(struct trace_event_raw_sched_process_exec *ctx) +{ + struct task_struct *task; + unsigned fname_off; + struct event *e = &(struct event){}; + pid_t pid; + u64 ts; + + /* remember time exec() was executed for this PID */ + pid = bpf_get_current_pid_tgid() >> 32; + ts = bpf_ktime_get_ns(); + bpf_map_update_elem(&exec_start, &pid, &ts, BPF_ANY); + + /* don't emit exec events when minimum duration is specified */ + if (min_duration_ns) + return 0; + + /* fill out the sample with data */ + task = (struct task_struct *)bpf_get_current_task(); + + e->exit_event = false; + e->pid = pid; + e->ppid = BPF_CORE_READ(task, real_parent, tgid); + bpf_get_current_comm(&e->comm, sizeof(e->comm)); + + fname_off = ctx->__data_loc_filename & 0xFFFF; + bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off); + + /* use perf event output to send data to user-space for post-processing */ + bpf_perf_event_output(ctx, &pe, BPF_F_CURRENT_CPU, e, sizeof(*e)); + return 0; +} + +SEC("tp/sched/sched_process_exit") +int handle_exit(struct trace_event_raw_sched_process_template *ctx) +{ + struct task_struct *task; + struct event *e = &(struct event){}; + pid_t pid, tid; + u64 id, ts, *start_ts, duration_ns = 0; + + /* get PID and TID of exiting thread/process */ + id = bpf_get_current_pid_tgid(); + pid = id >> 32; + tid = (u32)id; + + /* ignore thread exits */ + if (pid != tid) + return 0; + + /* if we recorded start of the process, calculate lifetime duration */ + start_ts = bpf_map_lookup_elem(&exec_start, &pid); + if (start_ts) + duration_ns = bpf_ktime_get_ns() - *start_ts; + else if (min_duration_ns) + return 0; + bpf_map_delete_elem(&exec_start, &pid); + + /* if process didn't live long enough, return early */ + if (min_duration_ns && duration_ns < min_duration_ns) + return 0; + + /* fill out the sample with data */ + task = (struct task_struct *)bpf_get_current_task(); + + e->exit_event = true; + e->duration_ns = duration_ns; + e->pid = pid; + e->ppid = BPF_CORE_READ(task, real_parent, tgid); + e->exit_code = (BPF_CORE_READ(task, exit_code) >> 8) & 0xff; + bpf_get_current_comm(&e->comm, sizeof(e->comm)); + + /* use perf event output to send data to user-space for post-processing */ + bpf_perf_event_output(ctx, &pe, BPF_F_CURRENT_CPU, e, sizeof(*e)); + + return 0; +} diff --git a/examples/c/bootstrap_legacy.c b/examples/c/bootstrap_legacy.c new file mode 100644 index 00000000..7776862d --- /dev/null +++ b/examples/c/bootstrap_legacy.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include +#include +#include +#include +#include +#include +#include "bootstrap.h" +#include "bootstrap_legacy.skel.h" + +static struct env { + bool verbose; + long min_duration_ms; +} env; + +const char *argp_program_version = "bootstrap 0.0"; +const char *argp_program_bug_address = ""; +const char argp_program_doc[] = "BPF bootstrap demo application.\n" + "\n" + "It traces process start and exits and shows associated \n" + "information (filename, process duration, PID and PPID, etc).\n" + "\n" + "USAGE: ./bootstrap [-d ] [-v]\n"; + +static const struct argp_option opts[] = { + { "verbose", 'v', NULL, 0, "Verbose debug output" }, + { "duration", 'd', "DURATION-MS", 0, "Minimum process duration (ms) to report" }, + {}, +}; + +static error_t parse_arg(int key, char *arg, struct argp_state *state) +{ + switch (key) { + case 'v': + env.verbose = true; + break; + case 'd': + errno = 0; + env.min_duration_ms = strtol(arg, NULL, 10); + if (errno || env.min_duration_ms <= 0) { + fprintf(stderr, "Invalid duration: %s\n", arg); + argp_usage(state); + } + break; + case ARGP_KEY_ARG: + argp_usage(state); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static const struct argp argp = { + .options = opts, + .parser = parse_arg, + .doc = argp_program_doc, +}; + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) +{ + if (level == LIBBPF_DEBUG && !env.verbose) + return 0; + return vfprintf(stderr, format, args); +} + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) +{ + const 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 (e->exit_event) { + printf("%-8s %-5s %-16s %-7d %-7d [%u]", ts, "EXIT", e->comm, e->pid, e->ppid, + e->exit_code); + if (e->duration_ns) + printf(" (%llums)", e->duration_ns / 1000000); + printf("\n"); + } else { + printf("%-8s %-5s %-16s %-7d %-7d %s\n", ts, "EXEC", e->comm, e->pid, e->ppid, + e->filename); + } +} + +int main(int argc, char **argv) +{ + // struct ring_buffer *rb = NULL; + struct perf_buffer *pe = NULL; + struct bootstrap_legacy_bpf *skel; + int err; + + /* Parse command line arguments */ + err = argp_parse(&argp, argc, argv, 0, NULL, NULL); + if (err) + return err; + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + + /* Cleaner handling of Ctrl-C */ + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Load and verify BPF application */ + skel = bootstrap_legacy_bpf__open(); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + /* Parameterize BPF code with minimum duration parameter */ + skel->rodata->min_duration_ns = env.min_duration_ms * 1000000ULL; + + /* Load & verify BPF programs */ + err = bootstrap_legacy_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load and verify BPF skeleton\n"); + goto cleanup; + } + + /* Attach tracepoints */ + err = bootstrap_legacy_bpf__attach(skel); + if (err) { + fprintf(stderr, "Failed to attach BPF skeleton\n"); + goto cleanup; + } + + /* Set up perf buffer polling */ + pe = perf_buffer__new(bpf_map__fd(skel->maps.pe), 8, handle_event, NULL, NULL, NULL); + if (!pe) { + err = -1; + fprintf(stderr, "Failed to create perf event buffer\n"); + goto cleanup; + } + + /* Process events */ + printf("%-8s %-5s %-16s %-7s %-7s %s\n", "TIME", "EVENT", "COMM", "PID", "PPID", + "FILENAME/EXIT CODE"); + while (!exiting) { + err = perf_buffer__poll(pe, 100 /* timeout, ms */); + /* Ctrl-C will cause -EINTR */ + if (err == -EINTR) { + err = 0; + break; + } + if (err < 0) { + printf("Error polling perf buffer: %d\n", err); + break; + } + } + +cleanup: + /* Clean up */ + perf_buffer__free(pe); + bootstrap_legacy_bpf__destroy(skel); + + return err < 0 ? -err : 0; +}