285 lines
6.0 KiB
C
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, ¶m_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);
|