Programming lesson
Building Custom System Calls in Linux: A Lizard Legion Guide to Process Logging
Learn how to implement custom system calls in Linux with a fun Silurian overlords theme. This tutorial covers kernel-level process log levels, static library creation, and security checks.
Introduction: Why Custom System Calls Matter in Modern Linux Development
System calls are the fundamental interface between user-space applications and the Linux kernel. In this tutorial, inspired by the Lizard Legion mission to build a coded message subsystem for the Silurian overlords, you'll learn how to create three custom system calls that manage a kernel-wide process log level. This project mirrors real-world scenarios in cloud computing (like Sky Skink) where tracking metadata and logging are critical. By the end, you'll understand how to extend the kernel's diagnostic logging (dmesg) with process-level filtering, a skill relevant for OS development, embedded systems, and even AI-driven monitoring tools.
Understanding the Kernel Log Level System
The Linux kernel uses eight log levels, from KERN_EMERG (0) to KERN_DEBUG (7). In this project, you'll map these to process log levels with names like PROC_OVERRIDE (0) and PROC_DEBUG (7). The system-wide process log level initializes to 0, meaning only override messages are logged. Any process can read this level, but only the superuser can change it. Messages with a level lower than or equal to the current level are logged; otherwise, they're ignored. This security model is similar to how modern apps like Slack or Discord manage notification priorities—only critical alerts get through when you're in do-not-disturb mode.
Part 1: Creating the Kernel-Wide Process Log Level Variable
First, you need a persistent variable in the kernel. Add an integer variable, say proc_log_level, in a suitable kernel file (e.g., kernel/printk.c). Initialize it to 0. This variable must survive until shutdown. Think of it as a global setting like your phone's Do Not Disturb slider—it stays until you change it.
// In kernel/printk.c or a new file
int proc_log_level = 0;
EXPORT_SYMBOL(proc_log_level);
Part 2: Implementing the System Calls
You'll implement three system calls: get_proc_log_level, set_proc_log_level, and proc_log_message. Each system call must be registered in the kernel's syscall table. For x86_64, you'll edit arch/x86/entry/syscalls/syscall_64.tbl and add entries. For example:
548 common get_proc_log_level sys_get_proc_log_level
549 common set_proc_log_level sys_set_proc_log_level
550 common proc_log_message sys_proc_log_message
Then implement the functions in a new file, say kernel/proc_log.c:
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
SYSCALL_DEFINE0(get_proc_log_level)
{
return proc_log_level;
}
SYSCALL_DEFINE1(set_proc_log_level, int, new_level)
{
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (new_level < 0 || new_level > 7)
return -EINVAL;
proc_log_level = new_level;
return new_level;
}
SYSCALL_DEFINE2(proc_log_message, char __user *, msg, int, level)
{
char kmsg[129];
int len;
if (level < 0 || level > 7)
return -EINVAL;
if (level > proc_log_level)
return level; // message ignored, but return level
len = strncpy_from_user(kmsg, msg, 128);
if (len < 0)
return -EFAULT;
kmsg[len] = '\0';
printk(KERN_INFO "PROC_%s [%s, %d]: %s\n",
level_to_name(level), current->comm, current->pid, kmsg);
return level;
}
Note: level_to_name is a helper you'll write to map numbers to names like ERROR, WARNING, etc. The format must match exactly: $log_level_name [$executable, $pid]: $message.
Part 3: Creating the Static Library
Create a directory process_log with process_log.h and libprocess_log.a. The header defines the API:
#ifndef PROCESS_LOG_H
#define PROCESS_LOG_H
#define PROC_OVERRIDE 0
#define PROC_ALERT 1
#define PROC_CRITICAL 2
#define PROC_ERROR 3
#define PROC_WARNING 4
#define PROC_NOTICE 5
#define PROC_INFO 6
#define PROC_DEBUG 7
int get_proc_log_level(void);
int set_proc_log_level(int new_level);
int proc_log_message(int level, char *message);
// Harness functions
int *get_proc_log_level_harness(void);
int *set_proc_log_level_harness(int new_level);
int *proc_log_message_harness(int level, char *message);
#endif
The implementation file (process_log.c) uses syscall():
#include <unistd.h>
#include <sys/syscall.h>
#include "process_log.h"
#define GET_PROC_LOG_LEVEL 548
#define SET_PROC_LOG_LEVEL 549
#define PROC_LOG_MESSAGE 550
int get_proc_log_level(void) {
return syscall(GET_PROC_LOG_LEVEL);
}
int set_proc_log_level(int new_level) {
return syscall(SET_PROC_LOG_LEVEL, new_level);
}
int proc_log_message(int level, char *message) {
return syscall(PROC_LOG_MESSAGE, message, level);
}
// Harness functions return array for direct syscall invocation
int *get_proc_log_level_harness(void) {
static int arr[2] = {GET_PROC_LOG_LEVEL, 0};
return arr;
}
int *set_proc_log_level_harness(int new_level) {
static int arr[3] = {SET_PROC_LOG_LEVEL, 1, 0};
arr[2] = new_level;
return arr;
}
int *proc_log_message_harness(int level, char *message) {
static int arr[4] = {PROC_LOG_MESSAGE, 2, 0, 0};
// Note: message pointer is passed as integer, cast carefully
arr[2] = (int)message;
arr[3] = level;
return arr;
}
Compile into a static library:
gcc -c process_log.c -o process_log.o
ar rcs libprocess_log.a process_log.o
Provide a Makefile:
all: libprocess_log.a
libprocess_log.a: process_log.o
ar rcs $@ $^
process_log.o: process_log.c process_log.h
gcc -c $< -o $@
clean:
rm -f *.o *.a
Part 4: Testing and Security Considerations
Create a user-space test program that includes process_log.h and links against libprocess_log.a. For example:
#include <stdio.h>
#include "process_log.h"
int main() {
int level = get_proc_log_level();
printf("Current log level: %d\n", level);
// Try to set (will fail without root)
if (set_proc_log_level(3) == -1)
printf("Failed to set log level (expected without root)\n");
// Log a message
proc_log_message(PROC_WARNING, "This is a test message");
return 0;
}
Run with sudo to test setting. Check dmesg for your logged messages. The harness functions allow the instructor to invoke system calls directly, bypassing library security checks—ensuring your kernel code is secure.
Real-World Connections: From Lizard Legion to AI Logging
This project mirrors how modern cloud platforms like AWS CloudWatch or Google Cloud Logging filter logs by severity. In AI training pipelines, logging levels help debug model performance without flooding storage. Even in gaming, games like Valorant use similar priority systems for anti-cheat logs. By mastering custom system calls, you're building skills for OS development, cybersecurity, and high-performance computing.
Conclusion
You've implemented three custom system calls for process logging, complete with a static library and security model. This project not only satisfies the Lizard Legion but also gives you hands-on experience with kernel programming—a rare and valuable skill. Remember to take VM snapshots as advised; kernel development can crash your system. Now go forth and serve your reptilian overlords!