|
| 1 | +# Tracee eBPF 编程技巧示例 |
| 2 | + |
| 3 | +本示例展示了从 Aqua Security 的 [Tracee](https://github.com/aquasecurity/tracee) 项目学到的 eBPF 编程最佳实践。 |
| 4 | + |
| 5 | +## 目录结构 |
| 6 | + |
| 7 | +``` |
| 8 | +tracee_patterns/ |
| 9 | +├── common/ # 模块化头文件 |
| 10 | +│ ├── common.h # 基础宏和工具函数 |
| 11 | +│ ├── types.h # 类型定义 |
| 12 | +│ ├── maps.h # BPF Map 定义 |
| 13 | +│ ├── buffer.h # 缓冲区操作 |
| 14 | +│ ├── core_compat.h # CO-RE 兼容性 |
| 15 | +│ └── filtering.h # 位图过滤器 |
| 16 | +├── tracee_patterns.bpf.c # BPF 程序 |
| 17 | +├── tracee_patterns.c # 用户空间程序 |
| 18 | +├── Makefile |
| 19 | +└── README.md |
| 20 | +``` |
| 21 | + |
| 22 | +## 核心技巧 |
| 23 | + |
| 24 | +### 1. 模块化代码组织 |
| 25 | + |
| 26 | +将代码按功能拆分到不同头文件,提高可维护性: |
| 27 | + |
| 28 | +```c |
| 29 | +#include "common/common.h" // 基础宏 |
| 30 | +#include "common/types.h" // 类型定义 |
| 31 | +#include "common/maps.h" // Map 定义 |
| 32 | +#include "common/buffer.h" // 缓冲区操作 |
| 33 | +``` |
| 34 | + |
| 35 | +### 2. statfunc 强制内联 |
| 36 | + |
| 37 | +eBPF 不支持真正的函数调用,使用 `__always_inline` 确保内联: |
| 38 | + |
| 39 | +```c |
| 40 | +#define statfunc static __always_inline |
| 41 | + |
| 42 | +statfunc int my_helper_function(void *arg) { |
| 43 | + // ... |
| 44 | +} |
| 45 | +``` |
| 46 | +
|
| 47 | +### 3. 分支预测优化 |
| 48 | +
|
| 49 | +使用 `likely/unlikely` 优化热路径: |
| 50 | +
|
| 51 | +```c |
| 52 | +if (unlikely(ptr == NULL)) |
| 53 | + return 0; |
| 54 | +
|
| 55 | +if (likely(condition)) |
| 56 | + do_common_case(); |
| 57 | +``` |
| 58 | + |
| 59 | +### 4. 内联汇编边界检查 |
| 60 | + |
| 61 | +满足 verifier 对变量边界的要求: |
| 62 | + |
| 63 | +```c |
| 64 | +// 确保 size 不超过 MAX_SIZE |
| 65 | +asm volatile("if %[size] < %[max] goto +1;\n" |
| 66 | + "%[size] = %[max];\n" |
| 67 | + : |
| 68 | + : [size] "r"(size), [max] "i"(MAX_SIZE)); |
| 69 | +``` |
| 70 | +
|
| 71 | +### 5. CO-RE 跨内核兼容 |
| 72 | +
|
| 73 | +使用 `bpf_core_*` 函数处理内核版本差异: |
| 74 | +
|
| 75 | +```c |
| 76 | +// 检查字段是否存在 |
| 77 | +if (bpf_core_field_exists(task->start_boottime)) |
| 78 | + return BPF_CORE_READ(task, start_boottime); |
| 79 | +
|
| 80 | +// 检查函数是否可用 |
| 81 | +if (bpf_core_enum_value_exists(enum bpf_func_id, BPF_FUNC_ktime_get_boot_ns)) |
| 82 | + return bpf_ktime_get_boot_ns(); |
| 83 | +``` |
| 84 | + |
| 85 | +### 6. Tail Call 链式调用 |
| 86 | + |
| 87 | +突破 BPF 指令数量限制: |
| 88 | + |
| 89 | +```c |
| 90 | +SEC("raw_tracepoint/sys_enter") |
| 91 | +int tracepoint_sys_enter(struct bpf_raw_tracepoint_args *ctx) |
| 92 | +{ |
| 93 | + // 使用 tail call 跳转到下一个程序 |
| 94 | + bpf_tail_call(ctx, &prog_array, TAIL_SYSCALL_ENTER_INIT); |
| 95 | + return 0; |
| 96 | +} |
| 97 | +``` |
| 98 | +
|
| 99 | +### 7. PerCPU Buffer 规避栈限制 |
| 100 | +
|
| 101 | +eBPF 栈限制 512 字节,使用 PerCPU Array 存储大型数据: |
| 102 | +
|
| 103 | +```c |
| 104 | +struct { |
| 105 | + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); |
| 106 | + __uint(max_entries, 1); |
| 107 | + __type(key, u32); |
| 108 | + __type(value, event_data_t); // 可能很大 |
| 109 | +} event_data_map SEC(".maps"); |
| 110 | +``` |
| 111 | + |
| 112 | +### 8. 位图策略过滤 |
| 113 | + |
| 114 | +使用 64 位整数同时评估 64 个策略: |
| 115 | + |
| 116 | +```c |
| 117 | +// equals_in_policies: 哪些策略匹配 |
| 118 | +// key_used_in_policies: 哪些策略使用了这个 key |
| 119 | +// 返回匹配的策略位图 |
| 120 | +return equals_in_policies | (match_if_key_missing & ~key_used_in_policies); |
| 121 | +``` |
| 122 | + |
| 123 | +### 9. X 宏生成枚举 |
| 124 | + |
| 125 | +避免重复定义: |
| 126 | + |
| 127 | +```c |
| 128 | +#define EVENT_LIST \ |
| 129 | + X(EVENT_NONE, = 0) \ |
| 130 | + X(EVENT_PROCESS_EXEC, ) \ |
| 131 | + X(EVENT_PROCESS_EXIT, ) |
| 132 | + |
| 133 | +typedef enum { |
| 134 | + #define X(name, val) name val, |
| 135 | + EVENT_LIST |
| 136 | + #undef X |
| 137 | +} event_id_t; |
| 138 | +``` |
| 139 | +
|
| 140 | +### 10. 限流日志 |
| 141 | +
|
| 142 | +避免相同日志大量输出: |
| 143 | +
|
| 144 | +```c |
| 145 | +// 使用 map 记录日志计数和时间戳 |
| 146 | +// 2 秒内相同日志不重复输出 |
| 147 | +if ((now - last_ts) < 2000000000ULL) { |
| 148 | + count++; |
| 149 | + return; // 不输出 |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +## 编译和运行 |
| 154 | + |
| 155 | +```bash |
| 156 | +# 编译 |
| 157 | +make |
| 158 | + |
| 159 | +# 运行 (需要 root 权限) |
| 160 | +sudo ./tracee_patterns |
| 161 | +``` |
| 162 | + |
| 163 | +## 输出示例 |
| 164 | + |
| 165 | +``` |
| 166 | +=== Tracee eBPF Patterns Demo === |
| 167 | +展示从 Tracee 学到的 eBPF 编程技巧 |
| 168 | +
|
| 169 | +TIME EVENT CPU PID TID UID COMM DETAILS |
| 170 | +───────────────────────────────────────────────────────────────────────────────── |
| 171 | +14:30:15.123456 PROCESS_EXEC 0 12345 12345 1000 bash FILE:/usr/bin/ls |
| 172 | +14:30:15.123789 FILE_OPEN 0 12345 12345 1000 ls DFD:-100 FILE:/etc/passwd |
| 173 | +14:30:15.124001 PROCESS_EXIT 0 12345 12345 1000 ls EXIT_CODE:0 |
| 174 | +``` |
| 175 | + |
| 176 | +## 参考 |
| 177 | + |
| 178 | +- [Tracee GitHub](https://github.com/aquasecurity/tracee) |
| 179 | +- [Tracee eBPF 源码](https://github.com/aquasecurity/tracee/tree/main/pkg/ebpf/c) |
| 180 | +- [BPF CO-RE 参考指南](https://nakryiko.com/posts/bpf-core-reference-guide/) |
0 commit comments