背景
ANOLISA 的 agentsight 组件用 uprobe/uretprobe 解密 SSL/TLS 流量——在用户态函数 SSL_read/SSL_write 上挂探针,捕获加密前/解密后的明文数据,从而零侵入地观测 AI agent 的 LLM API 调用。
agentsight 的 eBPF 程序 bpf/sslsniff.bpf.c:
- 在
SSL_read 挂 uprobe(入口探针)→ 获取 SSL 对象指针和缓冲区地址
- 在
SSL_read 挂 uretprobe(返回探针)→ 读取解密后的明文数据
- 同理
SSL_write
这是 agentsight 可观测闭环的核心。没有它,无法验证"省了多少 token"。
DragonOS 现状
完全缺失
| 检查项 |
结果 |
证据 |
grep uprobe|uretprobe 在 kernel/src/ |
零实现命中 |
仅有 bindings 引用,无代码使用 |
BPF_PROG_TYPE_UPROBE |
不存在 |
kernel/src/include/bindings/linux_bpf.rs:334-369,bpf_prog_type 枚举止于 NETFILTER=32 |
perf_type_id 枚举 |
无 uprobe 变体 |
kernel/src/perf/mod.rs |
TIF_UPROBE 常量 |
被注释掉 |
kernel/src/arch/x86_64/process/mod.rs:681 |
| 官方文档 |
明确缺失 |
kernel/trace/index.rst:4:"内核当前支持 kprobe 和 eBPF" |
kprobe 框架现状(uprobe 的最近参考)
DragonOS 的 kprobe 实现完整且成熟,是 uprobe 的最佳复用基础:
断点机制(x86_64):
KprobeBuilder::replace_inst() 保存原始指令到 X86KprobePoint.old_instruction[15],写入 0xcc(int3)—— kernel/crates/kprobe/src/arch/x86/mod.rs:84-102
- hit 后
X86KprobePoint::drop() 恢复 —— 同文件 L25-42
handler 回调流程:
int3 → do_int3() → EBreak::handle() → kprobe_handler() —— kernel/src/exception/ebreak.rs:15-35
KPROBE_MANAGER 查 break_list(BTreeMap<usize, Vec<LockKprobe>>),遍历 handler → call_pre_handler() + call_event_callback()
setup_single_step() 置 RFLAGS.TF → 单步执行保存指令 → #DB → DebugException::handle() → call_post_handler() —— kernel/src/exception/debug.rs:13-30
eBPF attach 路径:
perf_event_open(PERF_TYPE_MAX) → perf_event_open_kprobe() —— kernel/src/perf/kprobe.rs:161
ioctl(PERF_EVENT_IOC_SET_BPF) → do_set_bpf_prog() → EbpfVmRaw::new() + JIT → 注册为 event_callback —— 同文件 L44
核心 trait/struct(可复用的抽象层):
// kernel/crates/kprobe/src/arch/mod.rs
pub trait ProbeArgs: Send { fn break_address(&self) -> usize; fn debug_address(&self) -> usize; }
pub trait KprobeOps: Send { fn return_address(&self) -> usize; ... }
pub trait CallBackFunc: Send + Sync { fn call(&self, trap_frame: &dyn ProbeArgs); }
pub struct KprobeBuilder { symbol, symbol_addr, offset, pre_handler, post_handler, event_callback }
pub struct KprobeBasic { symbol, symbol_addr, enable, ... }
// kernel/src/debug/kprobe/mod.rs
pub struct KprobeManager { break_list: BTreeMap<usize, Vec<LockKprobe>>, debug_list: BTreeMap<usize, Vec<LockKprobe>> }
复用度分析
| kprobe 已有 |
uprobe 能否复用 |
说明 |
KprobeBuilder→KprobeBasic 生命周期模式 |
✅ 直接套用 |
builder → basic → register/unregister |
EBreak/DebugException 异常分发 |
✅ 共享 #BP/#DB 路径 |
加地址空间判断:内核地址 → kprobe,用户地址 → uprobe |
KprobeManager 的 BTreeMap<addr, Vec<handler>> |
✅ 同模式 |
|
PerfEventOps trait + BPF JIT bind |
✅ 结构相同 |
UprobePerfEvent 照 KprobePerfEvent 写 |
| int3 字节替换 |
❌ |
kprobe 直接写内核 text;uprobe 必须页级 COW——替换整个页映射 |
| 内核符号查找 |
❌ |
uprobe 需 path → inode → file offset 地址解析 |
| 全局断点列表 |
❌ |
uprobe 按进程隔离(同一 offset 在不同 mm 映射不同) |
| uretprobe trampoline |
❌ |
全新机制:用户栈注入 trampoline |
实现计划(分两阶段)
阶段一:uprobe(断点探针)
先做这个。验证 SSL_read 入口探针能挂上、能触发。
| 步骤 |
子系统 |
目录 |
做什么 |
| 1 |
uprobe crate |
kernel/crates/uprobe/(新建) |
架构无关逻辑:UprobeBuilder、UprobeBasic、probe point、ops trait——照 kprobe crate 结构 |
| 2 |
uprobe arch |
kernel/crates/uprobe/src/arch/x86/(新建) |
页级 COW:找到目标用户页 → copy → 副本写 int3 → 改页表指向副本。保留原始页备份 |
| 3 |
异常分发 |
kernel/src/exception/ebreak.rs、debug.rs |
#BP/#DB handler 加地址空间判断:rip 在内核空间 → kprobe,在用户空间 → uprobe |
| 4 |
内存管理 |
kernel/src/mm/ |
断点页 COW、原始页备份映射、页表操作、TLB shootdown(并发安全最难点) |
| 5 |
进程管理 |
kernel/src/process/ |
per-process uprobe 列表(mm_struct 关联)、恢复 TIF_UPROBE flag、返回用户态时检查 |
| 6 |
VFS |
kernel/src/filesystem/ |
path → inode → file offset 地址解析 |
| 7 |
Perf 集成 |
kernel/src/perf/ |
UprobePerfEvent + perf_event_open_uprobe() + 新 PERF_TYPE 变体 |
| 8 |
BPF bindings |
kernel/src/include/bindings/linux_bpf.rs |
添加 BPF_PROG_TYPE_UPROBE(如需) |
阶段二:uretprobe(返回探针)
agentsight 的 sslsniff.bpf.c 需要捕获 SSL_read 返回值(解密后的明文),这依赖 uretprobe。
| 步骤 |
做什么 |
| 1 |
trampoline 页注入用户地址空间 |
| 2 |
栈操作:入口探针触发时,保存原返回地址到 per-thread storage,替换栈上返回地址为 trampoline 地址 |
| 3 |
trampoline 执行 int3 → 内核 uretprobe handler 回调 → 恢复原返回地址 → 返回用户态 |
要碰的子系统清单
kernel/crates/uprobe/ (NEW — 核心逻辑 + arch)
kernel/src/exception/ebreak.rs (修改 — 地址空间判断分发)
kernel/src/exception/debug.rs (修改 — 同上)
kernel/src/mm/ (修改 — 页 COW + TLB shootdown)
kernel/src/process/ (修改 — per-process uprobe 追踪)
kernel/src/filesystem/ (修改 — path→inode→offset 解析)
kernel/src/perf/ (修改 — UprobePerfEvent + PERF_TYPE)
kernel/src/include/bindings/ (修改 — 枚举常量)
复杂度评估
大。约 3000-5000 行,8+ 文件,涉及 6 个子系统。
最大风险:用户态内存安全——COW 竞争、mmap/unmap 并发、TLB shootdown。这是内核开发中最硬的问题之一。kprobe 的直接字节写法在此完全不适用。
uretprobe 的 trampoline 注入是架构层面的独立机制,与断点探针共享的基础设施很少。
多架构支持(x86_64, riscv64, loongarch64)额外增加工作量和测试复杂度。
验证标准
阶段一(uprobe)
- 用户态程序地址挂上 uprobe,执行到该地址时触发
#BP,handler 收到 pt_regs
- eBPF 程序通过
perf_event_open + PERF_EVENT_IOC_SET_BPF attach 成功
- 在
SSL_read 入口挂 uprobe,能获取 SSL 对象指针和缓冲区参数
阶段二(uretprobe)
- 在
SSL_read 挂 uretprobe,函数返回时触发,能读取解密后的明文缓冲区
- agentsight 的
sslsniff.bpf.c 在 DragonOS 上完整运行,能捕获 TLS 明文
依赖关系
- 前置:无强依赖。但建议先完成 execve tracepoint issue(验证 eBPF→ring buffer 链路)
- 后续:agentsight 可观测闭环依赖此 issue 完成
- 建议:阶段一完成后验证再进入阶段二,不要一口吞
背景
ANOLISA 的 agentsight 组件用 uprobe/uretprobe 解密 SSL/TLS 流量——在用户态函数
SSL_read/SSL_write上挂探针,捕获加密前/解密后的明文数据,从而零侵入地观测 AI agent 的 LLM API 调用。agentsight 的 eBPF 程序
bpf/sslsniff.bpf.c:SSL_read挂 uprobe(入口探针)→ 获取 SSL 对象指针和缓冲区地址SSL_read挂 uretprobe(返回探针)→ 读取解密后的明文数据SSL_write这是 agentsight 可观测闭环的核心。没有它,无法验证"省了多少 token"。
DragonOS 现状
完全缺失
grep uprobe|uretprobe在kernel/src/BPF_PROG_TYPE_UPROBEkernel/src/include/bindings/linux_bpf.rs:334-369,bpf_prog_type枚举止于NETFILTER=32perf_type_id枚举kernel/src/perf/mod.rsTIF_UPROBE常量kernel/src/arch/x86_64/process/mod.rs:681kernel/trace/index.rst:4:"内核当前支持 kprobe 和 eBPF"kprobe 框架现状(uprobe 的最近参考)
DragonOS 的 kprobe 实现完整且成熟,是 uprobe 的最佳复用基础:
断点机制(x86_64):
KprobeBuilder::replace_inst()保存原始指令到X86KprobePoint.old_instruction[15],写入0xcc(int3)——kernel/crates/kprobe/src/arch/x86/mod.rs:84-102X86KprobePoint::drop()恢复 —— 同文件 L25-42handler 回调流程:
int3→do_int3()→EBreak::handle()→kprobe_handler()——kernel/src/exception/ebreak.rs:15-35KPROBE_MANAGER查break_list(BTreeMap<usize, Vec<LockKprobe>>),遍历 handler →call_pre_handler()+call_event_callback()setup_single_step()置RFLAGS.TF→ 单步执行保存指令 →#DB→DebugException::handle()→call_post_handler()——kernel/src/exception/debug.rs:13-30eBPF attach 路径:
perf_event_open(PERF_TYPE_MAX)→perf_event_open_kprobe()——kernel/src/perf/kprobe.rs:161ioctl(PERF_EVENT_IOC_SET_BPF)→do_set_bpf_prog()→EbpfVmRaw::new()+ JIT → 注册为event_callback—— 同文件 L44核心 trait/struct(可复用的抽象层):
复用度分析
KprobeBuilder→KprobeBasic生命周期模式EBreak/DebugException异常分发#BP/#DB路径KprobeManager的BTreeMap<addr, Vec<handler>>PerfEventOpstrait + BPF JIT bindUprobePerfEvent照KprobePerfEvent写path → inode → file offset地址解析实现计划(分两阶段)
阶段一:uprobe(断点探针)
kernel/crates/uprobe/(新建)UprobeBuilder、UprobeBasic、probe point、ops trait——照 kprobe crate 结构kernel/crates/uprobe/src/arch/x86/(新建)kernel/src/exception/ebreak.rs、debug.rs#BP/#DBhandler 加地址空间判断:rip在内核空间 → kprobe,在用户空间 → uprobekernel/src/mm/kernel/src/process/mm_struct关联)、恢复TIF_UPROBEflag、返回用户态时检查kernel/src/filesystem/path → inode → file offset地址解析kernel/src/perf/UprobePerfEvent+perf_event_open_uprobe()+ 新PERF_TYPE变体kernel/src/include/bindings/linux_bpf.rsBPF_PROG_TYPE_UPROBE(如需)阶段二:uretprobe(返回探针)
int3→ 内核 uretprobe handler 回调 → 恢复原返回地址 → 返回用户态要碰的子系统清单
复杂度评估
大。约 3000-5000 行,8+ 文件,涉及 6 个子系统。
最大风险:用户态内存安全——COW 竞争、mmap/unmap 并发、TLB shootdown。这是内核开发中最硬的问题之一。kprobe 的直接字节写法在此完全不适用。
uretprobe 的 trampoline 注入是架构层面的独立机制,与断点探针共享的基础设施很少。
多架构支持(x86_64, riscv64, loongarch64)额外增加工作量和测试复杂度。
验证标准
阶段一(uprobe)
#BP,handler 收到 pt_regsperf_event_open+PERF_EVENT_IOC_SET_BPFattach 成功SSL_read入口挂 uprobe,能获取 SSL 对象指针和缓冲区参数阶段二(uretprobe)
SSL_read挂 uretprobe,函数返回时触发,能读取解密后的明文缓冲区sslsniff.bpf.c在 DragonOS 上完整运行,能捕获 TLS 明文依赖关系