ftrace kprobe调试

来源:互联网 发布:金庸新修 知乎 编辑:程序博客网 时间:2024/05/17 23:26

kprobe

内核调试一般可以通过printk打印,这种方式需要重新编译内核,或者模块,如果编译内核还要重启设备才能生效。kprobe是一种内核调试方式,可以在内核执行代码位置中断,并执行我们插入的代码。同时内核提供了另一套接口 kprobe-trace,这个接口的优点是通过proc接口操作,不需要写代码,适合做一些临时的debug打印。

Mount debug分区

命令:mount -t debugfs nodev /sys/kernel/debug

Nodev可以是任意字符串

后面的目录也可以随意指定

重要文件接口

配置接口:/sys/kernel/debug/tracing/kprobe_events

读取信息接口:/sys/kernel/debug/tracing/trace

读取信息接口:/sys/kernel/debug/tracing/trace_pipe

开启某个kprobe接口:/sys/kernel/debug/tracing/events/kprobes/<EVENT>/enabled

过滤接口/sys/kernel/debug/tracing/events/kprobes/<EVENT>/filter

kprobe_events

添加删除kprobe的接口,直接echo 'xxxx' >>kprobe_events就可以。内核文档比较详细,但是大多数参数没什么用。如下:

  p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]: Set a probe  r[:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS]: Set a return probe  -:[GRP/]EVENT: Clear a probe GRP: Group name. If omitted, use "kprobes" for it. EVENT: Event name. If omitted, the event name is generated based on SYM+offs or MEMADDR. MOD: Module name which has given SYM. SYM[+offs]: Symbol+offset where the probe is inserted. MEMADDR: Address where the probe is inserted. FETCHARGS: Arguments. Each probe can have up to 128 args. %REG: Fetch register REG  @ADDR: Fetch memory at ADDR (ADDR should be in kernel)  @SYM[+|-offs]: Fetch memory at SYM +|- offs (SYM should be a data symbol) $stackN: Fetch Nth entry of stack (N >= 0)  $stack: Fetch stack address.  $retval: Fetch return value.(*)  +|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(**) NAME=FETCHARG : Set NAME as the argument name of FETCHARG.  FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types (u8/u16/u32/u64/s8/s16/s32/s64), "string" and bitfield  are supported.  (*) only for return probe.  (**) this is useful for fetching a field of data structures.


基本使用方式:

echo p do_fork>> kprobe_events

echo r do_fork>> kprobe_events

建议echo用‘>>’重定向输出,否则会把前面的冲掉。通过上面两个命令,下发了两个kprobe,“p do_fork”是当有进程调用do_fork函数的时候,会有打印信息。“r do_fork”是有进程调用do_fork完成之后会有打印信息。

>kprobe_events 命令可以将上面两个kprobe删除。

开启某个kprobe

创建kprobe的时候,会在events/kprobes/下为每个probe创建一个目录,目录下有这个kprobe相关的接口。下面是开启kprobe的方式。

echo 1 >events/kprobes/p_do_fork_0/enable 开启“p do_fork”kprobe

echo 1 >events/kprobes/r_do_fork_0/enable 开启“r do_fork”kprobe

要查看哪些进程触发了这些kprobe,可以通过trace、trace_pipe接口查看,输出格式如下,最左边是进程名,如果是<…>,可能是因为cat的时候,那个进程号对应的进程已经不存在了,第二个是进程PID,触发kprobe的时候记录的。FUNCTION就是触发的那个kprobe的名字,后面括号里是触发的时候代码位置,如果是“r”类型的kprobe,会显示返回到了什么代码位置。代码位置中的行号是反汇编对应的行号。

利用“r”方式的kprobe会显示函数返回地址,我们可以追某个函数被什么调用了,例如下面的do_fork被sys_clone调用了,我们就可以将sys_clone加到kprobe(echo r sys_clone >> kprobe_events),就可以看到哪个代码调用了sys_clone,直到追到我们想要看到的代码。


详细参数:

先说两个不重要的参数,几乎没什么用

GRP           : Group name. If omitted, use"kprobes" for it.

EVENT      : Event name. If omitted, the event nameis generated based on SYMBOL+offs or MEMADDR.

kprobe默认会在events/kprobes/目录下创建kprobe的接口目录,如果写了GRP、EVENT参数,kprobe目录的创建会按照我们的参数创建。例如


会在events目录下创建aa/bb的目录。

另外4个没什么用的参数:

@ADDR            : Fetch memory at ADDR (ADDR shouldbe in kernel)

@SYM[+|-offs]         : Fetch memory at SYM +|- offs (SYMshould be a data symbol)

$stackN   : Fetch Nth entry of stack (N >= 0)

$stack      : Fetch stack address.

ADDR SYM这两个很少用,因为只能读取固定地址。stack其实就是读取%sp寄存器,也没什么用。stackN是%sp+N,同样没什么用

$retval只能在“r”kprobe中使用,获取函数返回值,可以认为就是%ax。

更常用的是寄存器,因为大部分函数的参数都是通过寄存器传参的。少于6个的参数都是寄存器,依次是%di,%si,%dx,%cx,%r8,%r9。我们还可以用+0(%di)这种方式获取%di指向的地址的内容,并且可以嵌套(试过2层)+0(+16(%di))。

我们可以通过上面的参数获取一些值,默认都是以u64显示,可以通过类似 %di:u32这种方式获取指定类型的值。支持的类型有u8/u16/u32/u64/s8/s16/s32/s64/string。string是获取字符串。假如我们的%di是个字符串,我们可以写成+0(%di):string。我们可以给参数定义个名字如a=%di  b=%si

下面的是打印报文的一个例子,假设函数第一个参数是skb,并且skb->data指向ip头(协议栈中大部分时候都是这种情况),我们就可以打印出一些有用的信息,如下

skb=%di head=+0x18(%di) data=+0x20(%di) mac=+0x38(%di):u32 dev=+0(+16(%di)):string sip=+12(+0x20(%di)):u32 sport=+20(+0x20(%di)):u16 dip=+16(+0x20(%di)):u32 dport=+22(+0x20(%di)):u16

其中“head=+0x18(%di)”的0x18这个偏移需要根据当前内核中sk_buff结构体来确定,其他的几个偏移也一样。

过滤条件

创建一个kprobe之后,会生成这个kprobe的目录,里面包含4个文件,enable、filter、format、id。如果kprobe会打印太多东西,可以通过设置filter减少输出的内容。filter的格式和c语言的表达式类似,支持 ==,!=,>,<,>=,<=判断,并且支持与&&,或||,还有()。

例如我们对ip_rcv函数添加kprobe,命令如下

echo 'p ip_rcvskb=%di head=+0x18(%di) data=+0x20(%di) mac=+0x38(%di):u32 dev=+0(+16(%di)):string sip=+12(+0x20(%di)):u32 sport=+20(+0x20(%di)):u16 dip=+16(+0x20(%di)):u32 dport=+22(+0x20(%di)):u16 ' > kprobe_events

如果我们只想显示eth0口的报文,可以用如下filter。

echo dev==eth0  > events/kprobes/p_ip_rcv_0/filter

“dev”与我们设置kprobe的时候的“dev=+0(+16(%di)):string”对应。这一项是string类型,’==’会用strcmp比较。

如果我们只显示eth0,并且目的端口不是22的报文,过滤如下。报文是网络序,我们需要按照网络序去做匹配,22的 16进制是0x16,端口是u16类型,转换后就是0x1600。

echo 'dev==eth0&&dport!=0x1600'> events/kprobes/p_ip_rcv_0/filter

同样的方式我们还可以过滤ip等条件。

如果要关闭filter,可以echo ‘0’ >events/kprobes/p_ip_rcv_0/filter

 

0 0
原创粉丝点击