eBPF学习记录(二)使用bpftrace开发eBPF程序

上一节我们已经对eBPF有了一定的了解,现在我们先来看看bpftrace:

bpftrace 在 eBPF 和 BCC 之上构建了一个简化的跟踪语言,通过简单的几行脚本,就可以实现复杂的跟踪功能。并且,多行的跟踪指令也可以放到脚本文件中执行。因此,在编写简单的 eBPF 程序,特别是编写的 eBPF 程序用于临时的调试和排错时,你可以考虑直接使用 bpftrace ,而不需要用 C 或 Python 去开发一个复杂的程序。现在我们试试使用bpftrace吧。有问题可以查看官方文档。

  1. 安装 bpftrace:
sudo apt-get install bpftrace
  1. 查询所有内核插桩和跟踪点
sudo bpftrace -l
  1. 使用通配符查询内核插桩和跟踪点
    我们可以跟踪系统调用 execve 来跟踪短时进程,所以使用使用通配符execve查询
jian@ubuntu:~/linux-source-5.13.0$ sudo bpftrace -l '*execve*'
tracepoint:syscalls:sys_enter_execve
tracepoint:syscalls:sys_exit_execve
tracepoint:syscalls:sys_enter_execveat
tracepoint:syscalls:sys_exit_execveat
kprobe:audit_log_execve_info
kprobe:bprm_execve.part.0
kprobe:bprm_execve
kprobe:do_execveat_common.isra.0
kprobe:__ia32_compat_sys_execveat
kprobe:__ia32_compat_sys_execve
kprobe:__ia32_sys_execveat
kprobe:__ia32_sys_execve
kprobe:__x32_compat_sys_execveat
kprobe:__x64_sys_execveat
kprobe:__x32_compat_sys_execve
kprobe:__x64_sys_execve
kprobe:kernel_execve
kfunc:__ia32_sys_execveat
kfunc:__x64_sys_execveat
kfunc:audit_log_execve_info
kfunc:__x32_compat_sys_execveat
kfunc:__ia32_compat_sys_execveat
kfunc:__x32_compat_sys_execve
kfunc:__ia32_compat_sys_execve
kfunc:__ia32_sys_execve
kfunc:__x64_sys_execve
kfunc:kernel_execve
kfunc:bprm_execve

可以发现这些函数可以分为 tracepoint(内核静态探针),kprobe(内核态动态函数探针)和 kfunc (基于BPF的内核态动态函数探针)三类。kfunc,kprobe属于不稳定接口,而tracepoint则是稳定接口。因而,优先选择更稳定的跟踪点,以保证 eBPF 程序的可移植性(即在不同版本的内核中都可以正常执行)。

  1. 查询函数的入口参数或返回值
    我们刚刚查询到系统调用 execve , 他的入口参数是通过系统调用sys_enter_execve查询的,他的返回值是通过系统调用sys_exit_execve查询的,也就是说系统调用sys_enter_execve会返回execve的入口参数,系统调用sys_exit_execve会返回execve的返回值,我们也查询一下。
jian@ubuntu:~$ sudo bpftrace -lv tracepoint:syscalls:sys_enter_execve
BTF: using data from /sys/kernel/btf/vmlinux
tracepoint:syscalls:sys_enter_execve
    int __syscall_nr;
    const char * filename;
    const char *const * argv;
    const char *const * envp;
jian@ubuntu:~$ sudo bpftrace -lv tracepoint:syscalls:sys_exit_execve
BTF: using data from /sys/kernel/btf/vmlinux
tracepoint:syscalls:sys_exit_execve
    int __syscall_nr;
    long ret;
  1. 使用 bpftrace 来跟踪系统调用execve查看入口参数
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {join(args->argv);}'

bpftrace -e :表示直接从后面的字符串参数中读入 bpftrace 程序
tracepoint:syscalls:sys_enter_execve:表示跟踪的函数(如果多个函数可以用逗号分隔),其后的中括号表示跟踪点的处理函数;
join(args->argv) :表示直接读取argv参数,打印到终端。

  1. 使用 bpftrace 来跟踪系统调用execve查看返回值
sudo bpftrace -e 'tracepoint:syscalls:sys_exit_execve { printf("%-6d %-8s ret=%d \n",pid,comm,args->ret); }'

printf() 表示向终端中打印字符串,其用法类似于 C 语言中的 printf() 函数。
pid 和 comm 是 bpftrace 内置的变量,分别表示进程 PID 和进程名称。
args->ret:直接读取ret参数,打印到终端

  1. 使用 bpftrace 来跟踪多个系统调用
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve,tracepoint:syscalls:sys_enter_execveat {join(args->argv);}'

tracepoint:syscalls:sys_enter_execve,tracepoint:syscalls:sys_enter_execveat:跟踪的函数如果有多个可以用逗号分隔。

  1. 使用 bpftrace 来跟踪的同时输出其他信息
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%-6d %s called %s\n",pid,comm,str(args->filename)); }'

str(args->filename) 表示输出args->filename指针的数据。

  1. 写成脚本的形式跟踪
    新建文件jiaoben.bt如下:
#!/usr/bin/env bpftrace

tracepoint:syscalls:sys_enter_execve 
{
	printf("%-6d %s called %s\n",pid,comm,str(args->filename)); 
}

执行下面的命令就可以达到上一步的效果,

sudo ./jiaoben.bt
  1. 使用内核动态探针 kprobe监控内核函数vfs_open
    我们需要查看 vfs_open的跟踪点和入口参数并且监控他
jian@ubuntu:~/Desktop/bpf$ sudo bpftrace -l "*vfs_open*"
kprobe:vfs_open
kfunc:vfs_open

jian@ubuntu:~/Desktop/bpf$ sudo bpftrace -lv kfunc:vfs_open
BTF: using data from /sys/kernel/btf/vmlinux
kfunc:vfs_open
    const struct path * path;
    struct file * file;
    int retval;

jian@ubuntu:~/Desktop/bpf$ sudo bpftrace --include linux/path.h --include linux/dcache.h -e 'kprobe:vfs_open {printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name)); } '

理论上可以通过kretprobe探针查看查看vfs_open的返回值,但是不知道为什么我的电脑不支持,应该是内核需要配置一下:

sudo bpftrace -l "*kretprobe:*"

使用下面的命令还可以查看内核函数的调用栈(在ctrl+c后才能看到):

sudo bpftrace -e 'kprobe:vfs_open { @[kstack(perf)] = count(); }'
  1. 使用用户态函数探针 uprobe和uprobe检测用户态的程序test
    编写test.c文件:
// cat test.c 
#include 

int main(int argc, char **argv)
{
	printf("hello world!\n");
	return 0;
}

// gcc -g test.c -o test

编译成test应用程序:

gcc test.c -o test

使用bpftrace跟踪test程序的main函数,输出参数个数和第1个参数:

sudo bpftrace -e 'uprobe:/home/jian/Desktop/bpf/third/test:main {printf("count:%d  ",arg0);join(arg1)}'

使用bpftrace跟踪test程序的main函数,输出返回值:

sudo bpftrace -e 'uretprobe:/home/jian/Desktop/bpf/third/test:main { printf("test main return %d\n", retval); }'
  1. 使用用户态函数探针 uretprobe检测开源应用程序bash
jian@ubuntu:~$ which bash
/usr/bin/bash
jian@ubuntu:~$ sudo bpftrace -l "uprobe:/usr/bin/bash:*"  |grep readline
uprobe:/usr/bin/bash:initialize_readline
uprobe:/usr/bin/bash:pcomp_set_readline_variables
uprobe:/usr/bin/bash:posix_readline_initialize
uprobe:/usr/bin/bash:readline
uprobe:/usr/bin/bash:readline_internal_char
uprobe:/usr/bin/bash:readline_internal_setup
uprobe:/usr/bin/bash:readline_internal_teardown

jian@ubuntu:~$ sudo bpftrace -e 'uretprobe:/usr/bin/bash:readline { printf("User %d executed \"%s\" command\n", uid, str(retval)); }'
  1. 使用探针Software检测软件事件
jian@ubuntu:~/Desktop$ sudo 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:

jian@ubuntu:~/Desktop$ sudo bpftrace -e 'software:page-faults:100 { @[comm] = count(); }'
  1. 使用探针hardware检测硬件事件
    查看bpftrace支持的硬件事件:
jian@ubuntu:~/Desktop$ sudo bpftrace  -l 'h:*'
[sudo] password for jian: 
BTF: using data from /sys/kernel/btf/vmlinux
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:

你可能感兴趣的:(学习,eBPF)