commit 4bd0271e5d91531a510c90f31254a781b9e78c31
Author: jbitta <bittara3@uniba.sk>
Date: Tue, 21 Jan 2025 16:28:43 +0100
init
Diffstat:
A | Makefile | | | 30 | ++++++++++++++++++++++++++++++ |
A | README.md | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main.c | | | 374 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | process_exit.bpf.c | | | 64 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | process_exit.h | | | 28 | ++++++++++++++++++++++++++++ |
5 files changed, 553 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,30 @@
+ARCH=$(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/')
+
+main: main.c process_exit.skel.h
+ gcc -Wall -o main main.c -L../libbpf/src -l:libbpf.a -lelf -lz
+
+run: main
+ sudo ./main
+
+process_exit.bpf.o: process_exit.bpf.c vmlinux.h
+ clang \
+ -target bpf \
+ -D __TARGET_ARCH_$(ARCH) \
+ -I/usr/include/$(shell uname -m)-linux-gnu \
+ -Wall -O2 -g -c process_exit.bpf.c -o process_exit.bpf.o
+
+process_exit.skel.h: process_exit.bpf.o
+ bpftool gen skeleton process_exit.bpf.o > process_exit.skel.h
+
+vmlinux.h:
+ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
+
+clean:
+ rm process_exit.bpf.o
+ rm main
+
+clean-all: clean
+ rm vmlinux.h
+ rm process_exit.skel.h
+
+.PHONY: run clean clean-all
diff --git a/README.md b/README.md
@@ -0,0 +1,57 @@
+# Dependencie
+
+Na debianových systémoch
+```bash
+sudo apt install bpftool clang linux-headers-$(uname -r)
+```
+
+# Kompilácia
+```bash
+make
+```
+
+# Spustenie
+```bash
+sudo ./main
+```
+alebo
+```
+make run
+```
+
+# Prepínače
+
+- `-h` - help
+- `-c, --csv` - výstup vo formáte csv
+- `-f FILE, --file FILE` - výstup zapíš do súbora `FILE`
+- `-F param1,param2,...`, `--filter param1,param2,...` - filtruje parametre
+- `-C, --cumulative` - vypíš kumulatívne hodnoty pre `utime`, `stime`,
+ `inblock`, `oublock`, odfiltruje `cutime`, `cstime`, `cinblock`,
+ `coublock`
+- `-u UID, --uid UID` - vypíš údaje len od procesov, ktoré vlastní
+ používateľ s uid UID
+
+povolené hodnoty parametrov pre filter:
+- pcomm
+- tgid
+- pid
+- ppid
+- uid
+- age
+- utime
+- stime
+- exit
+- exitsig
+- nvcs
+- nivcs
+- cutime
+- cstime
+- inblock
+- oublock
+- cinblock
+- coublock
+
+## Príklad použitia
+```bash
+sudo ./main -F pcomm,ppid,utime,exit
+```
diff --git a/main.c b/main.c
@@ -0,0 +1,374 @@
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <bpf/libbpf.h>
+#include "process_exit.h"
+#include "process_exit.skel.h"
+
+#define PCOMM_FLAG 0x00001
+#define TGID_FLAG 0x00002
+#define PID_FLAG 0x00004
+#define PPID_FLAG 0x00008
+#define UID_FLAG 0x00010
+#define AGE_FLAG 0x00020
+#define UTIME_FLAG 0x00040
+#define STIME_FLAG 0x00080
+#define EXIT_FLAG 0x00100
+#define EXITSIG_FLAG 0x00200
+#define NVCS_FLAG 0x00400
+#define NIVCS_FLAG 0x00800
+#define CUTIME_FLAG 0x01000
+#define CSTIME_FLAG 0x02000
+#define INBLOCK_FLAG 0x04000
+#define OUBLOCK_FLAG 0x08000
+#define CINBLOCK_FLAG 0x10000
+#define COUBLOCK_FLAG 0x20000
+
+static int libbpf_print_fn(enum libbpf_print_level level,
+ const char* format, va_list args) {
+ if (level >= LIBBPF_DEBUG)
+ return 0;
+
+ return vfprintf(stderr, format, args);
+}
+
+static int csv = 0;
+static FILE* output_file = 0;
+static uint32_t parameter_flags = 0x3ffff; // defaultne vsetky parametre
+static int enabled_flag_count = 0;
+static int current_flags_printed = 1;
+static uint64_t processes_handled = 0;
+static int cumulative = 0;
+static volatile __sig_atomic_t running = 1;
+static uint32_t filtered_uid = 0;
+
+static void parse_filter_flags(char* flag_string) {
+ const char* token = strtok(flag_string, ",");
+ while (token) {
+ if (strcmp(token, "pcomm") == 0) parameter_flags |= PCOMM_FLAG;
+ else if (strcmp(token, "tgid") == 0) parameter_flags |= TGID_FLAG;
+ else if (strcmp(token, "pid") == 0) parameter_flags |= PID_FLAG;
+ else if (strcmp(token, "ppid") == 0) parameter_flags |= PPID_FLAG;
+ else if (strcmp(token, "uid") == 0) parameter_flags |= UID_FLAG;
+ else if (strcmp(token, "age") == 0) parameter_flags |= AGE_FLAG;
+ else if (strcmp(token, "utime") == 0) parameter_flags |= UTIME_FLAG;
+ else if (strcmp(token, "stime") == 0) parameter_flags |= STIME_FLAG;
+ else if (strcmp(token, "exit") == 0) parameter_flags |= EXIT_FLAG;
+ else if (strcmp(token, "exitsig") == 0) parameter_flags |= EXITSIG_FLAG;
+ else if (strcmp(token, "nvcs") == 0) parameter_flags |= NVCS_FLAG;
+ else if (strcmp(token, "nivcs") == 0) parameter_flags |= NIVCS_FLAG;
+ else if (strcmp(token, "cutime") == 0) parameter_flags |= CUTIME_FLAG;
+ else if (strcmp(token, "cstime") == 0) parameter_flags |= CSTIME_FLAG;
+ else if (strcmp(token, "inblock") == 0) parameter_flags |= INBLOCK_FLAG;
+ else if (strcmp(token, "oublock") == 0) parameter_flags |= OUBLOCK_FLAG;
+ else if (strcmp(token, "cinblock") == 0) parameter_flags |= CINBLOCK_FLAG;
+ else if (strcmp(token, "coublock") == 0) parameter_flags |= COUBLOCK_FLAG;
+ else fprintf(stderr, "Unknown parameter '%s'\n", token);
+
+ token = strtok(NULL, ",");
+ }
+}
+
+static void print_help(char* argv[]) {
+ fprintf(stdout,
+ "Usage: %s [OPTION]...\n"
+ "Print information about terminated processes.\n"
+ "\n"
+ "Options:\n"
+ " -c, --csv use csv format\n"
+ " -f FILE, --file FILE print to file FILE\n"
+ " -F params..., --filter params... filter process information based on\n"
+ " params\n"
+ " -u UID, --uid UID show only processes owened by user\n"
+ " with uid UID\n"
+ " -C, --cumulative show cumulative data for utime,\n"
+ " stime, inblock, oublock, filters\n"
+ " out cutime, cstime, cinblock,\n"
+ " coublock\n"
+ " -h, --help print this help message and exit\n"
+ "\n"
+ "Filter parameters:\n"
+ " pcomm, tgid, pid, ppid, uid, age, utime, stime, exit, exitsig, nvcs,\n"
+ " nivcs, cutime, cstime, inblock, oublock, cinblock, coublock\n",
+ argv[0]);
+}
+
+static void parse_options(int argc, char* argv[], const char** file) {
+ static struct option long_options[] = {
+ { "csv" , no_argument, 0, 'c' },
+ { "file" , required_argument, 0, 'f' },
+ { "filter" , required_argument, 0, 'F' },
+ { "cumulative" , no_argument, 0, 'C' },
+ { "help" , no_argument, 0, 'h' },
+ { "uid" , required_argument, 0, 'u' },
+ { 0 , 0, 0, 0 },
+ };
+
+ int ret = EXIT_FAILURE;
+ int opt;
+ while ((opt = getopt_long(argc, argv, "cf:hF:Cu:", long_options, NULL)) != -1) {
+ switch (opt) {
+ case 'c':
+ csv = 1;
+ break;
+ case 'f':
+ *file = optarg;
+ break;
+ case 'F':
+ parameter_flags = 0;
+ parse_filter_flags(optarg);
+ break;
+ case 'C':
+ cumulative = 1;
+ break;
+ case 'u':
+ errno = 0;
+ filtered_uid = strtoul(optarg, NULL, 10);
+ if (errno != 0 || optarg[0] == '-') {
+ fprintf(stderr, "Invalid UID after -u.\n");
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'h':
+ ret = EXIT_SUCCESS;
+ default:
+ print_help(argv);
+ exit(ret);
+ }
+ }
+}
+
+static void print_int_parameter2(__u64 parameter, const char* format) {
+ if (csv) {
+ format = "%d";
+ if (current_flags_printed < enabled_flag_count) {
+ format = "%d,";
+ current_flags_printed++;
+ } else {
+ current_flags_printed = 1;
+ }
+ }
+ fprintf(output_file, format, parameter);
+}
+
+static void print_int_parameter(__u64 parameter) {
+ print_int_parameter2(parameter, "%-7d\t");
+}
+
+static void print_float_parameter(float parameter) {
+ const char* format = "%-7.3f\t";
+ if (csv) {
+ format = "%.3f";
+ if (current_flags_printed < enabled_flag_count) {
+ format = "%.3f,";
+ current_flags_printed++;
+ } else {
+ current_flags_printed = 1;
+ }
+ }
+ fprintf(output_file, format, parameter);
+}
+
+static void print_pcomm_parameter(const char* parameter) {
+ const char* format = "%-16s";
+ if (csv) {
+ format = "%s";
+ if (current_flags_printed < enabled_flag_count) {
+ format = "%s,";
+ current_flags_printed++;
+ } else {
+ current_flags_printed = 1;
+ }
+ }
+ fprintf(output_file, format, parameter);
+}
+
+static void print_string_parameter(const char* parameter) {
+ const char* format = "%s\t";
+ if (csv) {
+ format = "%s";
+ if (current_flags_printed < enabled_flag_count) {
+ format = "%s,";
+ current_flags_printed++;
+ } else {
+ current_flags_printed = 1;
+ }
+ }
+ fprintf(output_file, format, parameter);
+}
+
+static void handle_event(void* ctx, int cpu, void* data, unsigned int data_sz) {
+ struct data_t* event = (struct data_t*)data;
+ (void)ctx;
+ (void)cpu;
+ (void)data_sz;
+
+ if (filtered_uid != 0 && filtered_uid != event->uid) {
+ return;
+ }
+
+ if (cumulative) {
+ event->utime += event->cutime;
+ event->stime += event->cstime;
+ event->inblock += event->cinblock;
+ event->oublock += event->coublock;
+ }
+
+ processes_handled++;
+
+ if (parameter_flags & PCOMM_FLAG) print_pcomm_parameter(event->task);
+ if (parameter_flags & TGID_FLAG) print_int_parameter(event->tgid);
+ if (parameter_flags & PID_FLAG) print_int_parameter(event->pid);
+ if (parameter_flags & PPID_FLAG) print_int_parameter(event->ppid);
+ if (parameter_flags & UID_FLAG) print_int_parameter(event->uid);
+ if (parameter_flags & AGE_FLAG) print_float_parameter((event->exit_time - event->start_time) / 1e9);
+ if (parameter_flags & UTIME_FLAG) print_float_parameter(event->utime / 1e9);
+ if (parameter_flags & STIME_FLAG) print_float_parameter(event->stime / 1e9);
+ if (parameter_flags & EXIT_FLAG) print_int_parameter(WIFEXITED(event->exit_code) ? WEXITSTATUS(event->exit_code) : -1);
+ if (parameter_flags & EXITSIG_FLAG) print_int_parameter(WIFSIGNALED(event->exit_code) ? WTERMSIG(event->exit_code) : -1);
+ if (parameter_flags & NVCS_FLAG) print_int_parameter(event->nvcsw);
+ if (parameter_flags & NIVCS_FLAG) print_int_parameter(event->nivcsw);
+ if (parameter_flags & CUTIME_FLAG) print_float_parameter(event->cutime / 1e9);
+ if (parameter_flags & CSTIME_FLAG) print_float_parameter(event->cstime / 1e9);
+ if (parameter_flags & INBLOCK_FLAG) print_int_parameter(event->inblock);
+ if (parameter_flags & OUBLOCK_FLAG) print_int_parameter(event->oublock);
+ if (parameter_flags & CINBLOCK_FLAG) print_int_parameter(event->cinblock);
+ if (parameter_flags & COUBLOCK_FLAG) print_int_parameter(event->coublock);
+ fprintf(output_file, "\n");
+
+ fflush(output_file);
+}
+
+static void lost_event(void* ctx, int cpu, long long unsigned cnt) {
+ (void)ctx;
+ (void)cpu;
+ (void)cnt;
+
+ printf("lost event\n");
+}
+
+static void print_header(void) {
+ if (parameter_flags & PCOMM_FLAG ) print_pcomm_parameter("PCOMM");
+ if (parameter_flags & TGID_FLAG ) print_string_parameter("TGID");
+ if (parameter_flags & PID_FLAG ) print_string_parameter("PID");
+ if (parameter_flags & PPID_FLAG ) print_string_parameter("PPID");
+ if (parameter_flags & UID_FLAG ) print_string_parameter("UID");
+ if (parameter_flags & AGE_FLAG ) print_string_parameter("age");
+ if (parameter_flags & UTIME_FLAG ) print_string_parameter("utime");
+ if (parameter_flags & STIME_FLAG ) print_string_parameter("stime");
+ if (parameter_flags & EXIT_FLAG ) print_string_parameter("exit");
+ if (parameter_flags & EXITSIG_FLAG ) print_string_parameter("exitsig");
+ if (parameter_flags & NVCS_FLAG ) print_string_parameter("nvcsw");
+ if (parameter_flags & NIVCS_FLAG ) print_string_parameter("nivcsw");
+ if (parameter_flags & CUTIME_FLAG ) print_string_parameter("cutime");
+ if (parameter_flags & CSTIME_FLAG ) print_string_parameter("cstime");
+ if (parameter_flags & INBLOCK_FLAG ) print_string_parameter("inblk");
+ if (parameter_flags & OUBLOCK_FLAG ) print_string_parameter("oublk");
+ if (parameter_flags & CINBLOCK_FLAG) print_string_parameter("cinblk");
+ if (parameter_flags & COUBLOCK_FLAG) print_string_parameter("coublk");
+ fprintf(output_file, "\n");
+
+ fflush(output_file);
+}
+
+static void catch_interrupt(int signal) {
+ (void)signal;
+ running = 0;
+}
+
+int main(int argc, char* argv[]) {
+
+ output_file = stdout;
+ const char* file_name = NULL;
+ parse_options(argc, argv, &file_name);
+ if ((enabled_flag_count = __builtin_popcount(parameter_flags)) == 0) {
+ fprintf(stderr, "No valid parameters in filter\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (cumulative) {
+ // pri kumulativnom vypise nahradime hodnoty suctom
+ // rodicovskych a dcerskych hodnot, preto nebudeme vypisovat
+ // cutime, cstime, atd.
+ parameter_flags &= ~CUTIME_FLAG;
+ parameter_flags &= ~CSTIME_FLAG;
+ parameter_flags &= ~CINBLOCK_FLAG;
+ parameter_flags &= ~COUBLOCK_FLAG;
+ enabled_flag_count -= 4;
+ }
+
+ if (getuid() != 0) {
+ fprintf(stderr, "You should run this program with root privileges (sudo).\n");
+ exit(EXIT_FAILURE);
+ }
+
+ libbpf_set_print(libbpf_print_fn);
+
+ struct process_exit_bpf* skel = process_exit_bpf__open_and_load();
+ if (!skel) {
+ fprintf(stderr, "Failed to open BPF object file\n");
+ return 1;
+ }
+
+ int err = process_exit_bpf__attach(skel);
+ if (err) {
+ fprintf(stderr, "Failed to attach BPF skeleton: %d\n", err);
+ process_exit_bpf__destroy(skel);
+ return 1;
+ }
+
+ struct perf_buffer* pb = perf_buffer__new(bpf_map__fd(skel->maps.output),
+ 8,
+ handle_event,
+ lost_event,
+ NULL,
+ NULL);
+ if (!pb) {
+ err = -1;
+ fprintf(stderr, "Failed to create ring buffer\n");
+ process_exit_bpf__destroy(skel);
+ return -err;
+ }
+
+ if (signal(SIGINT, catch_interrupt) == SIG_ERR) {
+ fprintf(stderr, "Can't set signal handler: %s\n", strerror(errno));
+ err = 1;
+ running = 0;
+ }
+
+ int writing_to_file = 0;
+ if (file_name != NULL && strcmp(file_name, "-") != 0) {
+ output_file = fopen(file_name, "w+");
+ writing_to_file = 1;
+ }
+
+ print_header();
+ while (running) {
+ err = perf_buffer__poll(pb, 1000);
+ if (err == -EINTR) {
+ err = 0;
+ break;
+ }
+ if (err < 0) {
+ printf("Error polling perf buffer: %d\n", err);
+ break;
+ }
+ }
+
+ printf("\nHandled %lu processes.\n", processes_handled);
+ fflush(stdout);
+
+ if (writing_to_file) {
+ fclose(output_file);
+ }
+
+ perf_buffer__free(pb);
+ process_exit_bpf__destroy(skel);
+
+ return -err;
+}
diff --git a/process_exit.bpf.c b/process_exit.bpf.c
@@ -0,0 +1,64 @@
+#include "vmlinux.h"
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+#include "process_exit.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
+ __uint(key_size, sizeof(u32));
+ __uint(value_size, sizeof(u32));
+} output SEC(".maps");
+
+SEC("tracepoint/sched/sched_process_exit")
+int sched_process_exit(void* ctx)
+{
+ struct task_struct* task = (typeof(task))bpf_get_current_task();
+ struct data_t data = {};
+
+ bpf_probe_read_kernel(&data.start_time, sizeof(data.start_time), &task->start_time);
+ data.exit_time = bpf_ktime_get_ns(),
+ bpf_probe_read_kernel(&data.utime, sizeof(data.utime), &task->utime);
+ bpf_probe_read_kernel(&data.stime, sizeof(data.stime), &task->stime);
+ bpf_probe_read_kernel(&data.tgid, sizeof(data.tgid), &task->tgid);
+ bpf_probe_read_kernel(&data.pid, sizeof(data.pid), &task->pid);
+
+ struct task_struct* real_parent = NULL;
+ bpf_probe_read_kernel(&real_parent, sizeof(struct task_struct*), &task->real_parent);
+ bpf_probe_read_kernel(&data.ppid, sizeof(data.ppid), &real_parent->pid);
+
+ struct cred* real_cred = NULL;
+ bpf_probe_read_kernel(&real_cred, sizeof(struct cred*), &task->real_cred);
+ bpf_probe_read_kernel(&data.uid, sizeof(data.uid), &real_cred->uid);
+
+ bpf_probe_read_kernel(&data.exit_code, sizeof(data.exit_code), &task->exit_code);
+ bpf_probe_read_kernel(&data.exit_signal, sizeof(data.exit_signal), &task->exit_signal);
+ bpf_probe_read_kernel(&data.nvcsw, sizeof(data.nvcsw), &task->nvcsw);
+ bpf_probe_read_kernel(&data.nivcsw, sizeof(data.nivcsw), &task->nivcsw);
+
+ struct signal_struct* sig = NULL;
+ bpf_probe_read_kernel(&sig, sizeof(struct signal_struct*), &task->signal);
+ bpf_probe_read_kernel(&data.cutime, sizeof(data.cutime), &sig->cutime);
+ bpf_probe_read_kernel(&data.cstime, sizeof(data.cstime), &sig->cstime);
+ bpf_probe_read_kernel(&data.inblock, sizeof(data.inblock), &sig->inblock);
+ bpf_probe_read_kernel(&data.oublock, sizeof(data.oublock), &sig->oublock);
+ bpf_probe_read_kernel(&data.cinblock, sizeof(data.cinblock), &sig->cinblock);
+ bpf_probe_read_kernel(&data.coublock, sizeof(data.coublock), &sig->coublock);
+
+ struct task_io_accounting* ioac = NULL;
+ bpf_probe_read_kernel(&ioac, sizeof(struct ioac_accounting*), &task->ioac);
+ __u64 read = 0;
+ __u64 written = 0;
+ bpf_probe_read_kernel(&read, sizeof(read), &ioac->read_bytes);
+ bpf_probe_read_kernel(&written, sizeof(written), &ioac->write_bytes);
+ data.inblock += read >> 9;
+ data.oublock += written >> 9;
+
+ bpf_get_current_comm(&data.task, sizeof(data.task));
+
+ bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU,
+ &data, sizeof(data));
+
+ return 0;
+}
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
diff --git a/process_exit.h b/process_exit.h
@@ -0,0 +1,28 @@
+#ifndef PROCESS_EXIT_H
+#define PROCESS_EXIT_H
+
+#define TASK_COMM_LEN 16
+
+struct data_t {
+ __u64 start_time;
+ __u64 exit_time;
+ __u64 utime;
+ __u64 stime;
+ __u32 tgid;
+ __u32 pid;
+ __u32 ppid;
+ __u32 uid;
+ __s32 exit_code;
+ __s32 exit_signal;
+ __u64 nvcsw;
+ __u64 nivcsw;
+ __u64 cutime;
+ __u64 cstime;
+ __u64 inblock;
+ __u64 oublock;
+ __u64 cinblock;
+ __u64 coublock;
+ char task[TASK_COMM_LEN];
+};
+
+#endif /* PROCESS_EXIT_H */