bpftrace 通过高度抽象的封装来使用 eBPF,大多数功能只需要寥寥几笔就可以运行起来,可以很快让我们搞清楚 eBPF 是什么样的,而暂时不关心 eBPF 复杂的内部机理。由于 bpftrace 深受 AWK 和 c 的影响,bpftrace 使用起来于 AWK 非常相似,那些内核 hook 注入点几乎可以按普通字符串匹配来理解,非常容易上手。
前面我们介绍了如何部署bpftrace工具,并且介绍了如何运行bpftrace脚本,这篇文章将介绍bpftrace脚本的语法。
probe:filter: {
actions;
}
tracepoint:timer:tick_stop
kprobe:do_sys_open
BEGIN{
print("hello world.\n");
}
END {
print("bye world.\n");
}
ebpf支持的probe:hardware,iter,kfunc,kprobe,software,tracepoint,uprobe。
youyeetoo@youyeetoo:~$ bpftrace -lv tracepoint:raw_syscalls:sys_exit
tracepoint:raw_syscalls:sys_exit
long id
long ret
youyeetoo@youyeetoo:~$
youyeetoo@youyeetoo:~$ cat /sys/kernel/debug/tracing/events/raw_syscalls/sys_exit/format
name: sys_exit
ID: 348
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:long id; offset:8; size:8; signed:1;
field:long ret; offset:16; size:8; signed:1;
print fmt: "NR %ld = %ld", REC->id, REC->ret
youyeetoo@youyeetoo:~$
无论 Dynamic tracing 或者 Static tracing,它们的目的都是监听特定函数调用事件,这些函数即可以在内核中,也可以在用户态的应用或者 lib 中。获知这些函数调用时的参数、返回值就已经实现了开发者大半目标。除此之外,bpfstrace 还内置了一些变量,用户访获得探测对象自身信息。这些变量在 bpftrace 中直接访问即可,如下:
2, ...,
#:bpftrace 程序自身的位置参数
kprobe:do_nanosleep {
@start[tid] = nsecs;
}
kretprobe:do_nanosleep /@start[tid] != 0/ {
printf("slept for %d ms\n", (nsecs - @start[tid]) / 1000000);
delete(@start[tid]);
}
youyeetoo@youyeetoo:~$ bpftrace bpf_test.bt
Attaching 2 probes...
slept for 0 ms
slept for 0 ms
slept for 0 ms
slept for 0 ms
slept for 0 ms
slept for 0 ms
slept for 0 ms
$name, 只在当前action中有效,超出action的{}不具备记忆能力。
bpftrace无法自定义函数,但提供了约36个内置函数,可以在bpftrace脚本的任意位置调用它们。完整的列表可以参考官方文档:(https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md)。
bpftrace的函数非常有限,原因是bpftrace脚本会编译为bytecode,交由内核中的eBPF VM执行,出于安全和效率考虑,eBPF VM不能允许用户执行任意函数,仅允许执行限定的函数,或缺有限的数据。
BEGIN{
print("hello world.\n");
}
END {
print("bye world.\n");
}
「注意:格式化字符结尾不要忘记换行,否则不会自动清空缓冲区到标准输出,就看不到输出了。」
youyeetoo@youyeetoo:~$ bpftrace -e 'interval:s:1 {time("%Y %H:%M:%S\n");}'
Attaching 1 probe...
2023 16:35:30
2023 16:35:31
2023 16:35:32
^C
youyeetoo@youyeetoo:~$ bpftrace --unsafe -e 'kprobe:do_nanosleep { system("ps -p %d\n", pid); }'
Attaching 1 probe...
PID TTY TIME CMD
933 ? 00:00:00 cron
^C
youyeetoo@youyeetoo:~$ bpftrace -e 'uprobe:bash:readline { printf("%s\n", ustack(perf, 3)); }'
stdin:1:1-21: WARNING: attaching to uprobe target file '/usr/bin/bash' but matched 2 binaries
uprobe:bash:readline { printf("%s\n", ustack(perf, 3)); }
~~~~~~~~~~~~~~~~~~~~
Attaching 1 probe...
56440bb42690 readline+0 (/usr/bin/bash)
56440bb42690 readline+0 (/usr/bin/bash)
56440bb42690 readline+0 (/usr/bin/bash)
bpftrace 也提供了常见的流程控制语句:① 条件语句 ② 循环语句
if(condition){
statements; //A
} else {
statements; //B
}
if(condition){
statements; //A
}
statements; //B
if(condition){
statements; //A
} else if(condition){
statements; //B
} else if(condition){
statements; //C
} else {
statements; //D
}
BEGIN{
$num = $1;
if($num >= 10){
$result = "A";
} else if($num >= 5){
$result = "B";
} else {
$result = "C"
}
printf("result: %s\n", $result);
exit();
}
youyeetoo@youyeetoo:~$ bpftrace bpf_test.bt 15
Attaching 1 probe...
result: A
youyeetoo@youyeetoo:~$ bpftrace bpf_test.bt 8
Attaching 1 probe...
result: B
youyeetoo@youyeetoo:~$ bpftrace bpf_test.bt 3
Attaching 1 probe...
result: C
youyeetoo@youyeetoo:~$
while(condition){
// do something
}
BEGIN{
$i = 0;
while($i < 10) {
printf("i = %d\n", $i);
$i++
}
exit();
}
youyeetoo@youyeetoo:~$ bpftrace bpf_test.bt
Attaching 1 probe...
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
本文分享自 Rice 嵌入式开发技术分享 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!