我试图通过使用libbpf
来检测用户空间nginx函数。我能够连接一个探针,并从探针打印pid
,tid
等。然而,每当我试图解析函数参数数据时,我都会遇到很大的问题。我已经能够做到这一点与bpftrace
,但不能这样做与libbpf
。我的问题是,如何正确访问和打印我想要跟踪的用户空间函数的参数?
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 %dn", err);
return -2;
}
bpf_probe_read_user_str(str, sizeof(str), &s_ptr);
bpf_printk("String: %sn", str);
return 0;
}
每当我试图解析函数参数bpf_probe_read_user
返回错误-14
。当我尝试使用bpf_core_read
时,验证器拒绝了以下错误的代码。
❯ 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: %sn", str($req->request_line.data));
}
所以我想解析数据并根据它做一些自定义逻辑。
下面是ngx_http_request_s
和ngx_str_t
结构体。
编辑
用户空间码:
#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 skeletonn");
return 1;
}
err = nginx_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to auto-attach BPF skeleton: %dn", err);
goto cleanup;
}
printf("Successfully started!n");
for (;;) {
sleep(1);
}
cleanup:
nginx_bpf__destroy(skel);
return -err;
}
您实际上没有正确定义BPF程序。您应该执行如下操作。请注意,您也不应该使用bpf_core_read()
或BPF_CORE_READ()
的CO-RE变体,而应使用BPF_PROBE_READ_USER()
:
SEC("uprobe//usr/sbin/nginx:ngx_http_finalize_request")
int BPF_KPROBE(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;
/* you can access rc directly now, btw */
s_ptr = BPF_PROBE_READ_USER(r, request_line.data);
/* note no dereferencing of s_ptr above */
bpf_probe_read_user_str(str, sizeof(str), s_ptr);
bpf_printk("String: %sn", str);
return 0;
你不必使用BPF_PROBE_READ_USER()
宏,你可以做同样的bpf_probe_read_user()
就像你在你的例子。但是,如果您需要跟踪几层指针,BPF_PROBE_READ_USER()
将特别方便。
但是对于用户空间类型没有CO-RE,它只适用于内核类型,因为内核提供BTF信息来允许正确地重定位偏移量。