Skip to content

feat(bpf): implement uprobe/uretprobe support #2150

Description

@sparkzky

背景

ANOLISA 的 agentsight 组件用 uprobe/uretprobe 解密 SSL/TLS 流量——在用户态函数 SSL_read/SSL_write 上挂探针,捕获加密前/解密后的明文数据,从而零侵入地观测 AI agent 的 LLM API 调用。

agentsight 的 eBPF 程序 bpf/sslsniff.bpf.c

  • SSL_readuprobe(入口探针)→ 获取 SSL 对象指针和缓冲区地址
  • SSL_readuretprobe(返回探针)→ 读取解密后的明文数据
  • 同理 SSL_write

这是 agentsight 可观测闭环的核心。没有它,无法验证"省了多少 token"。

DragonOS 现状

完全缺失

检查项 结果 证据
grep uprobe|uretprobekernel/src/ 零实现命中 仅有 bindings 引用,无代码使用
BPF_PROG_TYPE_UPROBE 不存在 kernel/src/include/bindings/linux_bpf.rs:334-369bpf_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 回调流程

  • int3do_int3()EBreak::handle()kprobe_handler() —— kernel/src/exception/ebreak.rs:15-35
  • KPROBE_MANAGERbreak_listBTreeMap<usize, Vec<LockKprobe>>),遍历 handler → call_pre_handler() + call_event_callback()
  • setup_single_step()RFLAGS.TF → 单步执行保存指令 → #DBDebugException::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 能否复用 说明
KprobeBuilderKprobeBasic 生命周期模式 ✅ 直接套用 builder → basic → register/unregister
EBreak/DebugException 异常分发 ✅ 共享 #BP/#DB 路径 加地址空间判断:内核地址 → kprobe,用户地址 → uprobe
KprobeManagerBTreeMap<addr, Vec<handler>> ✅ 同模式
PerfEventOps trait + BPF JIT bind ✅ 结构相同 UprobePerfEventKprobePerfEvent
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/(新建) 架构无关逻辑:UprobeBuilderUprobeBasic、probe point、ops trait——照 kprobe crate 结构
2 uprobe arch kernel/crates/uprobe/src/arch/x86/(新建) 页级 COW:找到目标用户页 → copy → 副本写 int3 → 改页表指向副本。保留原始页备份
3 异常分发 kernel/src/exception/ebreak.rsdebug.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)

  1. 用户态程序地址挂上 uprobe,执行到该地址时触发 #BP,handler 收到 pt_regs
  2. eBPF 程序通过 perf_event_open + PERF_EVENT_IOC_SET_BPF attach 成功
  3. SSL_read 入口挂 uprobe,能获取 SSL 对象指针和缓冲区参数

阶段二(uretprobe)

  1. SSL_read 挂 uretprobe,函数返回时触发,能读取解密后的明文缓冲区
  2. agentsight 的 sslsniff.bpf.c 在 DragonOS 上完整运行,能捕获 TLS 明文

依赖关系

  • 前置:无强依赖。但建议先完成 execve tracepoint issue(验证 eBPF→ring buffer 链路)
  • 后续:agentsight 可观测闭环依赖此 issue 完成
  • 建议:阶段一完成后验证再进入阶段二,不要一口吞

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions