linux内核模块使用tracepoint进行探测

linux_kernel_module_use_tracepoint_to_probe

代码说明:

该代码提供了一个可用的linux内核模块使用tracepoint来进行探测示例,并提供了十分详细的代码注释。示例使用的是sched_switch这个tracepoint,测试于linux-5.15.77环境。

代码核心原理:

找到内核中用于管理对应tracepoint的struct tracepoint结构体,然后在struct tracepoint对应成员填入自定义回调函数地址。

修改代码用于其他tracepoint方法:

  1. 生成一个管理想要使用的tracepoint的struct interest_tracepoint结构体,可用INIT_INTEREST_TRACEPOINT宏完成。
  2. 生成一个用于for_each_kernel_tracepoint回调来比对tracepoint表条目以找到指定tracepoint对应struct tracepoint地址的回调函数,可用TRACEPOINT_FIND宏完成。
  3. 自定义用于tracepoint的回调函数。
  4. 修改tracepoint_init函数中的几句代码。
  5. 在tracepoint_exit中完成回调函数注销工作。

感谢与参考:

https://gist.github.com/HugoGuiroux/0894091275169750d22f

代码:

linux_kernel_module_use_tracepoint_to_probe.c

#include <linux/module.h>
#include <linux/tracepoint.h>

/* ---------------------------------------------修改用于其他tracepoint时不用动的部分--------------------------------------------------- */

/* 用于管理感兴趣的tracepoint,即我要使用的tracepoint */
struct interest_tracepoint
{
    void *callback; /* 指向tracepoint要执行的回调函数 */
    /* 对于每个tracepoint,内核都会使用一个struct tracepoint结构体来管理,这个指针就指向这个结构体 */
    struct tracepoint *ptr;
    char is_registered; /* 记录我想使用的tracepoint对应回调函数是否已经注册, 0表示否,1表示是 */
};

/* 用于生成一个struct interest_tracepoint结构体,并初始化,参数:
tracepoint_name:要使用的tracepoint名称,比如sched_switch*/
#define INIT_INTEREST_TRACEPOINT(tracepoint_name) \
    static struct interest_tracepoint tracepoint_name##_tracepoint = {.callback = NULL, .ptr = NULL, .is_registered = 0};

/* 该宏用于生成for_each_kernel_tracepoint的回调函数,前者用于遍历整个内核的tracepoint表。
for_each_kernel_tracepoint每找到一个表项就会调用该回调函数。宏参数:
tracepoint_name:你想要找到的tracepoint的名称,比如sched_switch。
生成的回调函数使用的参数由for_each_kernel_tracepoint对回调函数的规定所决定。生成后函数的参数:
tp:找到的struct tracepoint 的指针
priv: 注册回调函数时传入的自定义数据指针,我注册时会传入用于管理我要使用的tracepoint的
struct interest_tracepoint指针*/
#define TRACEPOINT_FIND(tracepoint_name)                                             \
    static void tracepoint_name##_tracepoint_find(struct tracepoint *tp, void *priv) \
    {                                                                                \
        if (!strcmp(#tracepoint_name, tp->name))                                     \
        {                                                                            \
            ((struct interest_tracepoint *)priv)->ptr = tp;                          \
            return;                                                                  \
        }                                                                            \
    }

/* 用于注销一个tracepoint的回调函数 */
static void clear_tracepoint(struct interest_tracepoint *interest)
{

    /* 判断我们的回调函数是否已经注册 */
    if (interest->is_registered)
    { /* 进来,说明我们的回调函数是注册了的 */
        /* 注销我们在对应tracepoint上注册的回调函数 */
        tracepoint_probe_unregister(interest->ptr, interest->callback, NULL);
    }
}

/* --------------------------------------修改以下部分用于其他tracepoint------------------------------------------------------------------- */

/* 生成并初始化struct interest_tracepoint sched_switch */
INIT_INTEREST_TRACEPOINT(sched_switch)

/* 生成sched_switch_tracepoint_find函数 */
TRACEPOINT_FIND(sched_switch)

/* tracepoint: sched_switch触发时的回调函数,由我们自行挂载到该tracepoint上。
每个tracepoint的回调函数可接受的参数由include/trace/events/xxx.h 中的TRACE_EVENT宏定义确定。
对于tracepoint: sched_switch,参数定义在include/trace/events/sched.h 中的
TRACE_EVENT(sched_switch 中,为TP_PROTO(bool preempt, struct task_struct *prev, struct task_struct *next)。
因此本回调函数除了第一个参数外,其余参数就是由该TP_PROTO宏中的内容决定,无法更改参数内容与顺序。
void *data参数是在注册该回调函数时提供的额外数据,可以指向任何数据结构,取决于具体需求。
例如,如果在注册该回调函数时指定void *data指向某个特定结构体,那么每当该回调函数被触发时,
就可以通过data参数访问该结构体。这提供了一种灵活的方式来传递额外信息给回调函数,
避免了使用全局变量或其他潜在的问题引入方法。 */
static void sched_switch_tracepoint_callback(void *data, bool preempt, struct task_struct *prev, struct task_struct *next)
{
    /* 自定义代码 */
}

static int __init tracepoint_init(void)
{

    /* 在我们自己的struct interest_tracepoint中记录管理的tracepoint要调用的回调函数 */
    sched_switch_tracepoint.callback = sched_switch_tracepoint_callback;

    /* tracepoint并不是直接导出供外部模块直接引用的(http://lkml.iu.edu/hypermail/linux/kernel/1504.3/01878.html)),
    内核模块不能直接通过外部符号来访问tracepoint。所以需要用for_each_kernel_tracepoint遍历tracepoint表的方式来找到指定tracepoint。
    这需要我们注册一个回调函数用于比对找到的tracepoint条目是不是我们想要的tracepoint*/
    for_each_kernel_tracepoint(sched_switch_tracepoint_find, &sched_switch_tracepoint);

    /* 判断有没有找到想要使用的tracepoint对应的struct tracepoint指针 */
    if (!sched_switch_tracepoint.ptr)
    {
        pr_info("sched_switch's struct tracepoint not found\n");
        return 0;
    }

    /* 在内核管理sched_switch的struct tracepoint中填上我们的回调函数,这就完成了回调函数的注册 */
    tracepoint_probe_register(sched_switch_tracepoint.ptr, sched_switch_tracepoint.callback, NULL);

    sched_switch_tracepoint.is_registered = 1; /* 记录我们的回调函数已经注册 */

    return 0;
}

static void __exit tracepoint_exit(void)
{
    clear_tracepoint(&sched_switch_tracepoint);
}

module_init(tracepoint_init);
module_exit(tracepoint_exit);
MODULE_LICENSE("GPL");

Makefiel

obj-m += linux_kernel_module_use_tracepoint_to_probe.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值