3. kprobe event简介

1,500 阅读5分钟

1. 参考资料

pwl999 kprobe event的使用

Linux阅码场 会议记录-使用Ftrace研究Linux内核

2. kprobe event简介

kprobe event能够动态探测任何kprobe能探测到的地方(这意味着所有的函数,除了那些使用__kprobes/nokprobe_inline注明和被NOKPROBE_SYMBOL标记的函数),也可以实现动态的增加和删除。

在编译内核时配置 CONFIG_KPROBE_EVENTS=y,使能这个特性,不需要通过current_tracer文件接口来激活。取而代之的是,通过“/sys/kernel/debug/tracing/kprobe_events”接口增加probe点,通过“/sys/kernel/debug/tracing/events/kprobes/enable”接口进行使能。

2.1 kprobe是如何工作的
2.1.1 对于一个kprobe插桩来说
  1. 将在要插桩的目标地址中的内容复制并保存,给单步断点指令腾出位置;
  2. 以单步中断指令覆盖目标地址;
  3. 当指令流执行到断点时,断点处理函数会检查这个断电是否是由kprobe注册的。如果是,就会先执行kprobe处理函数;
  4. 原始的指令会接着执行,指令流继续;
  5. 当不再需要kprobe时,原始的字节内容会被复制回目标地址上,这样这些指令就回到了它们的初始状态。
2.1.2 如果是一个kretprobe
  1. 对函数入口进行kprobe插桩
  2. 当函数入口被kprobe命中时,将返回地址保存并替换为一个trampoline函数地址
  3. 当函数最终返回时,CPU将控制交给trampoline函数处理
  4. 在kretprobe处理完成之后,再返回到之前保存的地址
  5. 当不再需要kretprobe时,函数入口的kprobe就被移除了
2.2 增加/删除kprobe event的命令格式
 p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]  : Set a probe
 r[MAXACTIVE][:[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.
MAXACTIVE      : Maximum number of instances of the specified function that
                 can be probed simultaneously, or 0 for the default value
                 as defined in Documentation/kprobes.txt section 1.3.1.

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.(*)
 $comm         : Fetch current task comm.
 +|-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), hexadecimal types
                 (x8/x16/x32/x64), "string" and bitfield are supported.

 (*) only for return probe.
 (**) this is useful for fetching a field of data structures.

在FETCHARGS中支持一系列的types,Kprobe tracer能够使用给定的type来显示数据。

  • ‘s’ 、‘u’前缀:分别表明signed、unsigned;
  • ‘x’前缀:意味着16进制;
  • 数字:十进制(‘s’ and ‘u’) ,16进制(‘x’)。没有类型固定,数字使用‘x32’还是‘x64’取决于架构(x86-32 uses x32, and x86-64 uses x64);
  • 字符串:将会在内存中读取一个“null-terminated”的字符串。
  • 对“$comm”,默认是“string”类型,其他类型非法。

如果你使用了“‘p:’ or ‘r:’+event name” > kprobe_events命令,新的kprobe event将会被添加,可以看到新events对应的文件夹tracing/events/kprobes/,包含‘id’, ‘enabled’, ‘format’ and ‘filter’文件。

  • enable:使能
  • filter:过滤想要的信息
  • trigger:事件发生时触发其他功能,例如function功能
  • format:环形队列缓冲区的格式
  • id: event对应的id

image.png

3. kprobe event示例

3.1 查看"vfs_open"当前打开文件名
cd /sys/kernel/debug/tracing/
# 先禁用数据写入到 Ring 缓冲区
echo 0 > tracing_on
# 清空trace文件
echo 0 > trace
# 创建文件用于测试
mkdir -p /home/idle/study/ftrace/kprobe_event_tracer
touch /home/idle/study/ftrace/kprobe_event_tracer/test_vfs_open
echo test_vfs_open > /home/idle/study/ftrace/kprobe_event_tracer/test_vfs_open
# 设置kprobe规则,获取vfs_open函数第一个参数path中的文件name
echo 'p vfs_open name=+0x38(+0x8($arg1)):string namep=+0(+0x28(+0x8($arg1))):string' > ./kprobe_events
# 使能上述的kprobe
echo 1 > ./events/kprobes/p_vfs_open_0/enable
# 使能数据写入到 Ring 缓冲区
echo 1 > tracing_on
# 触发事件
cat /home/idle/study/ftrace/function_graph_tracer/test_vfs_open
test_vfs_open
# 获取trace结果
cat trace

image.png

其中echo 'p vfs_open name=+0x38(+0x8(arg1)):stringnamep=+0(+0x28(+0x8(arg1)):string namep=+0(+0x28(+0x8(arg1))):string' > ./kprobe_events含义为:

  • p:kprobe
  • vfs_open:要跟踪的函数名
  • name=+0x38(+0x8($arg1)):string表示vfs_open函数第一个参数path结构体中打开的文件的文件名

name是一个标识符,可随意定义
$arg1表示vfs_open函数的第一个参数struct path结构体地址
+0x8($arg1)表示struct path地址偏移+0x8得到struct dentry地址
+0x38(+0x8($arg1))表示struct dentry地址+0x38得到数组d_iname的地址,d_iname表示文件的名字
:string表示数据type是一个字符串

  • namep=+0(+0x28(+0x8($arg1))):string表示vfs_open函数第一个参数path结构体中打开的文件的文件名,与上面一样

namep是一个标识符,可随意定义
$arg1表示vfs_open函数的第一个参数struct path结构体地址
+0x8($arg1)表示struct path地址偏移+0x8得到struct dentry地址
+0x28(+0x8($arg1))表示struct dentry地址+0x28得到struct qstr d_name中的const unsigned char *name的地址,注意d_name不是指针,则计算总偏移需计算d_name与name的偏移之和
+0(+0x28(+0x8($arg1)))表示取地址name指向的内容,即文件名(数组不用+0)
:string表示数据type是一个字符串

int vfs_open(const struct path *path, struct file *file)
struct path {
	struct vfsmount *mnt;
	struct dentry *dentry;
} __randomize_layout;
struct dentry {
	/* RCU lookup touched fields */
	unsigned int d_flags;		/* protected by d_lock */
	......
	struct qstr d_name;
	struct inode *d_inode;		/* Where the name belongs to - NULL is
					 * negative */
	unsigned char d_iname[DNAME_INLINE_LEN];	/* small names */
        ......
 }
 struct qstr {
	union {
		struct { HASH_LEN_DECLARE; };
		u64 hash_len;
	};
	const unsigned char *name;
};

那么如何计算一个结构体中成员相对于结构体起始地址的偏移呢?

  • 手动按照结构体对其原则进行计算
  • 在qemu中使用gdb进行计算,不熟悉qemu可参考QEMU
(gdb) p &((struct path*)0)->dentry    
$1 = (struct dentry **) 0x8 <irq_stack_union+8>
(gdb) p &((struct dentry*)0)->d_iname
$2 = (unsigned char (*)[32]) 0x38 <irq_stack_union+56>
(gdb) p &((struct dentry*)0)->d_name 
$3 = (struct qstr *) 0x20 <irq_stack_union+32>
(gdb) p &((struct qstr*)0)->name    
$4 = (const unsigned char **) 0x8 <irq_stack_union+8>
3.2 查看"vfs_open"输出整数
# 使能上述的kprobe
echo 0 > ./events/kprobes/p_vfs_open_0/enable
# 清空trace文件
echo 0 > trace
# 设置kprobe规则,x64表示16进制的数,u64表示无符号10进制的数
echo 'p vfs_open name=+0x38(+0x8($arg1)):string namep=+0(+0x28(+0x8($arg1))):string d_flag=+0x40($arg2):x64 version=+0xb8($arg2):u64' > ./kprobe_events
# 使能上述的kprobe
echo 1 > ./events/kprobes/p_vfs_open_0/enable
# 触发事件
cat /home/idle/study/ftrace/function_graph_tracer/test_vfs_open
test_vfs_open
# 获取trace结果
cat trace

image.png

3.3 设置了一个kretprobe,用来记录返回值
# 使能上述的kprobe
echo 0 > ./events/kprobes/p_vfs_open_0/enable
# 清空trace文件
echo 0 > trace
# 设置kprobe规则
echo 'p vfs_open name=+0x38(+0x8($arg1)):string namep=+0(+0x28(+0x8($arg1))):string' > ./kprobe_events
echo 'r vfs_open ret_val=$retval' >> kprobe_events
# 使能上述的kprobe
echo 1 > events/kprobes/p_vfs_open_0/enable
echo 1 > events/kprobes/r_vfs_open_0/enable
# 触发事件
cat /home/idle/study/ftrace/function_graph_tracer/test_vfs_open
test_vfs_open
# 获取trace结果
cat trace

image.png

3.4 filter:捕获"vfs_open"查看指定文件的信息的事件
# 设置过滤条件,name中包含test字段
echo 'name ~ "*test*"' > ./events/kprobes/p_vfs_open_0/filter
# 清空trace文件
echo 0 > trace
# 触发
cat /home/idle/study/ftrace/kprobe_event_tracer/test_vfs_open
test_vfs_open
# 获取trace结果
cat trace

image.png

3.5 trigger:包含"test"字段的文件的事件会触发"stacktrace"堆栈打印
# 包含"test"字段的文件的事件会触发"stacktrace"堆栈打印
echo 'stacktrace if name ~ "*test*"' > ./events/kprobes/p_vfs_open_0/trigger  
# 清空trace文件
echo 0 > trace
# 触发
cat /home/idle/study/ftrace/kprobe_event_tracer/test_vfs_open
test_vfs_open
# 获取trace结果
cat trace

image.png

可用的trigger如下,具体解释可参考trace event简介之trigger

image.png