• eBPF学习笔记(二)—— eBPF开发工具


    前言

    目前eBPF存在几个开发工具链来协助 eBPF 程序的开发和管理,用于满足用户的不同需求:

    1. bpftrace
    2. BCC库
    3. libbpf C/C++库
    4. eBPF GO库

    本篇我们将对几种常用开发工具进行介绍。

    bpftrace

    bpftrace 是 Linux eBPF 的高级跟踪语言,可用于最新的 Linux 内核。 bpftrace 使用 LLVM 作为后端将脚本编译为 eBPF 字节码,并利用 BCC 与 Linux eBPF 子系统以及现有的 Linux 跟踪功能进行交互:内核动态跟踪 (kprobes)、用户级动态跟踪 (uprobes) 和跟踪点。bpftrace 语言的灵感来自 awk、C 和前身跟踪器,例如 DTrace 和 SystemTap。
    在这里插入图片描述

    安装

    apt-get install bpftrace
    
    • 1

    命令行模式

    1)列出所有插桩点

    bpftrace -l
    
    • 1

    2)添加查询条件,例如只查询xxx类型的插桩点

    bpftrace -l 'xxx:*'
    
    • 1

    例:

    root@ubuntu:~# bpftrace -l "tracepoint:*"
    tracepoint:vsock:virtio_transport_alloc_pkt
    tracepoint:vsock:virtio_transport_recv_pkt
    tracepoint:vb2:vb2_buf_done
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3)查询插桩点声明

    1. 通过调试信息查询
      cat /sys/kernel/debug/tracing/events/syscalls/xxx/format
      
      • 1
      例:查询sys_enter_execve事件的声明
      root@ubuntu:~# cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format
      name: sys_enter_execve
      ID: 716
      format:
      	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
      	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
      	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
      	field:int common_pid;	offset:4;	size:4;	signed:1;
      
      	field:int __syscall_nr;	offset:8;	size:4;	signed:1;
      	field:const char * filename;	offset:16;	size:8;	signed:0;
      	field:const char *const * argv;	offset:24;	size:8;	signed:0;
      	field:const char *const * envp;	offset:32;	size:8;	signed:0;
      
      print fmt: "filename: 0x%08lx, argv: 0x%08lx, envp: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->argv)), ((unsigned long)(REC->envp))
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    2. 通过bpftrace -lv命令查询
      bpftrace -lv xxx
      
      • 1
      例:查询sys_enter_execve事件的声明
      root@ubuntu:~# 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;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    4)跟踪execve系统调用,监控enter状态的命令与参数

    bpftrace -e 'tracepoint:syscalls:sys_enter_execve{printf("pid=%d called=%s filename=%s argv=",pid,comm,str(args->filename));join(args->argv);}'
    
    • 1

    在这里插入图片描述
    5)跟踪execve系统调用,监控exit状态的返回值

    bpftrace -e 'tracepoint:syscalls:sys_exit_execve{printf("pid=%d command=%s ret=%d \n",pid,comm,args->ret);}'
    
    • 1

    在这里插入图片描述
    6)查询内核函数调用栈(ctrl+c后显示结果)

    sudo bpftrace -e 'kprobe:vfs_open {@[kstack(perf)] = count();}'
    
    • 1

    在这里插入图片描述
    7)追踪自定义程序

    /*
    /* Filename: test.c
    /* Compile: gcc -g test.c -o test
    */ 
    
    #include 
    
    int main(int argc, char **argv)
    {
    	printf("hello world!\n");
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    追踪main函数,打印参数个数和第一个参数:

    bpftrace -e 'uprobe:/Work/Project/eBPF/bpftrace/test:main{printf("argc=%d argv[0]=",arg0);join(arg1);}'
    
    • 1

    在这里插入图片描述
    追踪main函数,打印返回值:

    sudo bpftrace -e 'uretprobe:/Work/Project/eBPF/bpftrace/test:main{printf("main:return_value=%d\n",retval);}'
    
    • 1

    在这里插入图片描述
    8)跟踪开源应用bash
    查询bash指令的位置:

    root@ubuntu:~# which bash
    /usr/bin/bash
    
    • 1
    • 2

    查询bash中与readline相关的跟踪点:

    root@ubuntu:~# bpftrace -l "uprobe:/usr/bin/bash:*" | grep readline
    uprobe:/usr/bin/bash:readline_internal_char
    uprobe:/usr/bin/bash:readline_internal_setup
    uprobe:/usr/bin/bash:posix_readline_initialize
    uprobe:/usr/bin/bash:readline
    uprobe:/usr/bin/bash:initialize_readline
    uprobe:/usr/bin/bash:pcomp_set_readline_variables
    uprobe:/usr/bin/bash:readline_internal_teardown
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    跟踪bash应用的readline探测点,列出用户与命令:

    root@ubuntu:~# bpftrace -e 'uretprobe:/usr/bin/bash:readline{printf("User %d executed \"%s\" command\n",uid,str(retval));}'
    Attaching 1 probe...
    User 0 executed "ls" command
    ...
    
    • 1
    • 2
    • 3
    • 4

    9)探测软件事件
    查询探针software支持的事件列表:

    root@ubuntu:~# bpftrace  -l 's:*'
    software:alignment-faults:
    software:bpf-output:
    software:context-switches:
    software:cpu-clock:
    software:cpu-migrations:
    software:dummy:
    software:emulation-faults:
    software:major-faults:
    software:minor-faults:
    software:page-faults:
    software:task-clock:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    探测page-faults:100事件(ctrl+c后显示结果):

    bpftrace -e 'software:page-faults:100{@[comm]=count();}'
    
    • 1

    在这里插入图片描述
    10)探测硬件事件
    查询探针hardware支持的事件列表:

    root@ubuntu:~# bpftrace  -l 'h:*'
    hardware:backend-stalls:
    hardware:branch-instructions:
    hardware:branch-misses:
    hardware:bus-cycles:
    hardware:cache-misses:
    hardware:cache-references:
    hardware:cpu-cycles:
    hardware:frontend-stalls:
    hardware:instructions:
    hardware:ref-cycles:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    尝试统计30秒内cache-missed次数超过1000000的进程,不知道为什么跟踪hardware所有事件都报错:

    bpftrace -e 'hardware:cache-misses:1000000{@[pid]=count();}interval:s:30{exit();}'
    
    • 1

    在这里插入图片描述

    脚本模式

    将命令编写为脚本模式,文件名:script.bt

    #!/usr/bin/env bpftrace
    
    tracepoint:syscalls:sys_enter_execve 
    {
    	printf("pid=%d command=%s called=%s \n",pid,comm,str(args->filename)); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    运行效果:
    在这里插入图片描述

    BCC

    BCC 是一个框架,使用户能够编写带有嵌入其中的 eBPF 程序的 python 程序。 该框架主要针对涉及应用程序和系统分析/跟踪的用例,其中 eBPF 程序用于收集统计信息或生成事件,而用户空间中的对应物收集数据并以人类可读的形式显示。 运行 python 程序将生成 eBPF 字节码并将其加载到内核中。
    在这里插入图片描述

    安装

    可以参考官方提供的安装文档或尝试使用以下命令安装:

    sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
    
    • 1

    测试

    文件名:hello.c

    int hello_world(void *ctx)
    {
        bpf_trace_printk("Hello, World!");
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    文件名:hello.py

    #!/usr/bin/env python3
    
    # 1) import bcc library
    from bcc import BPF
    
    # 2) load BPF program
    b = BPF(src_file="hello.c", cflags=["-Wno-macro-redefined"])
    
    # 3) attach kprobe
    b.attach_kprobe(event="__x64_sys_execve", fn_name="hello_world")
    
    # 4) read and print /sys/kernel/debug/tracing/trace_pipe
    b.trace_print()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行效果:
    在这里插入图片描述

    Map映射

    Map映射能够将BPF数据在内核态与用户态互相映射,部分细节笔者还没有研究得很透彻。

    文件名:open.c

    #include 
    #include 
    
    // 定义数据结构
    struct data_t {
      u32 pid;
      u64 ts;
      char comm[TASK_COMM_LEN];
      char fname[NAME_MAX];
    };
    
    // 定义性能事件映射
    BPF_PERF_OUTPUT(events);
    
    // 定义kprobe处理函数
    int bcc_do_sys_openat2(struct pt_regs *ctx, int dfd, const char __user * filename, struct open_how *how)
    {
      struct data_t data = { };
    
      // 获取PID和时间
      data.pid = bpf_get_current_pid_tgid();
      data.ts = bpf_ktime_get_ns();
    
      // 获取进程名
      if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0)
      {
        bpf_probe_read(&data.fname, sizeof(data.fname), (void *)filename);
      }
    
      // 提交性能事件
      events.perf_submit(ctx, &data, sizeof(data));
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    文件名:open.py

    from bcc import BPF
    
    # 1) 加载eBPF代码
    b = BPF(src_file="open.c", cflags=["-Wno-macro-redefined"])
    b.attach_kprobe(event="do_sys_openat2", fn_name="bcc_do_sys_openat2")
    
    # 2) 输出头
    print("%-18s %-16s %-6s %-16s" % ("TIME(s)", "COMM", "PID", "FILE"))
    
    # 3) 定义性能事件打印函数
    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 %-16s" % (time_s, event.comm, event.pid, event.fname))
    
    # 4) 绑定性能事件映射和输出函数,并从映射中循环读取数据
    b["events"].open_perf_buffer(print_event)
    while 1:
        try:
            b.perf_buffer_poll()
        except KeyboardInterrupt:
            exit()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    运行效果:
    在这里插入图片描述

    Hash映射

    Hash映射能使多个跟踪点之间互相共享数据,例如让sys_enter_execve和sys_exit_execve共享同一段数据。

    文件名:execsnoop.c

    #include 
    #include 
    #include 
    
    // consts for arguments (ensure below stack size limit 512)
    #define ARGSIZE 64
    #define TOTAL_MAX_ARGS 5
    #define FULL_MAX_ARGS_ARR (TOTAL_MAX_ARGS * ARGSIZE)
    #define LAST_ARG (FULL_MAX_ARGS_ARR - ARGSIZE)
    
    // perf event map (sharing data to userspace) and hash map (sharing data between tracepoints)
    struct data_t {
        u32 pid;
        char comm[TASK_COMM_LEN];
        int retval;
        unsigned int args_size;
        char argv[FULL_MAX_ARGS_ARR];
    };
    
    BPF_PERF_OUTPUT(events);
    BPF_HASH(tasks, u32, struct data_t);
    
    // helper function to read string from userspace.
    static int __bpf_read_arg_str(struct data_t *data, const char *ptr)
    {
    	if (data->args_size > LAST_ARG) {
    		return -1;
    	}
    
    	int ret = bpf_probe_read_user_str(&data->argv[data->args_size], ARGSIZE,
    					  (void *)ptr);
    	if (ret > ARGSIZE || ret < 0) {
    		return -1;
    	}
    	// increase the args size. the first tailing '\0' is not counted and hence it
    	// would be overwritten by the next call.
    	data->args_size += (ret - 1);
    
    	return 0;
    }
    
    //定义sys_enter_execve跟踪点处理函数.
    TRACEPOINT_PROBE(syscalls, sys_enter_execve)
    {
        // 变量定义
        unsigned int ret = 0;
        const char **argv = (const char **)(args->argv);
    
        // 获取进程PID和进程名称
        struct data_t data = { };
        u32 pid = bpf_get_current_pid_tgid();
        data.pid = pid;
        bpf_get_current_comm(&data.comm, sizeof(data.comm));
    
        // 获取第一个参数(即可执行文件的名字)
        if (__bpf_read_arg_str(&data, (const char *)argv[0]) < 0) {
            goto out;
        }
    
        // 获取其他参数(限定最多5个)
    	for (int i = 1; i < TOTAL_MAX_ARGS; i++) {
    		if (__bpf_read_arg_str(&data, (const char *)argv[i]) < 0) {
    			goto out;
    		}
    	}
    
     out:
        // 存储到哈希映射中
        tasks.update(&pid, &data);
        return 0;
    }
    
    // 定义sys_exit_execve跟踪点处理函数.
    TRACEPOINT_PROBE(syscalls, sys_exit_execve)
    {
        // 从哈希映射中查询进程基本信息
        u32 pid = bpf_get_current_pid_tgid();
        struct data_t *data = tasks.lookup(&pid);
    
        // 填充返回值并提交到性能事件映射中
        if (data != NULL) {
            data->retval = args->ret;
            events.perf_submit(args, data, sizeof(struct data_t));
    
            // 最后清理进程信息
            tasks.delete(&pid);
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    文件名:execsnoop.py

    from bcc import BPF
    from bcc.utils import printb
    
    # 1) 加载eBPF代码
    b = BPF(src_file="execsnoop.c", cflags=["-Wno-macro-redefined"])
    
    # 2) print header
    print("%-6s %-16s %-3s %s" % ("PID", "COMM", "RET", "ARGS"))
    
    # 3) 定义性能事件打印函数
    def print_event(cpu, data, size):
        # BCC自动根据"struct data_t"生成数据结构
        event = b["events"].event(data)
        printb(b"%-6d %-16s %-3d %-16s" % (event.pid, event.comm, event.retval, event.argv))
    
    # 4) 绑定性能事件映射和输出函数,并从映射中循环读取数据
    b["events"].open_perf_buffer(print_event)
    while 1:
        try:
            b.perf_buffer_poll()
        except KeyboardInterrupt:
            exit()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    运行效果:
    在这里插入图片描述

    libbpf C/C++库

    libbpf 库是一个基于 C/C++ 的通用 eBPF 库,它有助于将由 clang/LLVM 编译器生成的 eBPF 目标文件加载到内核中,并且通常通过提供易于使用的库 API 来抽象与 BPF 系统调用的交互应用程序。
    在这里插入图片描述

    安装

    方法一:使用apt安装相关模块

    apt-get install libbpf-dev
    
    • 1

    方法二:使用源码构建静态libbpf.a和共享libbpf.so,尽量选择与当前系统内核版本相近的源码版本

    cd src
    make
    
    • 1
    • 2

    CO-RE

    CO-RE (Compile Once – Run Everywhere)中文译为”一次编译,到处运行“。使用libbpf编译的程序,只要目标服务器内核支持BPF,即可将程序在不依赖LLVM、CLANG及内核头文件的情况下部署到目标服务器。

    Libbpf+BPF CO-RE的理念是,BPF程序与任何"正常"用户空间程序没有太大区别:它们应该汇编成小型二进制文件,然后以紧凑的形式进行部署,以瞄准主机。Libbpf 扮演 BPF 程序装载机的角色,执行平凡的设置工作(重定位、加载和验证 BPF 程序、创建 BPF map、连接到 BPF 挂钩等),让开发人员只担心 BPF 程序的正确性和性能。这种方法将开销保持在最低水平,消除沉重的依赖关系,使整体开发人员体验更加愉快。

    测试

    1)生成内核头文件

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

    2)开发内核态程序,生成脚手架文件(包含各项声明与BPF字节码,解除环境依赖)
    文件名:execsnoop.h

    #ifndef __EXECSNOOP_H
    #define __EXECSNOOP_H
    
    #define ARGSIZE  128
    #define TASK_COMM_LEN 16
    #define TOTAL_MAX_ARGS 60
    #define FULL_MAX_ARGS_ARR (TOTAL_MAX_ARGS * ARGSIZE)
    #define BASE_EVENT_SIZE (size_t)(&((struct event*)0)->args)
    #define EVENT_SIZE(e) (BASE_EVENT_SIZE + e->args_size)
    #define LAST_ARG (FULL_MAX_ARGS_ARR - ARGSIZE)
    
    struct event {
    	char comm[TASK_COMM_LEN];
    	pid_t pid;
    	int retval;
    	int args_count;
    	unsigned int args_size;
    	char args[FULL_MAX_ARGS_ARR];
    };
    
    #endif				/* __EXECSNOOP_H */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    文件名:execsnoop.bpf.c

    #include "vmlinux.h"
    #include "execsnoop.h"
    #include 
    
    static const struct event empty_event = { };
    
    // 定义哈希映射
    struct {
    	__uint(type, BPF_MAP_TYPE_HASH);
    	__uint(max_entries, 10240);
    	__type(key, pid_t);
    	__type(value, struct event);
    } execs SEC(".maps");
    
    // 定义性能事件映射
    struct {
    	__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
    	__uint(key_size, sizeof(u32));
    	__uint(value_size, sizeof(u32));
    }events SEC(".maps");
    
    // 定义sys_enter_execve跟踪点函数
    SEC("tracepoint/syscalls/sys_enter_execve")
    int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter
    					   *ctx)
    {
    	struct event *event;
    	const char **args = (const char **)(ctx->args[1]);
    	const char *argp;
    
    	// 查询PID
    	u64 id = bpf_get_current_pid_tgid();
    	pid_t pid = (pid_t) id;
    
    	// 保存一个空的event到哈希映射中
    	if (bpf_map_update_elem(&execs, &pid, &empty_event, BPF_NOEXIST)) {
    		return 0;
    	}
    	event = bpf_map_lookup_elem(&execs, &pid);
    	if (!event) {
    		return 0;
    	}
    	// 初始化event变量
    	event->pid = pid;
    	event->args_count = 0;
    	event->args_size = 0;
    
    	// 查询第一个参数
    	unsigned int ret = bpf_probe_read_user_str(event->args, ARGSIZE,
    						   (const char *)ctx->args[0]);
    	if (ret <= ARGSIZE) {
    		event->args_size += ret;
    	} else {
    		/* write an empty string */
    		event->args[0] = '\0';
    		event->args_size++;
    	}
    
    	// 查询其他参数,使用pragma unroll控制循环次数
    	event->args_count++;
    #pragma unroll
    	for (int i = 1; i < TOTAL_MAX_ARGS; i++) {
    		bpf_probe_read_user(&argp, sizeof(argp), &args[i]);
    		if (!argp)
    			return 0;
    
    		if (event->args_size > LAST_ARG)
    			return 0;
    
    		ret =
    		    bpf_probe_read_user_str(&event->args[event->args_size],
    					    ARGSIZE, argp);
    		if (ret > ARGSIZE)
    			return 0;
    
    		event->args_count++;
    		event->args_size += ret;
    	}
    
    	// 再尝试一次,确认是否还有未读取的参数
    	bpf_probe_read_user(&argp, sizeof(argp), &args[TOTAL_MAX_ARGS]);
    	if (!argp)
    		return 0;
    
    	// 如果还有未读取参数,则增加参数数量(用于输出"...")
    	event->args_count++;
    
    	return 0;
    }
    
    // 定义sys_exit_execve跟踪点函数
    SEC("tracepoint/syscalls/sys_exit_execve")
    int tracepoint__syscalls__sys_exit_execve(struct trace_event_raw_sys_exit *ctx)
    {
    	u64 id;
    	pid_t pid;
    	int ret;
    	struct event *event;
    
    	// 从哈希映射中查询进程基本信息
    	id = bpf_get_current_pid_tgid();
    	pid = (pid_t) id;
    	event = bpf_map_lookup_elem(&execs, &pid);
    	if (!event)
    		return 0;
    
    	// 更新返回值和进程名称
    	ret = ctx->ret;
    	event->retval = ret;
    	bpf_get_current_comm(&event->comm, sizeof(event->comm));
    
    	// 提交性能事件
    	size_t len = EVENT_SIZE(event);
    	if (len <= sizeof(*event))
    		bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, event,
    				      len);
    
    	// 清理哈希映射
    	bpf_map_delete_elem(&execs, &pid);
    	return 0;
    }
    // 定义许可证(前述的BCC默认使用GPL)
    char LICENSE[] SEC("license") = "GPL";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123

    编译命令:

    # clang -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -I/usr/include/x86_64-linux-gnu -I. -c execsnoop.bpf.c -o execsnoop.bpf.o
    # bpftool gen skeleton execsnoop.bpf.o > execsnoop.skel.h
    
    • 1
    • 2

    3)开发用户态程序,处理收集的信息
    文件名:execsnoop.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "execsnoop.h"
    #include "execsnoop.skel.h"
    
    // libbpf错误和调试信息回调的处理程序。
    static int libbpf_print_fn(enum libbpf_print_level level, const char *format,
    			   va_list args)
    {
    #ifdef DEBUGBPF
    	return vfprintf(stderr, format, args);
    #else
    	return 0;
    #endif
    }
    
    // 丢失事件的处理程序。
    void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
    {
    	fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu);
    }
    
    // 打印参数(替换'\0'为空格)
    static void print_args(const struct event *e)
    {
    	int args_counter = 0;
    
    	for (int i = 0; i < e->args_size && args_counter < e->args_count; i++) {
    		char c = e->args[i];
    		if (c == '\0') {
    			args_counter++;
    			putchar(' ');
    		} else {
    			putchar(c);
    		}
    	}
    	if (e->args_count > TOTAL_MAX_ARGS) {
    		fputs(" ...", stdout);
    	}
    }
    
    // 性能事件回调函数(向终端中打印进程名、PID、返回值以及参数)
    void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
    {
    	const struct event *e = data;
    	printf("%-16s %-6d %3d ", e->comm, e->pid, e->retval);
    	print_args(e);
    	putchar('\n');
    }
    
    // Bump RLIMIT_MEMLOCK,允许BPF子系统做任何它需要的事情。  
    static void bump_memlock_rlimit(void)
    {
    	struct rlimit rlim_new = {
    		.rlim_cur = RLIM_INFINITY,
    		.rlim_max = RLIM_INFINITY,
    	};
    
    	if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) {
    		fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit!\n");
    		exit(1);
    	}
    }
    
    int main(int argc, char **argv)
    {
    	struct execsnoop_bpf *skel;
    	struct perf_buffer_opts pb_opts;
    	struct perf_buffer *pb = NULL;
    	int err;
    
    	// 1. 设置调试输出函数,libbpf发生错误会回调libbpf_print_fn
    	libbpf_set_print(libbpf_print_fn);
    
    	// 2. 增大进程限制的内存,默认值通常太小,不足以存入BPF映射的内容
    	bump_memlock_rlimit();
    
    	// 3. 打开BPF程序
    	skel = execsnoop_bpf__open();
    	if (!skel) {
    		fprintf(stderr, "Failed to open BPF skeleton\n");
    		return 1;
    	}
    
    	// 4. 加载BPF字节码
    	err = execsnoop_bpf__load(skel);
    	if (err) {
    		fprintf(stderr, "Failed to load and verify BPF skeleton\n");
    		goto cleanup;
    	}
    
    	// 5. 挂载BPF字节码到跟踪点
    	err = execsnoop_bpf__attach(skel);
    	if (err) {
    		fprintf(stderr, "Failed to attach BPF skeleton\n");
    		goto cleanup;
    	}
    
    	// 6. 配置性能事件回调函数
    	pb_opts.sample_cb = handle_event;
    	pb_opts.lost_cb = handle_lost_events;
    	pb = perf_buffer__new(bpf_map__fd(skel->maps.events), 64, &pb_opts);
    	err = libbpf_get_error(pb);
    	if (err) {
    		pb = NULL;
    		fprintf(stderr, "failed to open perf buffer: %d\n", err);
    		goto cleanup;
    	}
    
    	printf("%-16s %-6s %3s %s\n", "COMM", "PID", "RET", "ARGS");
    
    	// 7. 从缓冲区中循环读取数据
    	while ((err = perf_buffer__poll(pb, 100)) >= 0) ;
    	printf("Error polling perf buffer: %d\n", err);
    
     cleanup:
    	perf_buffer__free(pb);
    	execsnoop_bpf__destroy(skel);
    	return err != 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124

    编译命令:

    # clang -g -O2 -Wall -I . -c execsnoop.c -o execsnoop.o
    # clang -Wall -O2 -g execsnoop.o -static -lbpf -lelf -lz -o execsnoop
    
    • 1
    • 2

    运行效果:
    在这里插入图片描述

    libbpf-bootstrap

    libbpf 在使用上并不是很直观,所以 eBPF 维护者开发了一个脚手架项目 libbpf-bootstrap,它结合了 BPF 社区的最佳开发实践,为初学者提供了一个简单易用的上手框架。

    安装依赖

    apt install clang libelf1 libelf-dev zlib1g-dev
    
    • 1

    源码编译

    # checkout libbpf-bootstrap
    git clone https://github.com/libbpf/libbpf-bootstrap
    
    # update submodules
    cd libbpf-bootstrap
    git submodule update --init --recursive
    
    # build existing samples
    cd example/c
    make
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    测试

    文件名:hello.bpf.c

    #include 
    #include 
    
    SEC("tracepoint/syscalls/sys_enter_execve")
    int handle_tp(void *ctx)
    {
        int pid = bpf_get_current_pid_tgid()>> 32;
        char fmt[] = "BPF triggered from PID %d.\n";
        bpf_trace_printk(fmt, sizeof(fmt), pid);
        return 0;
    }
    
    char LICENSE[] SEC("license") = "Dual BSD/GPL";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    文件名:hello.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "hello.skel.h"
    
    #define DEBUGFS "/sys/kernel/debug/tracing/"
    
    /* logging function used for debugging */
    static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
    {
    #ifdef DEBUGBPF
        return vfprintf(stderr, format, args);
    #else
        return 0;
    #endif
    }
    
    /* read trace logs from debug fs */
    void read_trace_pipe(void)
    {
        int trace_fd;
    
        trace_fd = open(DEBUGFS "trace_pipe", O_RDONLY, 0);
        if (trace_fd < 0)
            return;
    
        while (1) {
            static char buf[4096];
            ssize_t sz;
    
            sz = read(trace_fd, buf, sizeof(buf) - 1);
            if (sz> 0) {
                buf[sz] = 0;
                puts(buf);
            }
        }
    }
    
    /* set rlimit (required for every app) */
    static void bump_memlock_rlimit(void)
    {
        struct rlimit rlim_new = {
            .rlim_cur	= RLIM_INFINITY,
            .rlim_max	= RLIM_INFINITY,
        };
    
        if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) {
            fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit!\n");
            exit(1);
        }
    }
    
    int main(int argc, char **argv)
    {
        struct hello_bpf *skel;
        int err;
    
        /* Set up libbpf errors and debug info callback */
        libbpf_set_print(libbpf_print_fn);
    
        /* Bump RLIMIT_MEMLOCK to allow BPF sub-system to do anything */
        bump_memlock_rlimit();
    
        /* Open BPF application */
        skel = hello_bpf__open();
        if (!skel) {
            fprintf(stderr, "Failed to open BPF skeleton\n");
            return 1;
        }
    
        /* Load & verify BPF programs */
        err = hello_bpf__load(skel);
        if (err) {
            fprintf(stderr, "Failed to load and verify BPF skeleton\n");
            goto cleanup;
        }
    
        /* Attach tracepoint handler */
        err = hello_bpf__attach(skel);
        if (err) {
            fprintf(stderr, "Failed to attach BPF skeleton\n");
            goto cleanup;
        }
    
        printf("Hello BPF started, hit Ctrl+C to stop!\n");
    
        read_trace_pipe();
    
    cleanup:
        hello_bpf__destroy(skel);
        return -err;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    更新Makefile中待编译的APP列表:


    APPS = minimal minimal_legacy bootstrap uprobe kprobe fentry usdt sockfilter tc hello

    编译:
    在这里插入图片描述
    运行效果:
    在这里插入图片描述

    参考资料

  • 相关阅读:
    springboot全局异常处理详解
    Java_继承
    电路背诵结论
    janus videoroom之媒体录制
    第7章-使用统计方法进行变量有效性测试-7.4.1-简单线性回归
    2022 ICPC Gran Premio de Mexico 1ra Fecha(一)
    JS 数组的各个方法汇总
    No qualifying bean of type 问题
    华为Mate 60系列安装谷歌服务框架,安装Play商店,Google
    .NET Conf 2023 Chengdu - 成都站圆满结束!
  • 原文地址:https://blog.csdn.net/qq_41988448/article/details/127813132