libbpf-bootstrap Development Guide: System call hook - ksyscall

Table of contents

code analysis

BPF program section

Function Description

BPF_KSYSCALL

User program part

Function Description

Execution effect

The difference between ksyscall and kprobe


code analysis

BPF program section
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>

#define TASK_COMM_LEN 16

SEC("ksyscall/tgkill")
int BPF_KSYSCALL(tgkill_entry, pid_t tgid, pid_t tid, int sig)
{
	char comm[TASK_COMM_LEN];
	__u32 caller_pid = bpf_get_current_pid_tgid() >> 32;

	if (sig == 0) {
		/*
			If sig is 0, then no signal is sent, but existence and permission
			checks are still performed; this can be used to check for the
			existence of a process ID or process group ID that the caller is
			permitted to signal.
		*/
		return 0;
	}

	bpf_get_current_comm(&comm, sizeof(comm));
	bpf_printk(
		"tgkill syscall called by PID %d (%s) for thread id %d with pid %d and signal %d.",
		caller_pid, comm, tid, tgid, sig);
	return 0;
}

SEC("ksyscall/kill")
int BPF_KSYSCALL(entry_probe, pid_t pid, int sig)
{
	char comm[TASK_COMM_LEN];
	__u32 caller_pid = bpf_get_current_pid_tgid() >> 32;

	if (sig == 0) {
		/*
			If sig is 0, then no signal is sent, but existence and permission
			checks are still performed; this can be used to check for the
			existence of a process ID or process group ID that the caller is
			permitted to signal.
		*/
		return 0;
	}

	bpf_get_current_comm(&comm, sizeof(comm));
	bpf_printk("KILL syscall called by PID %d (%s) for PID %d with signal %d.", caller_pid,
		   comm, pid, sig);
	return 0;
}

char _license[] SEC("license") = "GPL";
Function Description

It traces two system calls: kill and tgkill. When these system calls are executed, the BPF program will print out relevant information.

Both tgkill and kill are Linux system calls, mainly used to send signals to processes or threads. But their working methods and usage scenarios are different.

  1. tgkill system call: The tgkill system call accepts three parameters: tgid (target process group ID), tid (target thread ID), and sig (signal to be sent). The main purpose of tgkill is to allow one process to send signals to another specific thread in the same process group. This system call was introduced to solve the problem of kill and tkill being unable to accurately send signals to specific threads.
  2. kill system call: The kill system call accepts two parameters: pid (target process ID) and sig (signal to be sent). The main purpose of kill is to allow one process to send a signal to another process. If the signal sent is 0, then kill will not actually send the signal, but will check whether the target process exists and whether the sending process has permission to send signals to the target process.

Difference: The main difference between kill and tgkill is the target to which they send signals. kill can only send signals to the entire process, while tgkill can send signals to precisely specific threads in the process. This makes tgkill more useful in scenarios where signal control is required for specific threads in multi-threaded programs. For example, in a multi-threaded application, you may need to send a signal to a specific thread rather than the entire process. In this case, using tgkill would be more appropriate.

BPF_KSYSCALL

BPF_KSYSCALL does the following:

  1. Defines a BPF program that is attached to the specified kernel system call.
  2. Declare the parameters of this program, which should match the parameters of the system call.
User program part
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "ksyscall.skel.h"

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
	return vfprintf(stderr, format, args);
}

static volatile sig_atomic_t stop;

static void sig_int(int signo)
{
	stop = 1;
}

int main(int argc, char **argv)
{
	struct ksyscall_bpf *skel;
	int err;

	/* Set up libbpf errors and debug info callback */
	libbpf_set_print(libbpf_print_fn);

	/* Open load and verify BPF application */
	skel = ksyscall_bpf__open_and_load();
	if (!skel) {
		fprintf(stderr, "Failed to open BPF skeleton\n");
		return 1;
	}

	/* Attach tracepoint handler */
	err = ksyscall_bpf__attach(skel);
	if (err) {
		fprintf(stderr, "Failed to attach BPF skeleton\n");
		goto cleanup;
	}

	if (signal(SIGINT, sig_int) == SIG_ERR) {
		fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
		goto cleanup;
	}

	printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
	       "to see output of the BPF programs.\n");

	while (!stop) {
		fprintf(stderr, ".");
		sleep(1);
	}

cleanup:
	ksyscall_bpf__destroy(skel);
	return -err;
}
Function Description

Load a BPF program, attach it to the kernel, and wait for an interrupt signal from the user.

Execution effect

clash-linux-6908    [002] d..31 78746.276570: bpf_trace_printk: tgkill syscall called by PID 6907 (clash-linux) for thread id 11399 with pid 6907 and signal 23.
clash-linux-6908    [002] d..31 78747.635256: bpf_trace_printk: tgkill syscall called by PID 6907 (clash-linux) for thread id 11399 with pid 6907 and signal 23.
clash-linux-6908    [002] d..31 78748.020156: bpf_trace_printk: tgkill syscall called by PID 6907 (clash-linux) for thread id 11399 with pid 6907 and signal 23.
clash-linux-6908    [002] d..31 78751.021938: bpf_trace_printk: tgkill syscall called by PID 6907 (clash-linux) for thread id 33841 with pid 6907 and signal 23.
clash-linux-6908    [000] d..31 78754.276769: bpf_trace_printk: tgkill syscall called by PID 6907 (clash-linux) for thread id 33841 with pid 6907 and signal 23.

The difference between ksyscall and kprobe

Both ksyscall and kprobe are mechanisms used for Linux kernel tracking and analysis. The main differences are as follows:

  • ksyscall is for system calls and can be used to track and analyze the entry and exit of system calls. It can capture the entry and exit of each system call and execute the corresponding processing function.
  • kprobe is more versatile and can be implanted with any kernel function address. When the kernel executes the instruction at this address, kprobe will be triggered and then the predefined processing function will be executed. So kprobe can trace system calls as well as ordinary kernel functions.
  • ksyscall is implemented based on kprobes, and kprobe is set internally at the entrance and exit of each system call.
  • ksyscall provides a simple and easy-to-use interface, which can easily track the system call process and parameters. kprobe needs to set the detection point and processing function by itself.
  • kprobe can track system calls as well as ordinary functions, making it more flexible. ksyscall can only trace system calls.
  • The performance overhead of ksyscall may be slightly lower because it only tracks system calls.

Guess you like

Origin blog.csdn.net/qq_32378713/article/details/131748939