Kprobes 使用与原理介绍

.

简介

Kprobes (kprobe and kretprobe)是一种Linux的调试机制,用于监控系统内部事件。你可以使用 Kprobes 来解决性能瓶颈、记录特定事件、跟踪问题等。

使用方法

kprobes 接口

内核源码samples目录下 kprobes/kprobe_example.c 提供了 Kprobes 测试用例。代码非常简单,主要配置了 struct kprobe 如下几个字段:

  • symbol_name
  • offset
  • pre_handler
  • post_handler

最后调用 register_kprobe() 注册定义的kprobe。

1
2
int register_kprobe(struct kprobe *p)
void unregister_kprobe(struct kprobe *p)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct kprobe {
struct hlist_node hlist;

/* list of kprobes for multi-handler support */
struct list_head list;

/*count the number of times this probe was temporarily disarmed */
unsigned long nmissed;

/* location of the probe point */
kprobe_opcode_t *addr;

/* Allow user to indicate symbol name of the probe point */
const char *symbol_name;

/* Offset into the symbol */
unsigned int offset;

/* Called before addr is executed. */
kprobe_pre_handler_t pre_handler;

/* Called after addr is executed, unless... */
kprobe_post_handler_t post_handler;

/* Saved opcode (which has been replaced with breakpoint) */
kprobe_opcode_t opcode;

/* copy of the original instruction */
struct arch_specific_insn ainsn;

/*
* Indicates various status flags.
* Protected by kprobe_mutex after this kprobe is registered.
*/
u32 flags;
};

kprobe trace

使用方法和基于tracepoint的events类似. 不同的是Kprobes可以probe几乎所有函数(除了标注__kprobes/nokprobe_inline 和被标记为 NOKPROBE_SYMBOL的函数),且在系统运行时能够动态添加/删除。使用方法详见内核文档 kprobetrace。主要是使用如下接口:

1
2
3
/sys/kernel/debug/tracing/kprobe_events
/sys/kernel/debug/tracing/events/kprobes/<EVENT>/enable
/sys/kernel/debug/tracing/dynamic_events

bpftrace

bpftrace使用很简单,详见 bpftrace guide。例如,使用 bpftrace 通过 kretprobe 获取 ktime_get 返回的时间:

1
sudo bpftrace -e 'kretprobe:ktime_get { printf("time: %lld\n", retval);}'

Kprobes在arm64上的实现

kprobe 和 kretprobe 原理相似,因此本文只分析kprobe流程。

register_kprobe流程

  • arch_prepare_kprobe() → arch_prepare_ss_slot(), 拷贝probe点的指令(接下来需要替换probe位置的指令),调用 get_insn_slot() 获取一个slot,按照如下顺序排放:
    • 拷贝的probe点的指令
    • brk 0x6
  • arm_kprobe() → arch_arm_kprobe(),probe位置的指令替换为 brk 0x4

BRK #<imm>,立即数 imm 会保存会在 ESR_ELx.ISS
ESR, From ARM Architecture Reference Manual D17.2.37

异常处理流程

1、执行到probe点后,由于已经替换成了 brk 0x4,执行后陷入el1 debug异常。 call_break_hook 根据 ESR.ISS(0x4)调用 kprobe_breakpoint_handler,然后再根据probe点的 addr 在kprobes的哈希链表找到注册的kprobe结构体,再调用结构体里的 pre_handler() 就完成了。调用栈如下:

1
2
3
4
5
6
7
8
handler_pre+0x24/0x70 [kprobe_example]
kprobe_breakpoint_handler+0xc0/0x174
call_break_hook+0x88/0xa4
brk_handler+0x2c/0x70
do_debug_exception+0x6c/0x110
el1_dbg+0x68/0x80
el1h_64_sync_handler+0xb4/0xd0
el1h_64_sync+0x64/0x68

2、执行完 pre_handler() 后,跳转到之前保存的slot执行,第一条指令就是原来probe点的指令,接下来执行下一条指令 brk 0x6 后再次陷入异常。流程和上文相似,只不过 ESR.ISS 变成了 0x6, 因此 call_break_hook 调用 kprobe_breakpoint_ss_handler,最后执行完 handler_post() 并退出异常,继续正常的代码流程。调用栈如下:

1
2
3
4
5
6
7
8
9
handler_post+0x24/0x50 [kprobe_example]
post_kprobe_handler+0x60/0xb0
kprobe_breakpoint_ss_handler+0x94/0xa0
call_break_hook+0x88/0xa4
brk_handler+0x2c/0x70
do_debug_exception+0x6c/0x110
el1_dbg+0x68/0x80
el1h_64_sync_handler+0xb4/0xd0
el1h_64_sync+0x64/0x68

Jump Optimization

ToDo

Safety Check

Preparing Detour Buffer

Pre-optimization

参考文档

  1. An introduction to KProbes
  2. Linux 系统动态追踪技术介绍

Kprobes 使用与原理介绍
https://yuliao0214.github.io/2023/11/08/kprobe/
Author
Yu Liao
Posted on
November 8, 2023
Licensed under