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

Linux内核调试技术——jprobe使用与实现(六)

作者头像
用户5807183
发布2019-07-15 10:44:23
1.6K0
发布2019-07-15 10:44:23
举报
文章被收录于专栏:Linux知识积累Linux知识积累

本文介绍kprobes中的第二种探测技术jprobe,它基于kprobe实现,不能在函数的任意位置插入探测点,只能在函数的入口处探测,一般用于监测函数的入参值。

1、jprobe使用实例

使用jprobe探测函数的入参值,需要编写内核模块。同kprobe一样,内核同样提供了jprobe的实例程序jprobe_example.c(位于sample/kprobes目录),该程序实现了探测do_fork函数入参的功能,用户可以以它为模板来探测其他函数(当然不是说什么函数都能探测的,限制同kprobe一样,另外需要注意的是一个被探测函数只能注册一个jprobe)。在分析jprobe_example.c之前先熟悉一下jprobe的基本结构与API接口。

1.1、jprobe结构体与API介绍

struct jprobe结构体定义如下:

该结构非常的简单,仅包含了一个kprobe结构(因为它是基于kprobe实现的)和一个entry指针,它保存的是探测点执行回调函数的地址,当触发调用被探测函数时,保存到该指针的地址会作为目标地址跳转执行(probe handling code to jump to),因此用户指定的探测函数得以执行。

相关的API如下:

int register_jprobe(struct jprobe *jp)

//向内核注册jprobe探测点

void unregister_jprobe(struct jprobe*jp)

//卸载jprobe探测点

int register_jprobes(struct jprobe **jps,int num)

//注册探测函数向量,包含多个不同探测点

void unregister_jprobes(struct jprobe **jps,int num)

//卸载探测函数向量,包含多个不同探测点

int disable_jprobe(struct jprobe *jp)

//临时暂停指定探测点的探测

int enable_jprobe(struct jprobe *jp)

//恢复指定探测点的探测

1.2、示例jprobe_example分析与演示

同kprobe_example.c一样,该示例程序仍以do_fork作为被探测函数进行探测。当创建进程时,探测函数会调用它打印出do_fork函数的入参值。下面详细分析:

程序定义了一个struct jprobe实例my_jprobe,指定被探测函数的名字是do_fork(可以修改它以达到探测其他函数的目的),然后探测回调函数为jdo_fork。在模块的初始化函数中,调用register_jprobe函数向kprobe子系统注册my_jprobe,这样jprobe探测默认就启用了,最后在exit函数中调用unregister_jprobe函数卸载。

jdo_fork函数也仅仅打印出了在调用do_fork函数时传入的clone_flags、stack_start和stack_size这三个入参值,整个实现非常简单直观,但是有两点需要注意:

1)探测回调函数的入参必须同被探测函数的一致,否则无法达到探测函数入参的目的,例如此处的jdo_fork函数入参unsigned long clone_flags、unsigned long stack_start、unsigned longstack_size、int __user *parent_tidptr和int __user *child_tidptr同do_fork函数是完全一致的(注意返回值固定为long类型)。

2)在回调函数执行完毕以后,必须调用jprobe_return函数(注释中也有强调),否则执行流程就回不到正常的执行流程中了,这一点后文会详细分析。

下面在x86_64环境下演示该程序的实际效果

在加载jprobe_example.ko模块以后,在终端随便敲几个命令触发进程创建,内核打印出以上message,可以看到do_fork的入参就被非常容易的获取到了,其他函数的探测也类似,不再详细描述。

2、jprobe实现分析

jpeobe的实现基于kprobe,在其基础之上分析它的实现,述主要包括jprobe注册流程和触发探测流程,涉及kprobe的部分不再详细描。

2.1、jprobe实现原理

利用kprobe,jprobe是一种特殊形式的kprobe,它有自己的pre_handler和break_handler回调函数,其中pre_handler回调函数负责保存原始调用上下文并为调用用户指定的探测函数jprobe->entry准备环境,然后跳转到jprobe->entry执行(被探测函数的入参信息在此得到输出),接着再次触发kprobe流程,在break_handler函数中恢复原始上下文,最后返回正常执行流程。

2.2、注册一个jprobe实例

jprobe探测模块调用register_jprobe函数向内核注册一个jprobe实例,代码路径kernel/kprobes.c,其主要流程如下图:

可见jprobe的注册流程非常的简单,它的本质就是注册一个kprobe,利用kprobe机制实现探测,只是探测回调函数并非用户自己定义,使用jprobe私有的而已。在注册完成后,jprobe(kprobe)机制启动,当函数调用流程执行到被探测函数时就会触发jprobe(kprobe)探测。

最后需要注意的是,jprobe是不能在同一个被探测点注册多个的,在kprobe的注册流程register_kprobe->register_aggr_kprobe->add_new_kprobe中会有判断

2.3、触发jprobe探测

基于kprobe机制,在执行到被探测函数后,会触发CPU异常,按照kprobe的执行流程,由kprobe_handler函数调用到pre_handler回调函数,即setjmp_pre_handler。该函数架构相关,它根据架构的不同进行一些栈或者寄存器相关的操作,保存现场以备调用结束后恢复,随后跳转到用户定的jprobe->entry处执行,在打印出用户需要的信息后,返回原有正常的流程继续执行。主要流程如下图:

x86平台下代码如下:

函数首先同样是保存现场,然后关闭中断并设置IP寄存器的值为jp->entry,最后返回1,这样在kprobe_int3_handler函数会跳过single_step。

于是在kprobe调用流程结束后跳转到用户的探测函数执行。在来看jprobe_return函数的实现:

这里使用int3指令再次触发CPU3异常,并且异常出的地址已经不再是BREAKPOINT_INSTRUCTION了,所以会进入到kprobe_int3_handler的以下流程执行:

同样是调用kprobe的break_handler回调函数执行,也即是longjmp_break_handler函数。

longjmp_break_handler函数恢复代码的原有上下文,打开内核抢占,最后交回给kprobe继续执行后面的single_step和恢复流程。不过值的注意的是第一条判断语句,由于本次int3异常是在jprobe_return函数中触发的,因此longjmp_break_handler函数的struct pt_regs *regs入参值是在调用jprobe_return函数环境上下文中的寄存器值,因此addr一定是在jprobe_return函数的地址范围内,所以以此判断本次调用的有效性,防止误入。

3、总结

jprobe探测技术基于kprobe实现,是kprobes三种探测技术中的第二种,内核开发人员可以用它来探测内核函数的调用以及调用时的入参值,使用非常方便。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2、jprobe实现分析
    • 2.2、注册一个jprobe实例
    • 3、总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档