Files
2026-05-23 14:54:14 +02:00

285 lines
6.0 KiB
C

/**
* Mit Hilfe von Kernel-Tracepoints Syscalls zählen.
*
* Kompilieren: make
* Laden: sudo make install
* Testen: cat /sys/kernel/debug/syscall_counter
* sudo make test
* Entladen: sudo make uninstall
* Kernel-Log: sudo dmesg -w
*
* Autor: Magnus Küderli
* Lizenz: GPL
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/seq_file.h>
#include <linux/tracepoint.h>
#include <linux/tracepoint-defs.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/debugfs.h>
#include <linux/limits.h>
// === OPTIONS ===
// alternative to spinlocks with less performance loss
#define USE_ATOMICS
// header generated by ./gen_syscall_names.sh called in make
// optional because it wasnt provided by default
#define WANT_SYSCALL_NAMES
// === === === ===
#ifdef WANT_SYSCALL_NAMES
# include "syscall_names.h"
# define MAX_SYSCALLS SYSCALL_MAX_ID
#else
# define MAX_SYSCALLS 512 // default value
#endif
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Magnus Küderli");
MODULE_DESCRIPTION("Count number of syscalls per id and expose via debugfs");
// === Parameter ===
static bool enabled = true;
static int param_set(const char* val, const struct kernel_param *kp) {
int val_num;
int base = 10;
int err = kstrtoint(val, base, &val_num);
if (err) {
return err;
}
if (val_num && !(val_num == 1)) {
// invalid input
return -EINVAL;
}
err = param_set_bool(val, kp);
if (err) {
return err;
}
// successfully updated parameter
printk(KERN_INFO "%s\n", val_num ? "Counter enabled!" : "Counter disabled!");
return 0;
}
static int param_get(char* buffer, const struct kernel_param *kp) {
// write value into buffer
int err = param_get_bool(buffer, kp);
return err;
}
static const struct kernel_param_ops param_ops = {
.set = param_set,
.get = param_get,
};
module_param_cb(enabled, &param_ops, &enabled, 0644);
MODULE_PARM_DESC(enabled, "Toggle to disable the syscall counter (default: true)");
// === === === === =
#ifndef USE_ATOMICS
unsigned long syscalls[MAX_SYSCALLS];
spinlock_t syscalls_lock;
#else
atomic64_t syscalls[MAX_SYSCALLS];
#endif
struct tracepoint *tp;
// function to hook into tracepoint
static void probe_sys_enter(void *data, struct pt_regs *regs, long id)
{
if (!enabled) {
// we stopped counting
return;
}
if (
id >= MAX_SYSCALLS ||
id < 0
) {
printk(KERN_INFO "Probe recieved syscall id out of bounds\n");
// id out of bounds;
return;
}
// overflow is not realistic (~> 500 years on busy sys)
#ifndef USE_ATOMICS
unsigned long flags;
// irqsave to save flags so a multiple interrupts on the same core dont deadlock
spin_lock_irqsave(&syscalls_lock, flags);
syscalls[id]++;
spin_unlock_irqrestore(&syscalls_lock, flags);
#else
atomic64_inc(&syscalls[id]);
#endif
}
static int syscall_counter_show(struct seq_file *m, void *v)
{
/*
seq_printf(m, "Systemaufruf-Zähler:\n");
seq_printf(m, "--------------------\n");
seq_printf(m, "Nr Anzahl Name\n");
*/
for (long id = 0; id < MAX_SYSCALLS; id++) {
#ifdef WANT_SYSCALL_NAMES
char const * const name = syscall_name(id);
#else
char const * const name = "unknown";
#endif
#ifndef USE_ATOMICS
spin_lock(&syscalls_lock);
unsigned long value = syscalls[id];
spin_unlock(&syscalls_lock);
#else
unsigned long value = atomic64_read(&syscalls[id]);
#endif
// ohne name, da Header fehlt
seq_printf(m, "%-4ld %-8lu %s\n", id, value, name);
}
return 0;
}
static int syscall_counter_open(struct inode *inode, struct file *file)
{
return single_open(file, syscall_counter_show, NULL);
}
static void lookup_sys_enter_tp(struct tracepoint *tp, void *tp_ptr) {
// cast to tp pointer from void
struct tracepoint **found = tp_ptr;
if (!strcmp(tp->name, "sys_enter")) {
*found = tp;
}
}
static int find_sys_enter_tp(struct tracepoint **tp_ptr)
{
*tp_ptr = NULL; // setzt tp auf NULL
for_each_kernel_tracepoint(lookup_sys_enter_tp, tp_ptr);
if (!*tp_ptr) {
// tp wasnt found, tp_ptr still null
return -ENOENT; // error no entry found
}
return 0;
}
static int register_tp(void)
{
int ret;
ret = find_sys_enter_tp(&tp);
if (ret) {
return ret;
}
ret = tracepoint_probe_register_prio(
tp,
probe_sys_enter,
NULL, // data (inp to probe funct)
0 // prio (0 is highest)
);
return ret;
}
static void unregister_tp(void)
{
tracepoint_probe_unregister(
tp,
probe_sys_enter,
NULL
);
tracepoint_synchronize_unregister(); // gegen use-after-free, invalide mem zugriffe nach entladen
}
// === Debug fs ===
static struct dentry *dbg_file;
static const struct file_operations syscall_fops = {
.open = syscall_counter_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init mod_syscall_tracepoint_init(void)
{
int failure;
#ifndef USE_ATOMICS
memset(&syscalls, 0, sizeof(syscalls));
spin_lock_init(&syscalls_lock);
#else
for (int i = 0; i < MAX_SYSCALLS; i++) {
atomic64_set(&syscalls[i], 0);
}
#endif
dbg_file = debugfs_create_file(
"syscall_counter",
0444, // octal permission numbering
NULL, // parent dir - here its root of fs
NULL, // optional data thingy
&syscall_fops // file operations
);
failure = register_tp();
if (failure) {
debugfs_remove(dbg_file);
return failure;
}
printk(KERN_INFO "BSL sys_tracer loaded successfully!\n");
return 0;
}
static void __exit mod_syscall_tracepoint_exit(void)
{
debugfs_remove(dbg_file);
unregister_tp();
printk(KERN_INFO "BSL sys_tracer unloaded successfully!\n");
}
module_init(mod_syscall_tracepoint_init);
module_exit(mod_syscall_tracepoint_exit);