在eBPF程序中使用bpf_probe_read复制大长度数据的问题
问题解决:eBPF读取write系统调用缓冲区的内核验证器错误
针对你在5.4.119内核下遇到的两个验证器错误,下面是具体原因和修复方案:
错误原因分析
1. R2 invalid mem access 'inv'
这个错误是因为内核验证器无法确认你操作的内存指针(R2)的有效性。通常是因为你没有对bpf_map_lookup_elem的返回值做非空检查,验证器认为你可能访问了无效内存,因此拒绝加载程序。
2. invalid stack type R4 off=-24 access_size=1039
5.4内核的eBPF栈大小限制为512字节,你尝试在栈上存储1024字节的缓冲区,直接触发了栈溢出,验证器报错。
修复方案
核心思路是:用BPF_MAP_TYPE_PERCPU_ARRAY存储大缓冲区,严格遵守内核验证器的指针有效性规则,避免栈溢出。
1. 正确定义Per-CPU数组Map
首先定义包含缓冲区和长度的结构体,作为Map的元素类型:
#include <uapi/linux/bpf.h> #include <linux/types.h> #include <linux/syscalls.h> // 存储write调用的用户缓冲区数据 struct write_records { char buf[1024]; size_t actual_len; }; // 定义Per-CPU数组Map,每个CPU单独占用一个元素 struct bpf_map_def SEC("maps") write_buf_map = { .type = BPF_MAP_TYPE_PERCPU_ARRAY, .key_size = sizeof(u32), .value_size = sizeof(struct write_records), .max_entries = 1, // 只需要一个索引即可,Per-CPU会自动隔离 };
2. 修正Tracepoint程序逻辑
在程序中必须先检查Map元素指针的有效性,再进行内存操作,同时避免使用栈存储大缓冲区:
SEC("tracepoint/syscalls/sys_enter_write") int trace_write_enter(struct trace_event_raw_sys_enter *ctx) { // 从tracepoint上下文获取write的参数 char __user *user_buf = (char __user *)ctx->args[1]; size_t write_len = ctx->args[2]; u32 map_key = 0; struct write_records *buf_data; // 获取Per-CPU数组的元素指针,必须检查非空 buf_data = bpf_map_lookup_elem(&write_buf_map, &map_key); if (!buf_data) { return 0; } // 计算实际读取长度,不超过缓冲区最大容量1024 size_t read_len = write_len > 1024 ? 1024 : write_len; // 读取用户态缓冲区到Per-CPU数组中 bpf_probe_read(buf_data->buf, read_len, user_buf); buf_data->actual_len = read_len; // 后续可将数据发送到用户态(例如通过Perf Event Array) // ... return 0; }
3. 移除__builtin_memset的替代方案
5.4内核对__builtin_memset的eBPF支持有限,且如果是为了初始化缓冲区,Per-CPU数组的内存默认会被初始化为0,无需手动调用memset。如果确实需要初始化,可通过循环赋值实现(但通常不需要)。
关键注意事项
- 5.4内核中,
bpf_probe_read可用于读取用户态内存,后续内核版本新增的bpf_probe_read_user在5.4中不可用。 - Per-CPU数组的每个元素在不同CPU上是独立的,无需考虑并发竞争,适合临时存储tracepoint的上下文数据。
- 如果需要将数据导出到用户态,建议搭配
BPF_MAP_TYPE_PERF_EVENT_ARRAY使用,将Per-CPU数组中的数据拷贝到Perf缓冲区后发送。
内容的提问来源于stack exchange,提问作者dcy
相关产品推荐
相关产品推荐

