• eBPF理解 (四)


    目录

    一、bpftrace

    二、BCC方法

    三、libbpf方法

    总结:


    一、bpftrace

    bpftrace是linux增强的eBPF的高级跟踪语言。其使用LLVM作为后端,将脚本编译为BPF字节码,并利用BCC与linux BPF进行交互,以实现linux的跟踪功能:kprobe,uprobe和tracepoint。使用方式可参考bpftrace 教程

    使用bpftrace查询跟踪点 -l

    查询execve跟踪点

    1. //查询所有内核插装和跟踪点
    2. # bpftrace -l
    3. //查询execve的跟踪点
    4. # bpftrace -l '*execve*'
    5. hardware:*execve*:
    6. kfunc:__ia32_compat_sys_execve
    7. kfunc:__ia32_compat_sys_execveat
    8. kfunc:__ia32_sys_execve
    9. kfunc:__ia32_sys_execveat
    10. kfunc:__x64_compat_sys_execve
    11. kfunc:__x64_compat_sys_execveat
    12. kfunc:__x64_sys_execve
    13. kfunc:__x64_sys_execveat
    14. kfunc:audit_log_execve_info
    15. kfunc:bprm_execve
    16. kfunc:kernel_execve
    17. kprobe:__ia32_compat_sys_execve
    18. kprobe:__ia32_compat_sys_execveat
    19. kprobe:__ia32_sys_execve
    20. kprobe:__ia32_sys_execveat
    21. kprobe:__x64_compat_sys_execve
    22. kprobe:__x64_compat_sys_execveat
    23. kprobe:__x64_sys_execve
    24. kprobe:__x64_sys_execveat
    25. kprobe:audit_log_execve_info
    26. kprobe:bprm_execve
    27. kprobe:do_execveat_common.isra.0
    28. kprobe:kernel_execve
    29. software:*execve*:
    30. tracepoint:syscalls:sys_enter_execve
    31. tracepoint:syscalls:sys_enter_execveat
    32. tracepoint:syscalls:sys_exit_execve
    33. tracepoint:syscalls:sys_exit_execveat

    内核插桩(kprobe)和跟踪点(tracepoint),建议使用tracepoint,更加稳定。

    查询函数的参数 -v

    1. //-v 参数查询 函的参数
    2. #bpftrace -lv tracepoint:syscalls:sys_enter_execve
    3. tracepoint:syscalls:sys_enter_execve
    4. int __syscall_nr
    5. const char * filename
    6. const char *const * argv
    7. const char *const * envp
    8. #bpftrace -lv tracepoint:syscalls:sys_exit_execve
    9. tracepoint:syscalls:sys_exit_execve
    10. int __syscall_nr
    11. long ret

    bpftrace 实例

    1. #sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve,tracepoint:syscalls:sys_enter_execveat { printf("%-6d %-8s", pid, comm); join(args->argv);}'
    2. Attaching 1 probe...
    3. 18376 bash ls --color=auto
    4. 18377 bash ls --color=auto

    命令行解析

    • bpftrace -e  从后面的字符串参数读入bpftrace程序
    • tracepoint:syscalls:sys_enter_execve 跟踪点处理函数
    • printf() 输出,pid 进程[PID,comm进程名称是bpftrace的内置变量
    • join(args->argv) 把字符串数组格式的参数用空格拼接起来

    再开一个中断,输入ls命令,即可在跟踪的终端上看到输出 ls命令


    二、BCC方法

    github参考

    C语言与Python语言

    1. from bcc import BPF
    2. # define BPF program
    3. prog = """
    4. #include
    5. // define output data structure in C
    6. struct data_t {
    7. u32 pid;
    8. u64 ts;
    9. char comm[TASK_COMM_LEN];
    10. };
    11. BPF_PERF_OUTPUT(events);
    12. int hello(struct pt_regs *ctx) {
    13. struct data_t data = {};
    14. data.pid = bpf_get_current_pid_tgid();
    15. data.ts = bpf_ktime_get_ns();
    16. bpf_get_current_comm(&data.comm, sizeof(data.comm));
    17. events.perf_submit(ctx, &data, sizeof(data));
    18. return 0;
    19. }
    20. """
    21. # load BPF program
    22. b = BPF(text=prog)
    23. b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
    24. # header
    25. print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))
    26. # process event
    27. start = 0
    28. def print_event(cpu, data, size):
    29. global start
    30. event = b["events"].event(data)
    31. if start == 0:
    32. start = event.ts
    33. time_s = (float(event.ts - start)) / 1000000000
    34. print("%-18.9f %-16s %-6d %s" % (time_s, event.comm, event.pid,
    35. "Hello, perf_output!"))
    36. # loop with callback to print_event
    37. b["events"].open_perf_buffer(print_event)
    38. while 1:
    39. b.perf_buffer_poll()

    程序说明:

    • struct data_t 定义C结构体,在内核和用户空间传递数据
    • BPF_PREF_OUTPUT(event)定义了一个性能事件映射event
    • bpf_get_current_pid_tgid 取低32位为进程PID
    • bpf_get_current_comm 获取进程名
    • events.perf_submit()  提交事件以供用户空间通过性能环形缓冲区读取数据
    • b["events"].open_perf_buffer(print_event):  将函数与事件流相关联
    • b.perf_buffer_poll(): 阻塞等事件

    三、libbpf方法

    参考链接 bashreadline例子

    1、生成 vmlinux.h文件

            sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

    2、bashreadline.h 文件

    1. /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
    2. /* Copyright (c) 2021 Facebook */
    3. #ifndef __BASHREADLINE_H
    4. #define __BASHREADLINE_H
    5. #define MAX_LINE_SIZE 80
    6. struct str_t {
    7. __u32 pid;
    8. char str[MAX_LINE_SIZE];
    9. };
    10. #endif /* __BASHREADLINE_H */

    3、bashreadline.bpf.c文件

    1. /* SPDX-License-Identifier: GPL-2.0 */
    2. /* Copyright (c) 2021 Facebook */
    3. #include
    4. #include
    5. #include
    6. #include "bashreadline.h"
    7. #define TASK_COMM_LEN 16
    8. //定义性能映射事件
    9. struct {
    10. __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
    11. __uint(key_size, sizeof(__u32));
    12. __uint(value_size, sizeof(__u32));
    13. } events SEC(".maps");
    14. //跟踪点
    15. SEC("uretprobe/readline")
    16. int BPF_KRETPROBE(printret, const void *ret) {
    17. struct str_t data;
    18. char comm[TASK_COMM_LEN];
    19. u32 pid;
    20. if (!ret)
    21. return 0;
    22. //获取进程名
    23. bpf_get_current_comm(&comm, sizeof(comm));
    24. if (comm[0] != 'b' || comm[1] != 'a' || comm[2] != 's' || comm[3] != 'h' || comm[4] != 0 )
    25. return 0;
    26. //获取进程号取低32位
    27. pid = bpf_get_current_pid_tgid() >> 32;
    28. data.pid = pid;
    29. bpf_probe_read_user_str(&data.str, sizeof(data.str), ret);
    30. //提交性能事件
    31. bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data));
    32. return 0;
    33. };
    34. //定义许可证
    35. char LICENSE[] SEC("license") = "GPL";

    编译并生成脚手架头文件,使用 clang 和 bpftool 将其编译成 BPF 字节码,然后再生成其脚手架头文件 bashreadline.skel.h

    1. 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
    2. bpftool gen skeleton bashreadline.bpf.o > bashreadline.skel.h

    其中

    • -target bpf  表示要生成 BPF 字节码,
    • D__TARGET_ARCH_x86_64  表示目标的体系结构是 x86_64,
    • -I  则是引入头文件路径。

    4、用户态程序开发

    1. #include
    2. #include
    3. #include "bashreadline.h"
    4. #include "bashreadline.skel.h"
    5. int main(int argc, char **argv)
    6. {
    7. LIBBPF_OPTS(bpf_object_open_opts, open_opts);
    8. static const struct argp argp = {
    9. .options = opts,
    10. .parser = parse_arg,
    11. .doc = argp_program_doc,
    12. };
    13. //定义BPF程序和性能事件缓冲区
    14. struct bashreadline_bpf *obj = NULL;
    15. struct perf_buffer *pb = NULL;
    16. char *readline_so_path;
    17. off_t func_off;
    18. int err;
    19. err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
    20. if (err)
    21. return err;
    22. if (libreadline_path) {
    23. readline_so_path = libreadline_path;
    24. } else if ((readline_so_path = find_readline_so()) == NULL) {
    25. warn("failed to find readline\n");
    26. return 1;
    27. }
    28. libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
    29. //设置调试输出函数
    30. libbpf_set_print(libbpf_print_fn);
    31. err = ensure_core_btf(&open_opts);
    32. if (err) {
    33. warn("failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err));
    34. goto cleanup;
    35. }
    36. //初始化BPF程序
    37. obj = bashreadline_bpf__open_opts(&open_opts);
    38. if (!obj) {
    39. warn("failed to open BPF object\n");
    40. goto cleanup;
    41. }
    42. //加载BPF字节码
    43. err = bashreadline_bpf__load(obj);
    44. if (err) {
    45. warn("failed to load BPF object: %d\n", err);
    46. goto cleanup;
    47. }
    48. func_off = get_elf_func_offset(readline_so_path, "readline");
    49. if (func_off < 0) {
    50. warn("cound not find readline in %s\n", readline_so_path);
    51. goto cleanup;
    52. }
    53. //挂载BPF字节码到跟踪点
    54. obj->links.printret = bpf_program__attach_uprobe(obj->progs.printret, true, -1,
    55. readline_so_path, func_off);
    56. if (!obj->links.printret) {
    57. err = -errno;
    58. warn("failed to attach readline: %d\n", err);
    59. goto cleanup;
    60. }
    61. //配置性能事件回调函数
    62. pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
    63. handle_event, handle_lost_events, NULL, NULL);
    64. if (!pb) {
    65. err = -errno;
    66. warn("failed to open perf buffer: %d\n", err);
    67. goto cleanup;
    68. }
    69. if (signal(SIGINT, sig_int) == SIG_ERR) {
    70. warn("can't set signal handler: %s\n", strerror(errno));
    71. err = 1;
    72. goto cleanup;
    73. }
    74. printf("%-9s %-7s %s\n", "TIME", "PID", "COMMAND");
    75. while (!exiting) {
    76. //从缓冲区读取数据
    77. err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
    78. if (err < 0 && err != -EINTR) {
    79. warn("error polling perf buffer: %s\n", strerror(-err));
    80. goto cleanup;
    81. }
    82. err = 0;
    83. }
    84. cleanup:
    85. if (readline_so_path)
    86. free(readline_so_path);
    87. perf_buffer__free(pb);
    88. bashreadline_bpf__destroy(obj);
    89. cleanup_core_btf(&open_opts);
    90. return err != 0;
    91. }

    编译为可执行文件

    1. clang -g -O2 -Wall -I . -c bashreadline.c -o bashreadline.o
    2. clang -Wall -O2 -g bashreadline.o -static -lbpf -lelf -lz -o bashreadline

    总的Makefile文件

    1. APPS = bashreadline
    2. .PHONY: all
    3. all: $(APPS)
    4. $(APPS):
    5. clang -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -I/usr/include/x86_64-linux-gnu -I. -c $@.bpf.c -o $@.bpf.o
    6. bpftool gen skeleton $@.bpf.o > $@.skel.h
    7. clang -g -O2 -Wall -I . -c $@.c -o $@.o
    8. clang -Wall -O2 -g $@.o -static -lbpf -lelf -lz -o $@
    9. vmlinux:
    10. $(bpftool) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

    5、测试

    1. root@root# ./bashreadline
    2. TIME PID COMMAND
    3. 12:08:51 2897 ls
    4. 12:08:54 2897 cd

    总结:

    1. bpftrace用在快速排除和定位系统上,简单的脚本开发并执行;
    2. BCC用在复杂的eBPF开发,使用最广泛;
    3. libbpf从内核中抽离出来的标准库,不在需要每台机器安装LLVM和内核头文件。

    参考

    bcc/libbpf-tools at master · iovisor/bcc · GitHub

    bpftrace/reference_guide.md at master · iovisor/bpftrace · GitHub


  • 相关阅读:
    (题目练习)条件概率+权值线段树+FWT+后缀数组
    【SpringBoot】请求与响应参数 IoC与DI 总结
    spring boot 项目中的application不能执行是什么问题
    < lambda表达式与包装器>——《C++高阶》
    React 的基本使用、脚手架中使用React
    工程伦理--9.5 职业能力
    手敲Cocos简易地图编辑器:人生地图是一本不断修改的书,每一次编辑都是为了克服新的阻挡
    【鸿蒙 HarmonyOS 4.0】TypeScript开发语言
    13.1 Go 反射(Reflection)
    Windows下编译Mediapipe,C++版本
  • 原文地址:https://blog.csdn.net/WANGYONGZIXUE/article/details/126442679