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

如何通过libbpf访问用户态Nginx函数的参数?

问题:使用libbpf插桩Nginx用户态函数时,如何正确访问并打印函数参数?

我尝试使用libbpf对用户态Nginx函数进行插桩,已成功挂载uprobe并能从探针中打印pid、tid等信息,但在解析函数参数数据时遇到了极大问题。我可以用bpftrace实现该功能,但无法用libbpf完成。

现有BPF代码(nginx.bpf.c)

#include "ngx_http.h"
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>

char LICENSE[] SEC("license") = "Dual BSD/GPL";

SEC("uprobe//usr/sbin/nginx:ngx_http_finalize_request")
int handle_ngx_http_finalize_request(struct ngx_http_request_s* r, ngx_int_t rc)
{
    u_char *s_ptr;
    u_char str[128];
    int err;

    err = bpf_probe_read_user(&s_ptr, sizeof(s_ptr), &r->request_line.data);
    if (!s_ptr || err < 0) {
        bpf_printk("Error %d\n", err);
        return -2;
    }

    bpf_probe_read_user_str(str, sizeof(str), &s_ptr);

    bpf_printk("String: %s\n", str);

    return 0;
}

错误现象

  • 使用bpf_probe_read_user时返回错误码-14
  • 使用bpf_core_read时,BPF验证器拒绝代码,错误日志如下:
❯ sudo ./nginx
libbpf: loading object 'nginx_bpf' from buffer
libbpf: elf: section(3) uprobe//usr/sbin/nginx:ngx_http_finalize_request, size 280, link 0, flags 6, type=1
libbpf: sec 'uprobe//usr/sbin/nginx:ngx_http_finalize_request': found program 'handle_ngx_http_finalize_request' at insn offset 0 (0 bytes), code size 35 insns (280 bytes)
libbpf: elf: section(4) .reluprobe//usr/sbin/nginx:ngx_http_finalize_request, size 32, link 12, flags 40, type=9
libbpf: elf: section(5) license, size 13, link 0, flags 3, type=1
libbpf: license of nginx_bpf is Dual BSD/GPL
libbpf: elf: section(6) .rodata, size 22, link 0, flags 2, type=1
libbpf: elf: section(7) .BTF, size 12720, link 0, flags 0, type=1
libbpf: elf: section(9) .BTF.ext, size 252, link 0, flags 0, type=1
libbpf: elf: section(12) .symtab, size 240, link 1, flags 0, type=2
libbpf: looking for externs among 10 symbols...
libbpf: collected 0 externs total
libbpf: map 'nginx_bp.rodata' (global data): at sec_idx 6, offset 0, flags 80.
libbpf: map 0 is "nginx_bp.rodata"
libbpf: sec '.reluprobe//usr/sbin/nginx:ngx_http_finalize_request': collecting relocation for section(3) 'uprobe//usr/sbin/nginx:ngx_http_finalize_request'
libbpf: sec '.reluprobe//usr/sbin/nginx:ngx_http_finalize_request': relo #0: insn #13 against '.rodata'
libbpf: prog 'handle_ngx_http_finalize_request': found data map 0 (nginx_bp.rodata, sec 6, off 0) for insn 13
libbpf: sec '.reluprobe//usr/sbin/nginx:ngx_http_finalize_request': relo #1: insn #28 against '.rodata'
libbpf: prog 'handle_ngx_http_finalize_request': found data map 0 (nginx_bp.rodata, sec 6, off 0) for insn 28
libbpf: loading kernel BTF '/sys/kernel/btf/vmlinux': 0
libbpf: map 'nginx_bp.rodata': created successfully, fd=4
libbpf: sec 'uprobe//usr/sbin/nginx:ngx_http_finalize_request': found 1 CO-RE relocations
libbpf: prog 'handle_ngx_http_finalize_request': relo #0: <byte_off> [2] typedef ngx_http_request_t.request_line.data (0:21:1 @ offset 992)
libbpf: prog 'handle_ngx_http_finalize_request': relo #0: no matching targets found
libbpf: prog 'handle_ngx_http_finalize_request': relo #0: substituting insn #1 w/ invalid insn
libbpf: prog 'handle_ngx_http_finalize_request': BPF program load failed: Invalid argument
libbpf: prog 'handle_ngx_http_finalize_request': -- BEGIN PROG LOAD LOG --
R1 type=ctx expected=fp
; int handle_ngx_http_finalize_request(ngx_http_request_t* r, ngx_int_t rc)
0: (bf) r3 = r1
1: <invalid CO-RE relocation>
failed to resolve CO-RE relocation <byte_off> [2] typedef ngx_http_request_t.request_line.data (0:21:1 @ offset 992)
processed 2 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
-- END PROG LOAD LOG --
libbpf: prog 'handle_ngx_http_finalize_request': failed to load: -22
libbpf: failed to load object 'nginx_bpf'
libbpf: failed to load BPF skeleton 'nginx_bpf': -22
Failed to open and load BPF skeleton

可正常工作的bpftrace代码(nginx.bt)

uprobe:/usr/sbin/nginx:ngx_http_finalize_request
{
    $req = (struct ngx_http_request_s*)arg0;
    printf("Request Line: %s\n", str($req->request_line.data));
}

用户态加载代码

#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "nginx.skel.h"

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
    return vfprintf(stderr, format, args);
}

int main(int argc, char **argv)
{
    struct nginx_bpf *skel;
    int err;

    libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
    libbpf_set_print(libbpf_print_fn);

    skel = nginx_bpf__open_and_load();
    if (!skel) {
        fprintf(stderr, "Failed to open and load BPF skeleton\n");
        return 1;
    }

    err = nginx_bpf__attach(skel);
    if (err) {
            fprintf(stderr, "Failed to auto-attach BPF skeleton: %d\n", err);
            goto cleanup;
    }

    printf("Successfully started!\n");

    for (;;) {
        sleep(1);
    }

cleanup:
    nginx_bpf__destroy(skel);
    return -err;
}

解决方案

错误原因分析

  1. bpf_probe_read_user返回-14(EFAULT):直接取&r->request_line.data属于在内核态非法解引用用户态指针,必须通过bpf_probe_read_user系列函数安全读取用户态内存。
  2. CO-RE重定位失败:CO-RE用于适配内核结构体版本差异,Nginx是用户态程序,内核无其BTF信息,因此libbpf无法完成CO-RE重定位。

修正后的BPF代码

#include "ngx_http.h"
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

char LICENSE[] SEC("license") = "Dual BSD/GPL";

// 手动定义ngx_str_t结构体(若头文件未正确包含时使用)
struct ngx_str_t {
    u_char *data;
    size_t len;
};

SEC("uprobe//usr/sbin/nginx:ngx_http_finalize_request")
int handle_ngx_http_finalize_request(struct ngx_http_request_s* r, ngx_int_t rc)
{
    struct ngx_str_t req_line;
    u_char str[128];
    int err;
    size_t copy_len;

    // 第一阶段:读取request_line结构体(用户态内存)
    err = bpf_probe_read_user(&req_line, sizeof(req_line), &r->request_line);
    if (err < 0) {
        bpf_printk("Failed to read request_line: %d\n", err);
        return -1;
    }

    // 校验数据有效性
    if (!req_line.data || req_line.len == 0) {
        bpf_printk("Invalid request line data\n");
        return -1;
    }

    // 计算安全读取长度,避免缓冲区溢出
    copy_len = req_line.len < (sizeof(str) - 1) ? req_line.len : (sizeof(str) - 1);

    // 第二阶段:读取request_line.data指向的字符串内容
    err = bpf_probe_read_user(str, copy_len, req_line.data);
    if (err < 0) {
        bpf_printk("Failed to read request string: %d\n", err);
        return -1;
    }

    // 手动添加字符串终止符
    str[copy_len] = '\0';

    bpf_printk("Request Line: %s\n", str);

    return 0;
}

关键修改说明

  • 分阶段读取用户态内存:先读取ngx_http_request_s中的request_line结构体,再读取结构体中data指针指向的字符串,避免直接解引用用户态指针。
  • 缓冲区溢出防护:根据ngx_str_t的len字段控制读取长度,防止超出BPF程序中数组的大小。
  • 移除CO-RE依赖:由于用户态程序无内核BTF支持,直接使用bpf_probe_read_user完成内存读取,无需CO-RE重定位。

编译运行注意事项

  1. 确保编译时正确包含Nginx头文件,或手动定义所需结构体(如ngx_str_t)。
  2. 保持常规libbpf编译流程,无需启用CO-RE相关编译选项。
  3. 运行用户态加载程序时需使用sudo获取足够权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 02:05:20