在谈论 eBPF 的挂载点时,**Probe(探针)**是绕不开的重要主题之一。
Probe 是 eBPF 程序实现内核行为监控、分析和调试的核心工具。它们通过挂载到内核的函数或事件上,在特定条件下触发 eBPF 程序。
下面我们详细介绍 Probe 的概念、分类、工作机制以及使用场景。
-
Probe 的概念
Probe 是一种轻量级探针机制,用于插入到内核或用户空间的特定位置,从而捕获行为或监控性能。
-
eBPF 中的 Probe 主要有以下两种类型:
- Kprobe 和 Kretprobe:挂载到内核函数的入口和返回点。
- Uprobe 和 Uretprobe:挂载到用户态函数的入口和返回点。
-
Probe 的分类与特点
(1) Kprobe 和 Kretprobe
Kprobe 和 Kretprobe 是 eBPF 中用于监控内核函数执行的重要工具。
Kprobe
-
挂载点:内核函数的入口。
-
作用:
- 捕获内核函数的调用参数。
- 分析函数调用的上下文信息。
-
上下文:
struct pt_regs,包含寄存器状态和参数信息。
示例:
SEC("kprobe/do_sys_open") int handle_kprobe(struct pt_regs *ctx) { const char *filename = (const char *)PT_REGS_PARM1(ctx); bpf_trace_printk("Opening file: %s\n", filename); return 0; }Kretprobe
-
挂载点:内核函数的返回点。
-
作用:
- 捕获函数的返回值。
- 分析函数执行的结果或后续行为。
-
上下文:
struct pt_regs,返回寄存器状态。
示例:
SEC("kretprobe/do_sys_open") int handle_kretprobe(struct pt_regs *ctx) { int retval = PT_REGS_RC(ctx); // 获取返回值 bpf_trace_printk("File opened, retval: %d\n", retval); return 0; }Kprobe 和 Kretprobe 的区别
特性 Kprobe Kretprobe 挂载点 函数入口 函数返回点 捕获内容 函数参数、调用上下文 函数返回值及上下文 使用场景 入参分析、逻辑捕获 返回值监控、结果验证 (2) Uprobe 和 Uretprobe
Uprobe 和 Uretprobe 用于用户态的函数跟踪,与 Kprobe 类似,但适用于用户空间程序。
Uprobe
-
挂载点:用户态函数入口。
-
作用:
- 监控用户态函数的调用。
- 分析函数的参数和行为。
-
上下文:
- 参数信息可通过特定方式获取,依赖用户空间函数签名。
示例:
SEC("uprobe/libc.so.6:malloc") int handle_uprobe(struct pt_regs *ctx) { size_t size = (size_t)PT_REGS_PARM1(ctx); bpf_trace_printk("malloc called with size: %lu\n", size); return 0; }Uretprobe
-
挂载点:用户态函数返回点。
-
作用:
- 捕获用户态函数返回值。
- 分析函数结果或行为。
-
上下文:
- 返回值可通过寄存器获取。
示例:
SEC("uretprobe/libc.so.6:malloc") int handle_uretprobe(struct pt_regs *ctx) { void *retval = (void *)PT_REGS_RC(ctx); bpf_trace_printk("malloc returned pointer: %p\n", retval); return 0; }Uprobe 和 Uretprobe 的特点
- 与特定的用户态函数绑定,适用于分析用户程序。
- 无需修改用户程序代码,实时捕获运行时信息。
-
-
Probe 的工作机制
-
挂载过程:
- Kprobe 和 Kretprobe:通过 eBPF 程序挂载到内核函数入口或返回点。
- Uprobe 和 Uretprobe:通过 eBPF 程序挂载到用户态函数的符号地址。
-
触发机制:
- 当目标函数被调用或返回时,Probe 被触发。
- 内核会将当前上下文信息传递给 eBPF 程序。
-
执行与返回:
- eBPF 程序执行预定义的逻辑(如参数记录、上下文分析)。
- 执行结束后,内核恢复函数的正常执行。
-
-
使用场景
(1) 调试与内核跟踪
-
目标:实时监控内核函数调用和返回。
-
场景:
- 捕获文件操作(如
sys_open)。 - 分析系统调用性能(如
sched_switch)。
- 捕获文件操作(如
(2) 性能分析
-
目标:定位性能瓶颈。
-
场景:
- 统计函数调用次数。
- 测量函数执行时间。
(3) 用户态程序监控
-
目标:分析用户空间程序行为。
-
场景:
- 捕获动态库调用(如
malloc)。 - 分析请求处理逻辑。
- 捕获动态库调用(如
-
-
Kprobe 和 Tracepoint 的对比
特性 Kprobe Tracepoint 挂载点 内核函数入口或返回点 内核预定义的事件触发点 性能 较高开销 开销更低,预先优化 功能复杂性 灵活,但需知道函数签名 适合监控特定行为 适用场景 精细化调试与跟踪 性能分析与事件监控
总结
Probe 是 eBPF 强大能力的核心之一,尤其是 Kprobe /Kretprobe 和 Uprobe/Uretprobe,提供了对内核和用户态函数的全面监控能力。它们在调试、性能分析、问题定位等领域有广泛应用,同时结合 eBPF 的强大数据处理能力,可以实现灵活而高效的监控和优化方案。
eBPF 程序通过挂载到内核的不同触发点运行,并在特定事件或条件下执行。这些挂载点定义了程序的触发位置和作用范围,并为 eBPF 提供了对应的上下文。
-
网络路径挂载点
网络路径挂载点主要用于处理网络流量,提供高性能、低延迟的解决方案。
(1) XDP (eXpress Data Path)
-
功能:直接在网卡驱动(NIC)层处理网络包,绕过 Linux 网络栈。
-
适用场景:
- 高性能包过滤。
- DDoS 防护。
- 网络负载均衡。
-
上下文:
- 提供网络包相关信息,如
struct xdp_md,包含包数据指针和长度。
- 提供网络包相关信息,如
-
特点:
- 性能极高,但逻辑复杂性受限。
示例:
SEC("xdp") int xdp_prog(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; // 简单过滤器,丢弃所有包 return XDP_DROP; }(2) TC ( Traffic Control )
-
功能:挂载在传输层或网络层,用于流量控制。
-
适用场景:
- 带宽限制。
- 网络流量统计。
- 流量重定向。
-
上下文:
- 提供
struct __sk_buff,包含数据包的元数据(如协议、源地址等)。
- 提供
-
特点:
- 相比 XDP,性能略低,但支持更复杂的逻辑。
示例:
SEC("tc") int tc_prog(struct __sk_buff *skb) { // 获取源 IP 地址(IPv4 示例) if (bpf_ntohs(skb->protocol) == ETH_P_IP) { struct iphdr *iph = bpf_hdr_pointer(skb, sizeof(*iph)); if (iph) { bpf_printk("Source IP: %d\n", iph->saddr); } } return TC_ACT_OK; } -
- 内核跟踪挂载点
用于监控和分析内核行为,支持挂载到内核函数或事件上。
(1) Kprobe 和 Kretprobe
-
功能:
Kprobe:挂载到内核函数入口,记录函数调用时的上下文。Kretprobe:挂载到函数返回点,获取返回值。
-
适用场景:
- 调试内核函数。
- 性能分析。
-
上下文:
- 提供
void *ctx,包含函数的寄存器状态和调用信息。
- 提供
示例:
SEC("kprobe/sys_open")
int kprobe_sys_open(struct pt_regs *ctx) {
const char *filename = (const char *)PT_REGS_PARM1(ctx);
bpf_trace_printk("Opening file: %s\n", filename);
return 0;
}
(2) Tracepoints
-
功能:挂载到内核预定义的事件触发点(如文件打开、调度器事件等)。
-
适用场景:
- 监控内核行为。
- 捕获关键性能指标。
-
上下文:
- 提供一个特定的
struct,包含事件的上下文信息(如文件描述符、进程 ID 等)。
- 提供一个特定的
示例:
SEC("tracepoint/syscalls/sys_enter_execve")
int tracepoint_execve(struct trace_event_raw_sys_enter *ctx) {
const char *filename = (const char *)ctx->args[0];
bpf_trace_printk("Executing program: %s\n", filename);
return 0;
}
- cgroup 挂载点
-
功能:挂载到 cgroup,用于限制资源或监控行为。
-
适用场景:
- 限制网络带宽或内存使用。
- 监控特定容器的行为。
-
上下文:
- 提供
struct cgroup或struct __sk_buff,根据功能不同上下文变化。
- 提供
示例:
SEC("cgroup/skb")
int cgroup_skb_filter(struct __sk_buff *skb) {
// 简单限制 TCP 包
if (skb->protocol == htons(ETH_P_IP)) {
struct iphdr *iph = bpf_hdr_pointer(skb, sizeof(*iph));
if (iph->protocol == IPPROTO_TCP) {
return 0; // 拒绝包
}
}
return 1; // 允许包
}
- 性能事件挂载点
-
功能:结合性能监控工具(如
perf),采集系统运行时的性能数据。 -
适用场景:
- 分析热点函数。
- 优化性能瓶颈。
-
上下文:
- 提供性能事件的上下文信息(如 CPU 使用率等)。
示例:
SEC("perf_event")
int perf_event_sample(struct bpf_perf_event_data *ctx) {
u64 counter = ctx->sample_period;
bpf_trace_printk("Sample period: %llu\n", counter);
return 0;
}
总结
eBPF 程序的挂载点决定了程序的触发机制和适用场景。选择适合的挂载点,需要综合考虑:
- 性能需求(如 XDP 优先于 TC)。
- 功能复杂性(如 Tracepoint 可捕获更多上下文)。
- 开发和调试成本(如 Kprobe 和 Tracepoint 的事件定义差异)。
这些挂载点与内核提供的上下文紧密结合,既扩展了 eBPF 的功能,也确保了安全性和高性能。