Linux内核调试利器|kprobe 原理与实现

在《Linux 内核调试利器 | kprobe 的使用》一文中,我们介绍过怎么使用 kprobe 来追踪内核函数,而本文将会介绍 kprobe 的原理和实现。

kprobe 原理

kprobe 可以用来跟踪内核函数中某一条指令在运行前和运行后的情况。

我们只需在 kprobe 模块中定义好指令执行前的回调函数 pre_handler() 和执行后的回调函数 post_handler(),那么内核将会在被跟踪的指令执行前调用 pre_handler() 函数,并且在指令执行后调用 post_handler() 函数。如下图所示:

那么,内核是怎样做到在被跟踪指令执行前调用 pre_handler() 函数和指令执行后调用 post_handler() 函数的呢?

如果你读过我们之前写的一篇文章《断点的原理》,那么就比较容易理解 kprobe 的原理了,因为 kprobe 使用了类似于断点的机制来实现的。

如果不了解断点的原理,那么请先看看这篇文章《断点的原理》。

当使用 kprobe 来跟踪内核函数的某条指令时,kprobe 首先会把要追踪的指令保存起来,然后把要追踪的指令替换成 int3 指令。如下图所示:

 

被追踪的指令替换成 int3 指令后,当内核执行到这条指令时,将会触发 do_int3() 异常处理例程。

do_int3() 异常处理例程的执行过程如下:

  1. 首先调用 kprobe 模块的 pre_handler() 回调函数。

  2. 然后将 CPU 设置为单步调试模式。

  3. 接着从异常处理例程中返回,并且执行原来的指令。

我们通过下图来展示 do_int3() 函数的执行过程:

由于设置了单步调试模式,当执行完原来的指令后,将会触发 debug异常(这是 Intel x86 CPU 的一个特性)。

当 CPU 触发 debug异常 后,内核将会执行 debug 异常处理例程 do_debug(),而 do_debug() 异常处理例程将会调用 kprobe 模块的 post_handler() 回调函数。

下图展示了 kprobe 的执行流程:

kprobe 实现

了解了 kprobe 的原理后,现在我们开始分析 kprobe 的代码实现。

由于 kprobe 的细节很多,本文只会对 kprobe 整个大体实现方式进行分析,有些细节需要读者自行阅读源码了解。

1. kprobe 初始化

一个功能的实现,一般都需要先初始化其所使用的资源和环境,kprobe 功能也不例外。

下面我们来看看 kprobe 的初始化过程,kprobe 的初始化由 init_kprobes() 函数实现:

static int __init init_kprobes(void)
{
    int i, err = 0;
    unsigned long offset = 0, size = 0;
    char *modname, namebuf[128];
    const char *symbol_name;
    void *addr;
    struct kprobe_blackpoint *kb;

    // 1) 初始化用于存储 kprobe 模块的哈希表
    for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
      
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值