上一节我们已经对eBPF有了一定的了解,现在我们先来看看bpftrace:
bpftrace 在 eBPF 和 BCC 之上构建了一个简化的跟踪语言,通过简单的几行脚本,就可以实现复杂的跟踪功能。并且,多行的跟踪指令也可以放到脚本文件中执行。因此,在编写简单的 eBPF 程序,特别是编写的 eBPF 程序用于临时的调试和排错时,你可以考虑直接使用 bpftrace ,而不需要用 C 或 Python 去开发一个复杂的程序。现在我们试试使用bpftrace吧。有问题可以查看官方文档。
sudo apt-get install bpftrace
sudo bpftrace -l
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 程序的可移植性(即在不同版本的内核中都可以正常执行)。
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;
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {join(args->argv);}'
bpftrace -e :表示直接从后面的字符串参数中读入 bpftrace 程序
tracepoint:syscalls:sys_enter_execve:表示跟踪的函数(如果多个函数可以用逗号分隔),其后的中括号表示跟踪点的处理函数;
join(args->argv) :表示直接读取argv参数,打印到终端。
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参数,打印到终端
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:跟踪的函数如果有多个可以用逗号分隔。
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%-6d %s called %s\n",pid,comm,str(args->filename)); }'
str(args->filename) 表示输出args->filename指针的数据。
#!/usr/bin/env bpftrace
tracepoint:syscalls:sys_enter_execve
{
printf("%-6d %s called %s\n",pid,comm,str(args->filename));
}
执行下面的命令就可以达到上一步的效果,
sudo ./jiaoben.bt
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(); }'
// 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); }'
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)); }'
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(); }'
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: