目录
bpftrace是linux增强的eBPF的高级跟踪语言。其使用LLVM作为后端,将脚本编译为BPF字节码,并利用BCC与linux BPF进行交互,以实现linux的跟踪功能:kprobe,uprobe和tracepoint。使用方式可参考bpftrace 教程
使用bpftrace查询跟踪点 -l
查询execve跟踪点
- //查询所有内核插装和跟踪点
- # bpftrace -l
-
- //查询execve的跟踪点
- # bpftrace -l '*execve*'
- hardware:*execve*:
- kfunc:__ia32_compat_sys_execve
- kfunc:__ia32_compat_sys_execveat
- kfunc:__ia32_sys_execve
- kfunc:__ia32_sys_execveat
- kfunc:__x64_compat_sys_execve
- kfunc:__x64_compat_sys_execveat
- kfunc:__x64_sys_execve
- kfunc:__x64_sys_execveat
- kfunc:audit_log_execve_info
- kfunc:bprm_execve
- kfunc:kernel_execve
- kprobe:__ia32_compat_sys_execve
- kprobe:__ia32_compat_sys_execveat
- kprobe:__ia32_sys_execve
- kprobe:__ia32_sys_execveat
- kprobe:__x64_compat_sys_execve
- kprobe:__x64_compat_sys_execveat
- kprobe:__x64_sys_execve
- kprobe:__x64_sys_execveat
- kprobe:audit_log_execve_info
- kprobe:bprm_execve
- kprobe:do_execveat_common.isra.0
- kprobe:kernel_execve
- software:*execve*:
- tracepoint:syscalls:sys_enter_execve
- tracepoint:syscalls:sys_enter_execveat
- tracepoint:syscalls:sys_exit_execve
- tracepoint:syscalls:sys_exit_execveat
-
内核插桩(kprobe)和跟踪点(tracepoint),建议使用tracepoint,更加稳定。
查询函数的参数 -v
- //-v 参数查询 函的参数
- #bpftrace -lv tracepoint:syscalls:sys_enter_execve
- tracepoint:syscalls:sys_enter_execve
- int __syscall_nr
- const char * filename
- const char *const * argv
- const char *const * envp
-
- #bpftrace -lv tracepoint:syscalls:sys_exit_execve
- tracepoint:syscalls:sys_exit_execve
- int __syscall_nr
- long ret
bpftrace 实例
- #sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve,tracepoint:syscalls:sys_enter_execveat { printf("%-6d %-8s", pid, comm); join(args->argv);}'
-
- Attaching 1 probe...
- 18376 bash ls --color=auto
- 18377 bash ls --color=auto
-
命令行解析
再开一个中断,输入ls命令,即可在跟踪的终端上看到输出 ls命令
C语言与Python语言
- from bcc import BPF
-
- # define BPF program
- prog = """
- #include
- // define output data structure in C
- struct data_t {
- u32 pid;
- u64 ts;
- char comm[TASK_COMM_LEN];
- };
- BPF_PERF_OUTPUT(events);
- int hello(struct pt_regs *ctx) {
- struct data_t data = {};
- data.pid = bpf_get_current_pid_tgid();
- data.ts = bpf_ktime_get_ns();
- bpf_get_current_comm(&data.comm, sizeof(data.comm));
- events.perf_submit(ctx, &data, sizeof(data));
- return 0;
- }
- """
-
- # load BPF program
- b = BPF(text=prog)
- b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
-
- # header
- print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))
-
- # process event
- start = 0
- def print_event(cpu, data, size):
- global start
- event = b["events"].event(data)
- if start == 0:
- start = event.ts
- time_s = (float(event.ts - start)) / 1000000000
- print("%-18.9f %-16s %-6d %s" % (time_s, event.comm, event.pid,
- "Hello, perf_output!"))
-
- # loop with callback to print_event
- b["events"].open_perf_buffer(print_event)
- while 1:
- b.perf_buffer_poll()
程序说明:
b["events"].open_perf_buffer(print_event)
: 将函数与事件流相关联b.perf_buffer_poll()
: 阻塞等事件参考链接 bashreadline例子
1、生成 vmlinux.h文件
sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
2、bashreadline.h 文件
- /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
- /* Copyright (c) 2021 Facebook */
- #ifndef __BASHREADLINE_H
- #define __BASHREADLINE_H
-
- #define MAX_LINE_SIZE 80
-
- struct str_t {
- __u32 pid;
- char str[MAX_LINE_SIZE];
- };
-
- #endif /* __BASHREADLINE_H */
3、bashreadline.bpf.c文件
- /* SPDX-License-Identifier: GPL-2.0 */
- /* Copyright (c) 2021 Facebook */
- #include
- #include
- #include
- #include "bashreadline.h"
-
- #define TASK_COMM_LEN 16
-
- //定义性能映射事件
- struct {
- __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
- __uint(key_size, sizeof(__u32));
- __uint(value_size, sizeof(__u32));
- } events SEC(".maps");
-
- //跟踪点
- SEC("uretprobe/readline")
- int BPF_KRETPROBE(printret, const void *ret) {
- struct str_t data;
- char comm[TASK_COMM_LEN];
- u32 pid;
-
- if (!ret)
- return 0;
- //获取进程名
- bpf_get_current_comm(&comm, sizeof(comm));
- if (comm[0] != 'b' || comm[1] != 'a' || comm[2] != 's' || comm[3] != 'h' || comm[4] != 0 )
- return 0;
-
- //获取进程号取低32位
- pid = bpf_get_current_pid_tgid() >> 32;
- data.pid = pid;
- bpf_probe_read_user_str(&data.str, sizeof(data.str), ret);
- //提交性能事件
- bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data));
-
- return 0;
- };
- //定义许可证
- char LICENSE[] SEC("license") = "GPL";
编译并生成脚手架头文件,使用 clang 和 bpftool 将其编译成 BPF 字节码,然后再生成其脚手架头文件 bashreadline.skel.h
- clang -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -I/usr/include/x86_64-linux-gnu -I. -c bashreadline.bpf.c -o bashreadline.bpf.o
- bpftool gen skeleton bashreadline.bpf.o > bashreadline.skel.h
其中
4、用户态程序开发
- #include
- #include
- #include "bashreadline.h"
- #include "bashreadline.skel.h"
-
- int main(int argc, char **argv)
- {
- LIBBPF_OPTS(bpf_object_open_opts, open_opts);
- static const struct argp argp = {
- .options = opts,
- .parser = parse_arg,
- .doc = argp_program_doc,
- };
-
- //定义BPF程序和性能事件缓冲区
- struct bashreadline_bpf *obj = NULL;
- struct perf_buffer *pb = NULL;
- char *readline_so_path;
- off_t func_off;
- int err;
-
- err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
- if (err)
- return err;
-
- if (libreadline_path) {
- readline_so_path = libreadline_path;
- } else if ((readline_so_path = find_readline_so()) == NULL) {
- warn("failed to find readline\n");
- return 1;
- }
-
- libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
- //设置调试输出函数
- libbpf_set_print(libbpf_print_fn);
-
- err = ensure_core_btf(&open_opts);
- if (err) {
- warn("failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err));
- goto cleanup;
- }
- //初始化BPF程序
- obj = bashreadline_bpf__open_opts(&open_opts);
- if (!obj) {
- warn("failed to open BPF object\n");
- goto cleanup;
- }
- //加载BPF字节码
- err = bashreadline_bpf__load(obj);
- if (err) {
- warn("failed to load BPF object: %d\n", err);
- goto cleanup;
- }
-
- func_off = get_elf_func_offset(readline_so_path, "readline");
- if (func_off < 0) {
- warn("cound not find readline in %s\n", readline_so_path);
- goto cleanup;
- }
- //挂载BPF字节码到跟踪点
- obj->links.printret = bpf_program__attach_uprobe(obj->progs.printret, true, -1,
- readline_so_path, func_off);
- if (!obj->links.printret) {
- err = -errno;
- warn("failed to attach readline: %d\n", err);
- goto cleanup;
- }
- //配置性能事件回调函数
- pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
- handle_event, handle_lost_events, NULL, NULL);
- if (!pb) {
- err = -errno;
- warn("failed to open perf buffer: %d\n", err);
- goto cleanup;
- }
-
- if (signal(SIGINT, sig_int) == SIG_ERR) {
- warn("can't set signal handler: %s\n", strerror(errno));
- err = 1;
- goto cleanup;
- }
-
- printf("%-9s %-7s %s\n", "TIME", "PID", "COMMAND");
- while (!exiting) {
- //从缓冲区读取数据
- err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
- if (err < 0 && err != -EINTR) {
- warn("error polling perf buffer: %s\n", strerror(-err));
- goto cleanup;
- }
- err = 0;
- }
-
- cleanup:
- if (readline_so_path)
- free(readline_so_path);
- perf_buffer__free(pb);
- bashreadline_bpf__destroy(obj);
- cleanup_core_btf(&open_opts);
-
- return err != 0;
- }
编译为可执行文件
- clang -g -O2 -Wall -I . -c bashreadline.c -o bashreadline.o
- clang -Wall -O2 -g bashreadline.o -static -lbpf -lelf -lz -o bashreadline
总的Makefile文件
- APPS = bashreadline
-
- .PHONY: all
- all: $(APPS)
-
- $(APPS):
- clang -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -I/usr/include/x86_64-linux-gnu -I. -c $@.bpf.c -o $@.bpf.o
- bpftool gen skeleton $@.bpf.o > $@.skel.h
- clang -g -O2 -Wall -I . -c $@.c -o $@.o
- clang -Wall -O2 -g $@.o -static -lbpf -lelf -lz -o $@
-
- vmlinux:
- $(bpftool) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
5、测试
- root@root# ./bashreadline
- TIME PID COMMAND
- 12:08:51 2897 ls
- 12:08:54 2897 cd
参考
bcc/libbpf-tools at master · iovisor/bcc · GitHub
bpftrace/reference_guide.md at master · iovisor/bpftrace · GitHub