ftrace kprobe调试

本文介绍了如何使用kprobe进行内核调试,包括挂载debug分区、kprobe_events的使用、开启和过滤kprobe。kprobe允许在内核代码执行点中断并执行自定义代码,而无需重新编译内核。调试过程涉及启用kprobe、设置过滤条件以减少输出,以及通过内核接口观察调试信息。

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

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值