此处转载:

一、Kprobe简单介绍

kprobe是一个动态地收集调试和性能信息的工具,它从Dprobe项目派生而来,是一种非破坏性工具,用户用它差点儿能够跟踪不论什么函数或被运行的指令以及一些异步事件(如timer)。

它的基本工作机制是:用户指定一个探測点。并把一个用户定义的处理函数关联到该探測点。当内核运行到该探測点时,对应的关联函数被运行。然后继续运行正常的代码路径。

kprobe实现了三种类型的探測点: kprobes, jprobes和kretprobes (也叫返回探測点)。

kprobes是能够被插入到内核的不论什么指令位置的探測点,jprobes则仅仅能被插入到一个内核函数的入口,而kretprobes则是在指定的内核函数返回时才被运行。

一般。使用kprobe的程序实现作一个内核模块。模块的初始化函数来负责安装探測点。退出函数卸载那些被安装的探測点。kprobe提供了接口函数(APIs)来安装或卸载探測点。

眼下kprobe支持例如以下架构:i386、x86_64、ppc64、ia64(不支持对slot1指令的探測)、sparc64 (返回探測还没有实现)。

二、Kprobe实现原理

当安装一个kprobes探測点时。kprobe首先备份被探測的指令,然后使用断点指令(即在i386和x86_64的int3指令)来代替被探測指令的头一个或几个字节。当CPU运行到探測点时,将因运行断点指令而运行trap操作,那将导致保存CPU的寄存器,调用对应的trap处理函数。而trap处理函数将调用对应的notifier_call_chain(内核中一种异步工作机制)中注冊的全部notifier函数。kprobe正是通过向trap对应的notifier_call_chain注冊关联到探測点的处理函数来实现探測处理的。

当kprobe注冊的notifier被运行时,它首先运行关联到探測点的pre_handler函数,并把对应的kprobe
struct和保存的寄存器作为该函数的參数,接着,kprobe单步运行被探測指令的备份。最后,kprobe运行post_handler。等全部这些运行完成后。紧跟在被探測指令后的指令流将被正常运行。

kretprobe也使用了kprobes来实现,当用户调用register_kretprobe()时,kprobe在被探測函数的入口建立了一个探測点。当运行到探測点时,kprobe保存了被探測函数的返回地址并代替返回地址为一个trampoline的地址,kprobe在初始化时定义了该trampoline而且为该trampoline注冊了一个kprobe,当被探測函数运行它的返回指令时。控制传递到该trampoline,因此kprobe已经注冊的相应于trampoline的处理函数将被运行。而该处理函数会调用用户关联到该kretprobe上的处理函数。处理完成后,设置指令寄存器指向已经备份的函数返回地址。因而原来的函数返回被正常运行。



被探測函数的返回地址保存在类型为kretprobe_instance的变量中。结构kretprobe的maxactive字段指定了被探測函数能够被同一时候探測的实例数,函数register_kretprobe()将预分配指定数量的kretprobe_instance。假设被探測函数是非递归的而且调用时已经保持了自旋锁(spinlock),那么maxactive为1就足够了; 假设被探測函数是非递归的且执行时是抢占失效的,那么maxactive为NR_CPUS就能够了;假设maxactive被设置为小于等于0, 它被设置到缺省值(假设抢占使能,
即配置了 CONFIG_PREEMPT,缺省值为10和2*NR_CPUS中的最大值,否则缺省值为NR_CPUS)。



假设maxactive被设置的太小了,一些探測点的运行可能被丢失,可是不影响系统的正常运行,在结构kretprobe中nmissed字段将记录被丢失的探測点运行数,它在返回探測点被注冊时设置为0,每次当运行探測函数而没有kretprobe_instance可用时,它就加1。

三、Kprobe注冊函数

kprobe为每一类型的探測点提供了注冊和卸载函数。

1.register_kprobe



它用于注冊一个kprobes类型的探測点,其函数原型为:

int register_kprobe(struct kprobe *kp);

为了使用该函数。用户须要在源文件里包括头文件linux/kprobes.h。



该函数的參数是struct kprobe类型的指针。struct kprobe包括了字段addr、pre_handler、post_handler和fault_handler,addr指定探測点的位置,pre_handler指定运行到探測点时运行的处理函数,post_handler指定运行完探測点后运行的处理函数。fault_handler指定错误处理函数,当在运行pre_handler、post_handler以及被探測函数期间错误发生时。它会被调用。在调用该注冊函数前。用户必须先设置好struct kprobe的这些字段,用户能够指定不论什么处理函数为NULL。



该注冊函数会在kp->addr地址处注冊一个kprobes类型的探測点,当运行到该探測点时,将调用函数kp->pre_handler。运行完被探測函数后,将调用kp->post_handler。假设在运行kp->pre_handler或kp->post_handler时或在单步跟踪被探測函数期间错误发生,将调用kp->fault_handler。

该函数成功时返回0,否则返回负的错误码。



探測点处理函数pre_handler的原型例如以下:

int pre_handler(struct kprobe *p, struct pt_regs *regs);

用户必须依照该原型參数格式定义自己的pre_handler,当然函数名取决于用户自己。

參数p就是指向该处理函数关联到的kprobes探測点的指针,能够在该函数内部引用该结构的不论什么字段。就如同在使用调用register_kprobe时传递的那个參数。參数regs指向执行到探測点时保存的寄存器内容。kprobe负责在调用pre_handler时传递这些參数,用户不必关心,仅仅是要知道在该函数内你能訪问这些内容。



一般地,它应当始终返回0。除非用户知道自己在做什么。

探測点处理函数post_handler的原型例如以下:

void post_handler(struct kprobe *p, struct pt_regs *regs,
unsigned long flags);

前两个參数与pre_handler同样。最后一个參数flags总是0。



错误处理函数fault_handler的原刑例如以下:

int fault_handler(struct kprobe *p, struct pt_regs *regs, int trapnr);

前两个參数与pre_handler同样,第三个參数trapnr是与错误处理相关的架构依赖的trap号(比如,对于i386,通常的保护错误是13。而页失效错误是14)。



假设成功地处理了异常。它应当返回1。

2 . register_kretprobe



该函数用于注冊类型为kretprobes的探測点。它的原型例如以下:

int register_kretprobe(struct kretprobe *rp);

为了使用该函数,用户须要在源文件里包括头文件linux/kprobes.h。



该注冊函数的參数为struct kretprobe类型的指针,用户在调用该函数前必须定义一个struct kretprobe的变量并设置它的kp.addr、handler以及maxactive字段。kp.addr指定探測点的位置,handler指定探測点的处理函数。maxactive指定能够同一时候执行的最大处理函数实例数,它应当被恰当设置。否则可能丢失探測点的某些执行。



该注冊函数在地址rp->kp.addr注冊一个kretprobe类型的探測点。当被探測函数返回时。rp->handler会被调用。



假设成功,它返回0,否则返回负的错误码。

kretprobe处理函数的原型例如以下:

int kretprobe_handler(struct kretprobe_instance *ri, struct pt_regs *regs);

參数regs指向保存的寄存器。ri指向类型为struct kretprobe_instance的变量,该结构的ret_addr字段表示返回地址,rp指向对应的kretprobe_instance变量,task字段指向对应的task_struct。结构struct kretprobe_instance是注冊函数register_kretprobe依据用户指定的maxactive值来分配的。kprobe负责在调用kretprobe处理函数时传递对应的kretprobe_instance。

3. 相应于每个注冊函数。有相应的卸载函数。

void unregister_kprobe(struct kprobe *kp);
void unregister_jprobe(struct jprobe *jp);
void unregister_kretprobe(struct kretprobe *rp);

上面是相应与三种探測点类型的卸载函数。当使用探測点的模块卸载或须要卸载已经注冊的探測点时,须要使用相应的卸载函数来卸载已经注冊的探測点。kp,jp和rp分别为指向结构struct kprobe,struct jprobe和struct kretprobe的指针。它们应当指向调用相应的注冊函数时使用的那个结构。也就说注冊和卸载必须针对相同的探測点。否则会导致系统崩溃。这些卸载函数能够在注冊后的不论什么时刻调用。

四 、Kprobe限制

kprobe同意在同一地址注冊多个kprobes,可是不能同一时候在该地址上有多个jprobes。

通常,用户能够在内核的不论什么位置注冊探測点,特别是能够对中断处理函数注冊探測点,可是也有一些例外。

假设用户尝试在实现kprobe的代码(包含kernel/kprobes.c和arch/*/kernel/kprobes.c以及do_page_fault和notifier_call_chain)中注冊探測点。register_*probe将返回-EINVAL.



假设为一个内联(inline)函数注冊探測点,kprobe无法保证对该函数的全部实例都注冊探測点,由于gcc可能隐式地内联一个函数。因此,要记住,用户可能看不到预期的探測点的运行。

一个探測点处理函数可以改动被探測函数的上下文,如改动内核数据结构,寄存器等。因此,kprobe可以用来安装bug解决代码或注入一些错误或測试代码。

假设一个探測处理函数调用了还有一个探測点,该探測点的处理函数不将执行,可是它的nmissed数将加1。

多个探測点处理函数或同一处理函数的多个实例可以在不同的CPU上同一时候执行。



除了注冊和卸载,kprobe不会使用mutexe或分配内存。

探測点处理函数在执行时是失效抢占的。依赖于特定的架构,探測点处理函数执行时也可能是中断失效的。因此,对于不论什么探測点处理函数,不要使用导致睡眠或进程调度的不论什么内核函数(如尝试获得semaphore)。

kretprobe是通过代替返回地址为提前定义的trampoline的地址来实现的。因此栈回溯和gcc内嵌函数__builtin_return_address()调用将返回trampoline的地址而不是真正的被探測函数的返回地址。

假设一个函数的调用次数与它的返回次数不同样,那么在该函数上注冊的kretprobe探測点可能产生无法预料的结果(do_exit()就是一个典型的样例,但do_execve() 和 do_fork()没有问题)。

当进入或退出一个函数时,假设CPU正执行在一个非当前任务全部的栈上,那么该函数的kretprobe探測可能产生无法预料的结果,因此kprobe并不支持在x86_64上对__switch_to()的返回探測。假设用户对它注冊探測点,注冊函数将返回-EINVAL。

五、怎样在内核中引入Kprobe

kprobe已经被包括在2.6内核中。可是仅仅有最新的内核才提供了上面描写叙述的所有功能,因此假设读者想实验本文附带的内核模块,须要最新的内核,作者在2.6.18内核上測试的这些代码。内核缺省时并没有使能kprobe,因此用户需使能它。



为了使能kprobe。用户必须在编译内核时设置CONFIG_KPROBES,即选择在“Instrumentation Support“中的“Kprobes”项。假设用户希望动态载入和卸载使用kprobe的模块,还必须确保“Loadable module support” (CONFIG_MODULES)和“Module unloading” (CONFIG_MODULE_UNLOAD)设置为y。假设用户还想使用kallsyms_lookup_name()来得到被探測函数的地址,也要确保CONFIG_KALLSYMS设置为y,当然设置CONFIG_KALLSYMS_ALL为y将更好。

六、Kprobe使用实例

演示样例见内核原码: samples/kprobes/

Linux下 kprobe工具的使用的更多相关文章

  1. 哈工大 计算机系统 实验一 Linux下C工具应用

    所有实验文件可见github 计算机系统实验整理 实验报告 实 验(一) 题 目 Linux下C工具应用 专 业 计算机学院 学 号 班 级 学 生 指 导 教 师 实 验 地 点 实 验 日 期 计 ...

  2. Linux下sysstat工具学习

    Linux下,我们多用ssh链接服务器远程操控.对于系统的监控必不可少,sysstat很不错的监控工具包. sysstat官网:http://sebastien.godard.pagesperso-o ...

  3. linux如何ARP嗅探 Linux下嗅探工具Dsniff安装记录

      先来下载依赖包 和一些必须要用到的工具 我这里用的是 dsniff-2.3 的版本 wget http://www.monkey.org/~dugsong/dsniff/dsniff-2.3.ta ...

  4. Linux下同步工具inotify+rsync使用详解

    1. rsync 1.1 什么是rsync rsync是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件.它使用所谓的“Rsync演算法”来使本地和远程两个主机之间的文件达到同步,这 ...

  5. linux下debug工具

    在linux下开发难免会遇到bug,但是由于没有图形IDE,导致debug也变得困难,其实只要掌握一些常用的debug工具,一些错误就能很快解决,本文就介绍一些常用的工具用以调试: log 输出log ...

  6. Linux下应急工具

    Linux下的应急工具 在Linux下,应急的查看点无非那个几个,一是看表现(宕机.高CPU.高内存.高IO.高网络通信),二看连接.三看进程.四看日志.五看文件(Linux一切皆文件),再者结合起来 ...

  7. (转)Linux下同步工具inotify+rsync使用详解

    原文:https://segmentfault.com/a/1190000002427568 1. rsync 1.1 什么是rsync rsync是一个远程数据同步工具,可通过LAN/WAN快速同步 ...

  8. Linux下SSH工具 PAC Manager的安装

    PAC Manager, Linux下类似SecureCRT Xshell的SSH工具,该工具功能上相当的不错,完全可以代替SecureCRT Xshell的功能. PAC (Perl Auto Co ...

  9. Linux下Vim工具常用命令

    原文地址: http://www.cnblogs.com/lizhenghn/p/3675011.html 在linux下做开发,甚至是只做管理维护工作,也少不了Vim的使用.作为一个新手,我也是刚刚 ...

随机推荐

  1. 27.Node.js模块系统

    转自:http://www.runoob.com/nodejs/nodejs-module-system.html 为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统. 模 ...

  2. 1.3 Quick Start中 Step 4: Send some messages官网剖析(博主推荐)

    不多说,直接上干货! 一切来源于官网 http://kafka.apache.org/documentation/ Step 4: Send some messages Step : 发送消息 Kaf ...

  3. linux创建新用户并给予root权限

    root比windows的系统管理员的能力更大,足以把整个系统的大部分文件删掉,导致系统完全毁坏,不能再次使用.所以,用root进行不当的操作是相当危险的,轻微的可以死机,严重的甚至不能开机.所以,在 ...

  4. Maven搭建hadoop环境报Missing artifact jdk.tools:jdk.tools:jar:1.7(5种办法,2种正解)

    刚刚写的那一篇,是网上比较主流的解决办法. 鉴于实际情况,有伙伴的机器上没有遇到这个问题,我们再探究原因,最终还有4种情况需要说明. 先说,另外一种"正解". <depend ...

  5. C#创建子线程,子线程使用委托更新控件

    一.背景 由于在窗体程序中通过点击一个button按键后需要更新TreeView控件的内容,由于等待时间比较长,主程序无法一起在那边等待,需要去处理其它的事情,所以就需要创建新的子线程来处理.因为主线 ...

  6. [置顶] MVC三层架构在各框架中的特征

    1.从结构上分析jsp+servlet图解原理: 在基于mvc设计模式下的最原始的jsp+Servlet框架,在某种程度上是不能够达到mvc最直观的体现.当客户端发送请求到服务器时,服务器会将从客户端 ...

  7. Vue 学习记录<1>

    1.环境搭建:(前提node.js搭建) # 全局安装 vue-cli $ npm install --global vue-cli   # 创建一个基于 webpack 模板的新项目 $ vue i ...

  8. HDU 5389 Zero Escape (MUT#8 dp优化)

    [题目链接]:pid=5389">click here~~ [题目大意]: 题意: 给出n个人的id,有两个门,每一个门有一个标号,我们记作a和b,如今我们要将n个人分成两组,进入两个 ...

  9. Python 极简教程(六)运算符

    运算符,我们日常生活中使用的加减乘除,都是运算符的一种.当然这种一般我们称为算术运算符,用于处理数字运算的. 但是在计算机语言中,还有很多的运算符.用于处理不用的情况. 主要有以下几类: 算术运算符 ...

  10. PHP模拟链表操作

    PHP模拟链表操作 一.总结 1.类成员用的是-> 2.对象节点相连的话,因为是对象,所以不用取地址符号 3.数组传递参数的时候传引用的方法 ,& 二.PHP模拟链表操作 代码一: /* ...