前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内核调试技术——kprobe使用与实现(五)

Linux内核调试技术——kprobe使用与实现(五)

作者头像
用户5807183
发布2019-07-15 17:01:45
2.1K0
发布2019-07-15 17:01:45
举报
文章被收录于专栏:Linux知识积累Linux知识积累

Linux内核调试技术——kprobe使用与实现(一)

Linux内核调试技术——kprobe使用与实现(二)

Linux内核调试技术——kprobe使用与实现(三)

Linux内核调试技术——kprobe使用与实现(四)

Linux内核调试技术——kprobe使用与实现(五)-触发kprobe探测和回调

前文中,从register_kprobe函数注册kprobe的流程已经看到,用户指定的被探测函数入口地址处的指令已经被替换成架构相关的BREAKPOINT_INSTRUCTION指令,若是正常的代码流程执行到该指令,将会触发异常,进入架构相关的异常处理函数,kprobe注册的回调函数及被探测函数的单步执行流程均在该流程中执行。由于不同架构实现存在差别,下面来分析x86架构的实现:

x86_64架构下,执行到前文中替换的BREAKPOINT_INSTRUCTION指令后将触发INT3中断,进而调用到do_int3函数。do_int3函数做的事情比较多,但是和kprobe相关的仅代码中列出的这1处,下面来看kprobe_int3_handler函数,这个函数比较长,分段来分析:

本地中断在处理kprobe期间依然被禁止,同时调用user_mode函数确保本处理函数处理的int3中断是在内核态执行流程期间被触发的(因为kprobe不会从用户态触发),这里之所以要做这么一个判断是因为同arm定义特殊未处理指令回调函数不同,这里的do_int3要通用的多,并不是单独为kprobe所设计的。然后获取被探测指令的地址保存到addr中(对于int3中断,其被Intel定义为trap,那么异常发生时EIP寄存器内指向的为异常指令的后一条指令),同时会禁用内核抢占,注释中说明在reenter_kprobe和单步执行时会有选择的重新开启内核抢占。接下来获取当前cpu的kprobe_ctlblk控制结构体和本次要处理的kprobe实例p,然后根据不同的情况进行不同分支的处理。在继续分析前先来看一下x86_64架构kprobe_ctlblk结构体的定义

kprobe_old_flags和kprobe_saved_flags字段用于保存寄存器pt_regs的flag标识。

下面回到函数中根据不同的情况分别分析:

1、p存在且curent_kprobe存在

对于kprobe重入的情况,调用reenter_kprobe函数单独处理:

这个流程对于KPROBE_HIT_SS KPROBE_HIT_SSDONE和KPROBE_HIT_ACTIVE,递增nmissed值并调用setup_singlestep函数进入单步处理流程(该函数最后一个入参此时设置为1,针对reenter的情况会将kprobe_status状态设置为KPROBE_REENTER并调用save_previous_kprobe执行保存当前kprobe的操作)。对于KPROBE_REENTER阶段还是直接报BUG。注意最后函数会返回1,do_int3也会直接返回,表示该中断已被kprobe截取并处理,无需再处理其他分支。

2、p存在但curent_kprobe不存在

这是一般最通用的kprobe执行流程,首先调用set_current_kprobe绑定p为当前正在处理的kprobe:

这里在设置current_kprobe全局变量的同时,还会同时设置kprobe_saved_flags和kprobe_old_flags的flag值,它们用于具体的架构指令相关处理。接下来处理pre_handler回调函数,有注册的话就调用执行,然后调用setup_singlestep启动单步执行。在调试完成后直接返回1。

3、p不存在且被探测地址的指令也不是BREAKPOINT_INSTRUCTION

这种情况表示kprobe可能已经被其他CPU注销了,则让他执行原始指令即可,因此这里设置regs->ip值为addr并重新开启内核抢占返回1。

4、p不存在但curent_kprobe存在

这种情况一般用于实现jprobe,因此会调用curent_kprobe的break_handler回调函数,然后在break_handler返回非0的情况下执行单步执行,最后返回1。具体在jprobe实现中再详细分析。

单步执行

单步执行其实就是执行被探测点的原始指令,涉及的主要函数即前文中分析kprobe触发及处理流程时遗留的singlestep函数(arm)和setup_singlestep函数(x86),它们的实现原理完全不同,其中会涉及许多cpu架构相关的知识,因此会比较晦涩。下面从原理角度逐一分析,并不涉及太多架构相关的细节:

x86_64架构的单步执行函数其主要原理是:当程序执行到某条想要单独执行CPU指令时,在执行之前产生一次CPU异常,此时把异常返回时的CPU的EFLAGS寄存器的TF(调试位)位置为1,把IF(中断屏蔽位)标志位置为0,然后把EIP指向单步执行的指令。当单步指令执行完成后,CPU会自动产生一次调试异常(由于TF被置位)。此时,Kprobes会利用debug异常,执行post_handler()。下面来简单看一下:

首先在前文中已经介绍了,函数的最后一个入参reenter表示是否重入,对于重入的情况那就调用save_previous_kprobe函数保存当前正在运行的kprobe,然后绑定p和current_kprobe并设置kprobe_status为KPROBE_REENTER;对于非重入的情况则设置kprobe_status为KPROBE_HIT_SS。

接下来考试准备单步执行,首先设置regs->flags中的TF位并清空IF位,同时把int3异常返回的指令寄存器地址改为前面保存的被探测指令,当int3异常返回时这些设置就会生效,即立即执行保存的原始指令(注意这里是在触发int3之前原来的上下文中执行,因此直接执行原始指令即可,无需特别的模拟操作)。该函数返回后do_int3函数立即返回,由于cpu的标识寄存器被设置,在单步执行完被探测指令后立即触发debug异常,进入debug异常处理函数do_debug。

首先调用resume_execution函数将debug异常返回的下一条指令设置为被探测之后的指令,这样异常返回后程序的流程就会按正常的流程继续执行;然后恢复kprobe执行前保存的flags标识;接下来如果kprobe不是重入的并且设置了post_handler回调函数,就设置kprobe_status状态为KPROBE_HIT_SSDONE并调用post_handler函数;如果是重入的kprobe则调用restore_previous_kprobe函数恢复之前保存的kprobe。最后调用reset_current_kprobe函数解除本kprobe和current_kprobe的绑定,如果本kprobe由单步执行触发,则说明do_debug异常处理还有其他流程带处理,返回0,否则返回1。

至此,kprobe的一般处理流程就分析完了,最后分析一下剩下的最后一个回调函数fault_handler。

出错回调

出错会调函数fault_handler会在执行pre_handler、single_step和post_handler期间触发内存异常时被调用,对应的调用函数为kprobe_fault_handler,它同样时架构相关的,下面来看一下(x86_64):

1、do_page_fault->__do_page_fault->kprobes_fault

可见在触发缺页异常之后,若当前正在处理kprobe流程期间,会调用kprobe_fault_handler进行处理。

2、do_general_protection->notify_die->kprobe_exceptions_notify

前文中init_kprobes初始化时会注册die内核通知链kprobe_exceptions_nb,它的回调函数为kprobe_exceptions_notify,在内核触发DIE_GPF类型的notify_die时,该函数会调用kprobe_fault_handler进行处理。下面来简单看一下x86_64架构的kprobe_fault_handler函数实现:

kprobe_fault_handler函数会找到当前正在处理的kprobe,然后根据处理状态的不同本别处理。首先若是单步执行或是重入的情况,则说明单步执行是发生了内存错误,则复位当前正在处理的kprobe,同时设置PC指针为异常触发指令地址,就好像它是一个普通的缺页异常,由内核后续的处理流程处理;若是执行pre_handler和post_handler回调函数期间出错,则递增kprobe的nmiss字段值,然后调用fault_handler回调函数执行用户指定的操作,fault_handler函数返回0,即没有修复内存异常,则会直接调用fixup_exception函数尝试修复。。

以上fault_handler回调函数分析完毕。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux知识积累 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Linux内核调试技术——kprobe使用与实现(一)
  • Linux内核调试技术——kprobe使用与实现(二)
  • Linux内核调试技术——kprobe使用与实现(三)
  • Linux内核调试技术——kprobe使用与实现(四)
  • 出错回调
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档