Linux内核调试技术kprobe使用与实现Word格式.docx

上传人:b****7 文档编号:22530835 上传时间:2023-02-04 格式:DOCX 页数:85 大小:277.31KB
下载 相关 举报
Linux内核调试技术kprobe使用与实现Word格式.docx_第1页
第1页 / 共85页
Linux内核调试技术kprobe使用与实现Word格式.docx_第2页
第2页 / 共85页
Linux内核调试技术kprobe使用与实现Word格式.docx_第3页
第3页 / 共85页
Linux内核调试技术kprobe使用与实现Word格式.docx_第4页
第4页 / 共85页
Linux内核调试技术kprobe使用与实现Word格式.docx_第5页
第5页 / 共85页
点击查看更多>>
下载资源
资源描述

Linux内核调试技术kprobe使用与实现Word格式.docx

《Linux内核调试技术kprobe使用与实现Word格式.docx》由会员分享,可在线阅读,更多相关《Linux内核调试技术kprobe使用与实现Word格式.docx(85页珍藏版)》请在冰豆网上搜索。

Linux内核调试技术kprobe使用与实现Word格式.docx

6、在kprobes的注册和注销过程中不会使用mutex锁和动态的申请内存;

7、kprobes回调函数的运行期间是关闭内核抢占的,同时也可能在关闭中断的情况下执行,具体要视CPU架构而定。

因此不论在何种情况下,在回调函数中不要调用会放弃CPU的函数(如信号量、mutex锁等);

8、kretprobe通过替换返回地址为预定义的trampoline的地址来实现,因此栈回溯和gcc内嵌函数__builtin_return_address()调用将返回trampoline的地址而不是真正的被探测函数的返回地址;

9、如果一个函数的调用此处和返回次数不相等,则在类似这样的函数上注册kretprobe将可能不会达到预期的效果,例如do_exit()函数会存在问题,而do_execve()函数和do_fork()函数不会;

10、如果当在进入和退出一个函数时,CPU运行在非当前任务所有的栈上,那么往该函数上注册kretprobe可能会导致不可预料的后果,因此,kprobes不支持在X86_64的结构下为__switch_to()函数注册kretprobe,将直接返回-EINVAL。

二、kprobe原理

下面来介绍一下kprobe是如何工作的。

具体流程见下图:

1、当用户注册一个探测点后,kprobe首先备份被探测点的对应指令,然后将原始指令的入口点替换为断点指令,该指令是CPU架构相关的,如i386和x86_64是int3,arm是设置一个未定义指令(目前的x86_64架构支持一种跳转优化方案JumpOptimization,内核需开启CONFIG_OPTPROBES选项,该种方案使用跳转指令来代替断点指令);

2、当CPU流程执行到探测点的断点指令时,就触发了一个trap,在trap处理流程中会保存当前CPU的寄存器信息并调用对应的trap处理函数,该处理函数会设置kprobe的调用状态并调用用户注册的pre_handler回调函数,kprobe会向该函数传递注册的structkprobe结构地址以及保存的CPU寄存器信息;

3、随后kprobe单步执行前面所拷贝的被探测指令,具体执行方式各个架构不尽相同,arm会在异常处理流程中使用模拟函数执行,而x86_64架构则会设置单步调试flag并回到异常触发前的流程中执行;

4、在单步执行完成后,kprobe执行用户注册的post_handler回调函数;

5、最后,执行流程回到被探测指令之后的正常流程继续执行。

三、kprobe使用实例

在分析kprobe的实现之前先来看一下如何利用kprobe对函数进行探测,以便于让我们对kprobre所完成功能有一个比较清晰的认识。

目前,使用kprobe可以通过两种方式,第一种是开发人员自行编写内核模块,向内核注册探测点,探测函数可根据需要自行定制,使用灵活方便;

第二种方式是使用kprobesonftrace,这种方式是kprobe和ftrace结合使用,即可以通过kprobe来优化ftrace来跟踪函数的调用。

下面来分别介绍:

1、编写kprobe探测模块

内核提供了一个structkprobe结构体以及一系列的内核API函数接口,用户可以通过这些接口自行实现探测回调函数并实现structkprobe结构,然后将它注册到内核的kprobes子系统中来达到探测的目的。

同时在内核的samples/kprobes目录下有一个例程kprobe_example.c描述了kprobe模块最简单的编写方式,开发者可以以此为模板编写自己的探测模块。

1.1、kprobe结构体与API介绍

structkprobe结构体定义如下:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

structkprobe{

structhlist_nodehlist;

/*listofkprobesformulti-handlersupport*/

structlist_headlist;

/*countthenumberoftimesthisprobewastemporarilydisarmed*/

unsignedlongnmissed;

/*locationoftheprobepoint*/

kprobe_opcode_t*addr;

/*Allowusertoindicatesymbolnameoftheprobepoint*/

constchar*symbol_name;

/*Offsetintothesymbol*/

unsignedintoffset;

/*Calledbeforeaddrisexecuted.*/

kprobe_pre_handler_tpre_handler;

/*Calledafteraddrisexecuted,unless...*/

kprobe_post_handler_tpost_handler;

/*

*...calledifexecutingaddrcausesafault(eg.pagefault).

*Return1ifithandledfault,otherwisekernelwillseeit.

*/

kprobe_fault_handler_tfault_handler;

*...calledifbreakpointtrapoccursinprobehandler.

*Return1ifithandledbreak,otherwisekernelwillseeit.

kprobe_break_handler_tbreak_handler;

/*Savedopcode(whichhasbeenreplacedwithbreakpoint)*/

kprobe_opcode_topcode;

/*copyoftheoriginalinstruction*/

structarch_specific_insnainsn;

*Indicatesvariousstatusflags.

*Protectedbykprobe_mutexafterthiskprobeisregistered.

u32flags;

};

其中各个字段的含义如下:

structhlist_nodehlist:

被用于kprobe全局hash,索引值为被探测点的地址;

structlist_headlist:

用于链接同一被探测点的不同探测kprobe;

kprobe_opcode_t*addr:

被探测点的地址;

constchar*symbol_name:

被探测函数的名字;

unsignedintoffset:

被探测点在函数内部的偏移,用于探测函数内部的指令,如果该值为0表示函数的入口;

kprobe_pre_handler_tpre_handler:

在被探测点指令执行之前调用的回调函数;

kprobe_post_handler_tpost_handler:

在被探测指令执行之后调用的回调函数;

kprobe_fault_handler_tfault_handler:

在执行pre_handler、post_handler或单步执行被探测指令时出现内存异常则会调用该回调函数;

kprobe_break_handler_tbreak_handler:

在执行某一kprobe过程中触发了断点指令后会调用该函数,用于实现jprobe;

kprobe_opcode_topcode:

保存的被探测点原始指令;

structarch_specific_insnainsn:

被复制的被探测点的原始指令,用于单步执行,架构强相关(可能包含指令模拟函数);

u32flags:

状态标记。

涉及的API函数接口如下:

intregister_kprobe(structkprobe*kp)//向内核注册kprobe探测点

voidunregister_kprobe(structkprobe*kp)//卸载kprobe探测点

intregister_kprobes(structkprobe**kps,intnum)//注册探测函数向量,包含多个探测点

voidunregister_kprobes(structkprobe**kps,intnum)//卸载探测函数向量,包含多个探测点

intdisable_kprobe(structkprobe*kp)//临时暂停指定探测点的探测

intenable_kprobe(structkprobe*kp)//恢复指定探测点的探测

1.2、用例kprobe_example.c分析与演示

该用例函数非常简单,它实现了内核函数do_fork的探测,该函数会在fork系统调用或者内核kernel_thread函数创建进程时被调用,触发也十分的频繁。

下面来分析一下用例代码:

/*Foreachprobeyouneedtoallocateakprobestructure*/

staticstructkprobekp={

.symbol_name="

do_fork"

staticint__initkprobe_init(void)

{

intret;

kp.pre_handler=handler_pre;

kp.post_handler=handler_post;

kp.fault_handler=handler_fault;

ret=register_kprobe(&

kp);

if(ret<

0){

printk(KERN_INFO"

register_kprobefailed,returned%d\n"

ret);

returnret;

}

Plantedkprobeat%p\n"

kp.addr);

return0;

}

staticvoid__exitkprobe_exit(void)

unregister_kprobe(&

kprobeat%punregistered\n"

module_init(kprobe_init)

module_exit(kprobe_exit)

MODULE_LICENSE("

GPL"

);

程序中定义了一个structkprobe结构实例kp并初始化其中的symbol_name字段为“do_fork”,表明它将要探测do_fork函数。

在模块的初始化函数中,注册了

pre_handler、post_handler和fault_handler这3个回调函数分别为handler_pre、handler_post和handler_fault,最后调用register_kprobe注册。

在模块的卸载函数中调用unregister_kprobe函数卸载kp探测点。

staticinthandler_pre(structkprobe*p,structpt_regs*regs)

#ifdefCONFIG_X86

pre_handler:

p->

addr=0x%p,ip=%lx,"

"

flags=0x%lx\n"

addr,regs->

ip,regs->

flags);

#endif

#ifdefCONFIG_PPC

addr=0x%p,nip=0x%lx,"

msr=0x%lx\n"

nip,regs->

msr);

#ifdefCONFIG_MIPS

addr=0x%p,epc=0x%lx,"

status=0x%lx\n"

cp0_epc,regs->

cp0_status);

#ifdefCONFIG_TILEGX

addr=0x%p,pc=0x%lx,"

ex1=0x%lx\n"

pc,regs->

ex1);

/*Adump_stack()herewillgiveastackbacktrace*/

handler_pre回调函数的第一个入参是注册的structkprobe探测实例,第二个参数是保存的触发断点前的寄存器状态,它在do_fork函数被调用之前被调用,该函数仅仅是打印了被探测点的地址,保存的个别寄存器参数。

由于受CPU架构影响,这里对不同的架构进行了宏区分(虽然没有实现arm架构的,但是支持的,可以自行添加);

/*kprobepost_handler:

calledaftertheprobedinstructionisexecuted*/

staticvoidhandler_post(structkprobe*p,structpt_regs*regs,

unsignedlongflags)

post_handler:

addr=0x%p,flags=0x%lx\n"

addr=0x%p,msr=0x%lx\n"

addr=0x%p,status=0x%lx\n"

addr=0x%p,ex1=0x%lx\n"

handler_post回调函数的前两个入参同handler_pre,第三个参数目前尚未使用,全部为0;

该函数在do_fork函数调用之后被调用,这里打印的内容同handler_pre类似。

/*

*fault_handler:

thisiscalledifanexceptionisgeneratedforany

*instructionwithinthepre-orpost-handler,orwhenKprobes

*single-stepstheprobedinstruction.

staticinthandler_fault(structkprobe*p,structpt_regs*regs,inttrapnr)

fault_handler:

addr=0x%p,trap#%dn"

addr,trapnr);

/*Return0becausewedon'

thandlethefault.*/

handler_fault回调函数会在执行handler_pre、handler_post或单步执行do_fork时出现错误时调用,这里第三个参数时具体发生错误的trapnumber,与架构相关,例如i386的pagefault为14。

下面将它编译成模块在我的x86(CentOS3.10)环境下进行演示,首先确保架构和内核已经支持kprobes,开启以下选项(一般都是默认开启的):

Symbol:

KPROBES[=y]

Type:

boolean

Prompt:

Kprobes

Location:

(3)->

Generalsetup

Definedatarch/Kconfig:

37

Dependson:

MODULES[=y]&

&

HAVE_KPROBES[=y]

Selects:

KALLSYMS[=y]

174

Selectedby:

X86[=y]

然后使用以下Makefile单独编译kprobe_example.ko模块:

obj-m:

=kprobe_example.o

CROSS_COMPILE='

'

KDIR:

=/lib/modules/$(shelluname-r)/build

all:

make-C$(KDIR)M=$(PWD)modules

clean:

rm-f*.ko*.o*.mod.o*.mod.c.*.cmd*.symversmodul*

加载到内核中后,随便在终端上敲一个命令,可以看到dmesg中打印如下信息:

<

6>

addr=0xc0439cc0,ip=c0439cc1,flags=0x246

addr=0xc0439cc0,flags=0x246

addr=0xc0439cc0,ip=c0439cc1,flags=

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 文学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1