目录
代码分析
BPF 程序部分
#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";
功能说明
它追踪了两个系统调用:kill 和 tgkill。当这些系统调用被执行时,BPF 程序会打印出相关的信息。
tgkill和kill都是 Linux 的系统调用,主要用于向进程或线程发送信号。但它们的工作方式和使用场景有所不同。
- tgkill 系统调用:tgkill 系统调用接受三个参数:tgid (目标进程组 ID),tid (目标线程 ID),和 sig (要发送的信号)。tgkill 的主要作用是允许一个进程向同一进程组的另一个特定线程发送信号。这个系统调用是为了解决 kill 和 tkill 无法精确地向特定线程发送信号的问题而引入的。
- kill 系统调用:kill 系统调用接受两个参数:pid (目标进程 ID) 和 sig (要发送的信号)。kill 的主要作用是允许一个进程向另一个进程发送信号。如果发送的信号是 0,那么 kill 不会真正地发送信号,而是检查目标进程是否存在,以及发送进程是否有权限向目标进程发送信号。
区别:kill 和 tgkill 的主要区别在于它们发送信号的目标不同。kill 只能向整个进程发送信号,而 tgkill 则可以精确地向进程中的特定线程发送信号。这使得 tgkill 在需要对多线程程序中的特定线程进行信号控制的场景下更为有用。例如,在一个多线程的应用中,你可能需要向一个特定的线程发送一个信号,而不是整个进程,这种情况下,使用 tgkill 就会更加合适。
BPF_KSYSCALL
BPF_KSYSCALL 做了以下工作:
- 定义一个 BPF 程序,该程序附加到指定的内核系统调用上。
- 声明这个程序的参数,这些参数应该匹配系统调用的参数。
用户程序部分
#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;
}
功能说明
加载一个 BPF 程序,将其附加到内核,然后等待用户的中断信号。
执行效果
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.
ksyscall与kprobe 的区别
ksyscall和kprobe都是用于Linux内核追踪和分析的机制,主要区别如下:
- ksyscall是针对系统调用的,可以用来追踪和分析系统调用的进入和退出。它可以捕获每个系统调用的进入和退出,并执行相应的处理函数。
- kprobe更通用,可以植入任意内核函数地址,当内核执行该地址上的指令时,就会触发kprobe,然后执行预定义的处理函数。所以kprobe可以追踪系统调用也可以追踪普通内核函数。
- ksyscall是基于kprobes实现的,内部会在每个系统调用的入口和出口设置kprobe。
- ksyscall提供了简单易用的接口,可以非常方便地追踪系统调用流程和参数。kprobe需要自己设置探测点和处理函数。
- kprobe可以追踪系统调用也可以追踪普通函数,灵活性更强。ksyscall只能追踪系统调用。
- ksyscall的性能开销可能略低一些,因为它只追踪系统调用。