You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用uprobe获取请求/响应体?OpenTelemetry Go工具链技术问询

OpenTelemetry Go eBPF工具链:HTTP请求/响应体获取方案

1. 从Event结构体获取response.req

response.req对应net/http.Response的Req字段,要拿到它需要两步:

  • 注入字段偏移:在probe.go里通过inject.Constants添加Response.Req的偏移注入:
    inject.WithOffset("resp_req_off", structfield.NewID("net/http", "Response", "Req"), ver)
    
  • eBPF侧解析指针:在对应的.bpf.c程序中,从Response指针(通常是uprobe的参数1)加上偏移得到Req指针:
    void *resp = PT_REGS_PARM1(ctx);
    uint64_t req_ptr = (uint64_t)resp + resp_req_off;
    
    如果需要把这个指针传到用户态的Event结构体,要在Event里新增uint64_t req_ptr;字段,在eBPF侧赋值后,用户态Go代码就能拿到该指针,后续用于解析Request的Body等字段。

2. 读取net/http.Request.Body字段(interface{}类型)

Body是io.ReadCloser接口,在64位Go中,接口内存布局为两个8字节指针:第一个指向itab(类型元数据),第二个指向实际实现的结构体实例。你注入的body_ptr_pos是接口的起始偏移,要读取实际Body内容:

  • 注入实现结构体的偏移:比如目标场景中Body是*bytes.Buffer,需要注入其Buf和Len字段的偏移:
    inject.WithOffset("buf_off", structfield.NewID("bytes", "Buffer", "Buf"), ver)
    inject.WithOffset("buf_len_off", structfield.NewID("bytes", "Buffer", "Len"), ver)
    
  • eBPF侧安全读取:
    // 从Request指针拿到Body接口的起始地址
    void *body_iface = (void *)((uint64_t)req_ptr + body_ptr_pos);
    // 读取接口的第二个指针(实际实现结构体的地址)
    void *body_impl;
    bpf_probe_read_user(&body_impl, sizeof(body_impl), (void *)((uint64_t)body_iface + 8));
    
    // 读取Buffer的内容指针和长度
    char *buf_addr;
    uint64_t buf_len;
    bpf_probe_read_user(&buf_addr, sizeof(buf_addr), (void *)((uint64_t)body_impl + buf_off));
    bpf_probe_read_user(&buf_len, sizeof(buf_len), (void *)((uint64_t)body_impl + buf_len_off));
    
    // 将内容拷贝到Event的缓冲区(注意截断,避免超过eBPF栈大小)
    #define MAX_BODY_SIZE 1024
    char body_buf[MAX_BODY_SIZE];
    uint64_t copy_len = buf_len > MAX_BODY_SIZE ? MAX_BODY_SIZE : buf_len;
    bpf_probe_read_user(body_buf, copy_len, buf_addr);
    
    // 赋值到Event字段
    __builtin_memcpy(&event.body_data, body_buf, copy_len);
    event.body_len = copy_len;
    
    注意:如果Body是其他实现(如*bufio.Reader),需要对应注入该结构体的字段偏移并调整解析逻辑;必须使用bpf_probe_read_user/bpf_copy_from_user安全读取用户态内存,避免非法访问。

3. 用uprobe获取HTTP请求/响应体的完整流程

核心步骤

  1. 选择uprobe挂载点:
    • 请求体:挂载net/http.(*ServeMux).ServeHTTP或net/http.serverHandler.ServeHTTP的入口,此时Request已初始化完成,可读取Body。
    • 响应体:挂载上述函数的出口,或net/http.(*response).Write等函数,读取响应内容。
  2. 关联请求与响应:用Request的指针作为唯一标识,在入口uprobe生成事件时记录该指针,出口uprobe中通过Response的Req指针关联到对应的请求事件。
  3. 用户态处理:在Go代码中读取eBPF事件,根据body_len字段处理拷贝的内容,若需要完整大请求体,可考虑使用环形缓冲区或分段读取(但eBPF中受限于内存,通常做截断处理)。

关键注意事项

  • 不要硬编码结构体偏移:必须通过structfield.NewID结合Go版本号获取偏移,不同Go版本的结构体布局可能变化。
  • 内存安全:eBPF中不能直接访问用户态内存,必须使用安全读取函数,避免触发内核报错。
  • 性能考虑:大请求体的读取会带来性能开销,建议只捕获必要的内容,或添加采样逻辑。

内容的提问来源于stack exchange,提问作者Evan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 09:25:56