kprobe是一种重要的内核调试技术, 可以在内核运行过程中, 向内核函数中添加回调函数,打印调试信息或修改内核执行逻辑。
本文介绍的是一种较为常见的使用方式, 即通过编写内核模块来使用kprobe.
以下是一个demo. 我们定义了一个回调函数set_state_pre, 通过调用kprobe的接口register_kprobe,hook到内核函数tcp_set_state
中. 最终的效果就是,在执行内核函数tcp_set_state之前,会执行我们注册的回调函数set_state_pre. 在回调函数中,我们可以做很多事
情,本例中, 我们是获取了内核的参数sk和state, 并打印四元组和state信息。
和普通的内核模块编译一样, 安装好内核kernel-devel包,写好对应的makefile文件,make生成对应ko文件。使用insmod加载对应ko文
件即可以运行
#include <linux/kprobes.h>
#include <net/tcp.h>
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
static int set_state_pre(struct kprobe *p, struct pt_regs *regs)
{
/* 从寄存器获取函数参数 */
struct sock *sk = (struct sock *)regs->di;
int state = (int)regs->si;
struct inet_sock *inet = inet_sk(sk);
u16 dport = ntohs(sk->__sk_common.skc_dport);
char saddr[INET6_ADDRSTRLEN], daddr[INET6_ADDRSTRLEN];
if (sk->sk_family == AF_INET) {
snprintf(saddr, INET_ADDRSTRLEN, "%pI4", &inet->inet_saddr);
snprintf(daddr, INET_ADDRSTRLEN, "%pI4", &inet->inet_daddr);
} else {
snprintf(saddr, INET6_ADDRSTRLEN, "%pI6", &inet->inet_saddr);
snprintf(daddr, INET6_ADDRSTRLEN, "%pI6", &inet->inet_daddr);
}
pr_info("%s %u %s %u sk=%p, pre_state=%u, state=%u\n",
daddr, dport, saddr, ntohs(inet->inet_sport), sk, sk->sk_state, state);
return 0;
}
static struct kprobe set_state_kp = {
.symbol_name = "tcp_set_state",
.pre_handler = set_state_pre,
};
static int __init kprobe_init(void)
{
return register_kprobe(&set_state_kp);
}
static void __exit kprobe_exit(void)
{
unregister_kprobe(&set_state_kp);
}
module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");