内置变量
无论 Dynamic tracing 或者 Static tracing,它们的目的都是监听特定函数调用事件,这些函数即可以在内核中,也可以在用户态的应用或者 lib 中。获知这些函数调用时的参数、返回值就已经实现了开发者大半目标。除此之外,bpfstrace 还内置了一些变量,用户访获得探测对象自身信息。这些变量在 bpftrace 中直接访问即可。
pid
/tid
Bpftrace 或者说 eBPF 工作在内核,因此这些变量都与内核中进程表示有关。
先说
tid
,内核中线程与进程没做作明确区分,它们都是相同的调度对象task_sruct
。tid
是 thread id 的缩写,由于历史原因,在 task 中的成员是task_sruct.pid
。所以对于 Linux 内核,线程 = 轻量级进程。而
pid
实际上指的是内核中进程组,由 task 中的task_sruct.tgid
成员表示。也就是说,进程 = 线程组。
uid
/gid
执行函数的用户ID、组ID。
nsecs
时间戳,纳秒。
elapsed
ebpfs 启动后的纳秒数。
numaid
NUMA = Non-Uniform Memory Access,与多核 CPU 的内存访问相关。
cpu
当前 cpu 编号,从 0 开始。
comm
进程名称,通常为进程可执行文件名。
kstack
内核栈。
ustack
用户栈。
arg0
,arg1
, ...,argN
函数参数。
sarg0
,sarg1
, ...,sargN
.函数参数(栈中)。
retval
返回值。
func
函数名,可以在可执行文件的符号表中这个函数名。
probe
探针的完整名称,也就是 bpftrace 中 形如 'kprobe:do_nanosleep'
curtask
当前 task struct。
rand
一个无符号 32 位随机数。
cgroup
当前进程的 Cgroup,内核资源组,类似 namespace,docker 等虚拟化技术即基于内核提供的这一基础设施。
cpid
子进程 pid,bpftrace 允许通过
-c
指定一个cmd
运行,然后在该进程上安装 probe。$1
,$2
, ...,$N
,$#
.bpftrace 程序自身的位置参数
这些内置变量可以在 bpftrace 脚本中直接使用。
变量
bpftrace 不仅能将监测到的函数调用信息 print 出来,更进一步,它提供了存储空间,允许在处理 probe 时保存、累计数据。bpftrace 脚本最终被编译为 Bytecode,由 eBPF VM 执行。
不同于传统的 trace 技术仅提供有限的 ring buffer,eBPF 为 VM 提供了无上限的存储 Maps(图中底部中间)。bpftrace 也可以读写 Maps,在多个 probe actions 和 多次事件中保存数据,直到 bpftrace 程序退出或者主动清除。
全局变量 @name
所谓全局有几个方面的意思:
- 对所有 probe actions 可见;
- bpftrace 生命周期内可见;
bpftrace 支持两种变量形式:
- 简单变量,
@name = value
- Map,
@name[key] = value
简单变量就是单纯的变量名和值,很容易理解,你可以在脚本中创建任意数量的简单变量。
Map 非常接近 Python 中的 Dict,或者 C 中的数组,但数组索引可以是数字、字符串等。例如借助内置变量 tid
可以为每个线程记录独立的数值。
# example.bt
kprobe:do_nanosleep {
/*以线程 tid 作为 key,不同线程的 value 不同*/
@start[tid] = nsecs;
}
kretprobe:do_nanosleep /@start[tid] != 0/ {
printf("slept for %d ms\n", (nsecs - @start[tid]) / 1000000);
delete(@start[tid]);
}
# bpftrace example.bt
Attaching 2 probes...
slept for 1000 ms
slept for 1000 ms
slept for 1000 ms
slept for 1009 ms
slept for 2002 ms
临时变量
$name
, 只在当前 action 中有效,超出 action 的 {}
不具备记忆能力。