https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md
一、说明
bpftrace 参考指南
有关参考摘要,请参阅 README.md 中有关探测器类型的部分以及本指南中的探测器、变量内置和函数内置部分。
这是一个进展中的工作。 如果缺少某些内容,请检查 bpftrace 源码以查看这些文档是否已过时。 如果您发现了什么,请提交问题或拉
取请求以更新这些文档。 此外,请使这些文档尽可能简洁以保持简洁(灵感来自 v7vol2b.pdf 第 106 页的 6 页 awk 摘要)。 将更长
的示例和讨论留给 /docs 中的其他文件、/tools/*_examples.txt 文件或博客文章和其他文章。
二、Terminology
术语 说明 -------------------------------------- BPF Berkeley Packet Filter:一种内核技术,最初是为优化数据包过滤器的处理而开发的(例如,tcpdump 表达式) eBPF Enhanced BPF:一种扩展 BPF 的内核技术,使其可以对任何事件执行更通用的程序,例如下面列出的 bpftrace 程序。 它利用 BPF 沙盒虚拟机环境。 另请注意,eBPF 通常简称为 BPF。 probe 软件或硬件中的检测点,它生成可以执行 bpftrace 程序的事件。 static tracing 代码中的硬编码检测点。 由于这些是固定的,因此它们可以作为稳定 API 的一部分提供并记录在案。 dynamic tracing 也称为动态检测,这是一种可以通过实时修改指令文本来检测任何软件事件(例如函数调用和返回)的技术。 目标软件通常不需要特殊功能来支持动态跟踪,除了 bpftrace 可以读取的符号表。 由于这会检测所有软件文本, 因此它不被认为是稳定的 API,并且目标函数可能不会在其源代码之外记录。 tracepoints 一种用于提供静态跟踪的 Linux 内核技术。 kprobes 一种 Linux 内核技术,用于提供内核功能的动态跟踪。 uprobes 一种 Linux 内核技术,用于提供用户级函数的动态跟踪。 USDT User Statically-Defined Tracing:用户级软件的静态追踪点。 一些应用程序支持 USDT。 BPF map 一个 BPF 内存对象,bpftrace 使用它来创建许多更高级别的对象。 BTF BPF Type Format:编码与 BPF 程序/映射相关的调试信息的元数据格式。
三、Usage
不带选项的 bpftrace 总结了命令行用法:
# bpftrace USAGE: bpftrace [options] filename bpftrace [options] -e 'program' OPTIONS: -B MODE output buffering mode ('line', 'full', or 'none') -d debug info dry run -dd verbose debug info dry run -e 'program' execute this program -h show this help message -I DIR add the specified DIR to the search path for include files. --include FILE adds an implicit #include which is read before the source file is preprocessed. -l [search] list probes -p PID enable USDT probes on PID -c 'CMD' run CMD and enable USDT probes on resulting process //bpftrace和要trace的命令写在同一行 ######### -q keep messages quiet -v verbose messages -k emit a warning when a bpf helper returns an error (except read functions) -kk check all bpf helper functions --version bpftrace version ENVIRONMENT: BPFTRACE_STRLEN [default: 64] bytes on BPF stack per str() BPFTRACE_NO_CPP_DEMANGLE [default: 0] disable C++ symbol demangling BPFTRACE_MAP_KEYS_MAX [default: 4096] max keys in a map BPFTRACE_MAX_PROBES [default: 512] max number of probes bpftrace can attach to BPFTRACE_MAX_BPF_PROGS [default: 512] max number of generated BPF programs BPFTRACE_CACHE_USER_SYMBOLS [default: auto] enable user symbol cache BPFTRACE_VMLINUX [default: none] vmlinux path used for kernel symbol resolution BPFTRACE_BTF [default: none] BTF file EXAMPLES: bpftrace -l '*sleep*' list probes containing "sleep" //Segmentation fault bpftrace -e 'kprobe:do_nanosleep { printf("PID %d sleeping...\n", pid); }' trace processes calling sleep bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' count syscalls by process name
补充:
bpftrace -l 'tracepoint:sched:sched_*' //成功 bpftrace -l 'u:/data/local/tmp/ebpf_test_1:*' //成功
1. Hello World
bpftrace 程序的最基本示例:
# bpftrace -e 'BEGIN { printf("Hello, World!\n"); }' Attaching 1 probe... Hello, World! ^C
该程序的语法将在 Language 部分进行解释。 在本节中,我们将介绍工具的使用。
程序将继续运行,直到按下 Ctrl-C 或调用 exit() 函数。 当程序退出时,将打印所有填充的maps:此行为和maps将在后面的部分中解释。
2. -e 'program': One-Liners
-e 选项允许指定一个程序,并且是一种构造单行代码的方法:
# bpftrace -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s is sleeping.\n", comm); }' Attaching 1 probe... iscsid is sleeping. irqbalance is sleeping. iscsid is sleeping. iscsid is sleeping. [...]
此示例在进程调用 nanosleep 系统调用时打印。 同样,程序的语法将在 Language 部分进行解释。
3. filename: Program Files
保存为文件的程序通常称为脚本,可以通过指定文件名来执行。 我们通常会使用 .bt 文件扩展名,它是 bpftrace 的缩写,但该扩展名会被忽略。
例如,使用 cat -n(枚举输出行)列出 sleepers.bt 文件:
# cat -n sleepers.bt 1 tracepoint:syscalls:sys_enter_nanosleep 2 { 3 printf("%s is sleeping.\n", comm); 4 }
运行 sleepers.bt:
# bpftrace sleepers.bt Attaching 1 probe... iscsid is sleeping. iscsid is sleeping. [...]
它还可以使其可执行文件独立运行。 首先在顶部添加一个解释器行 (#!),其中包含安装的 bpftrace 的路径(/usr/local/bin 是默认值)或 env 的路径(通常只是 /usr/bin/env),然后是 bpftrace (所以它会在你的 $PATH 中找到 bpftrace):
1 #!/usr/local/bin/bpftrace 2 3 tracepoint:syscalls:sys_enter_nanosleep 4 { 5 printf("%s is sleeping.\n", comm); 6 }
然后使其可执行:
# chmod 755 sleepers.bt # ./sleepers.bt Attaching 1 probe... iscsid is sleeping. iscsid is sleeping. [...]
4. -l: Listing Probes
来自 tracepoint 和 kprobe 库的探测可以用 -l 列出。 //实测不生效
# bpftrace -l | more tracepoint:xfs:xfs_attr_list_sf tracepoint:xfs:xfs_attr_list_sf_all tracepoint:xfs:xfs_attr_list_leaf tracepoint:xfs:xfs_attr_list_leaf_end [...] # bpftrace -l | wc -l 46260
其他库动态生成探针,例如 uprobe,并且需要特定的方法来确定可用的探针。 请参阅后面的 Probes 部分。
可以添加搜索词:
# bpftrace -l '*nanosleep*' tracepoint:syscalls:sys_enter_clock_nanosleep tracepoint:syscalls:sys_exit_clock_nanosleep tracepoint:syscalls:sys_enter_nanosleep tracepoint:syscalls:sys_exit_nanosleep kprobe:nanosleep_copyout kprobe:hrtimer_nanosleep [...]
列出跟踪点时的 -v 选项将显示它们的参数以供 args 内置函数使用。 例如:
# bpftrace -lv tracepoint:syscalls:sys_enter_open tracepoint:syscalls:sys_enter_open int __syscall_nr; const char * filename; int flags; umode_t mode;
如果 BTF 可用,也可以列出 struct/union/enum 定义。 例如:
# bpftrace -lv "struct path" struct path { struct vfsmount *mnt; struct dentry *dentry; };
5. -d: Debug Output
-d 选项产生调试输出,但不运行程序。 这主要用于调试 bpftrace 本身的问题。 您还可以使用 -dd 生成更详细的调试输出,这也将打印未优化的 IR。
如果您是 bpftrace 的最终用户,您通常不需要 -d 或 -v 选项,您可以跳到 Language 部分。
# bpftrace -d -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s is sleeping.\n", comm); }' Program tracepoint:syscalls:sys_enter_nanosleep call: printf string: %s is sleeping.\n builtin: comm [...]
输出以 Program 开头,然后是程序的抽象语法树 (AST) 表示。
继续:
[...] %printf_t = type { i64, [16 x i8] } [...] define i64 @"tracepoint:syscalls:sys_enter_nanosleep"(i8*) local_unnamed_addr section "s_tracepoint:syscalls:sys_enter_nanosleep" { entry: %comm = alloca [16 x i8], align 1 %printf_args = alloca %printf_t, align 8 %1 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 %3 = bitcast %printf_t* %printf_args to i8* call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 24, i32 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) call void @llvm.memset.p0i8.i64(i8* nonnull %2, i8 0, i64 16, i32 1, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1, i64 0 call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull %4, i8* nonnull %2, i64 16, i32 1, i1 false) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 24) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 [...]
本节展示 llvm 中间表示 (IR) 程序集,然后将其编译到 BPF 中。
6. -v: Verbose Output
-v 选项在程序运行时打印有关该程序的更多信息:
# bpftrace -v -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s is sleeping.\n", comm); }' Attaching 1 probe... The verifier log: 0: (bf) r6 = r1 1: (b7) r1 = 0 2: (7b) *(u64 *)(r10 -24) = r1 ... 23: (b7) r5 = 24 24: (85) call bpf_perf_event_output#25 25: (b7) r0 = 0 26: (95) exit processed 26 insns (limit 131072), stack depth 40 Attaching tracepoint:syscalls:sys_enter_nanosleep iscsid is sleeping. iscsid is sleeping. [...]
这包括验证器(verifier )日志:然后是来自内核验证器的日志消息。
增加一个Android上的demo,可以打印出id:
# bpftrace -v -e 'kprobe:do_nanosleep { printf("PID %d sleeping...\n", pid); }' INFO: node count: 7 Attaching 1 probe... Program ID: 44 The verifier log: processed 15 insns (limit 1000000) max_states_per_insn 0 total_states 1 peak_states 1 mark_read 0 Attaching kprobe:do_nanosleep PID 1 sleeping... PID 1616 sleeping... PID 2622 sleeping...
7. Preprocessor Options
-I 选项可用于将目录添加到 bpftrace 查找标头文件的目录列表中。 可以定义多次。
# cat program.bt #include <foo.h> BEGIN { @ = FOO } # bpftrace program.bt definitions.h:1:10: fatal error: 'foo.h' file not found # ls /tmp/include foo.h # bpftrace -I /tmp/include program.bt Attaching 1 probe...
--include 选项可用于包含头文件。 可以定义多次。 头文件按照它们定义的顺序包含,并且它们在正在执行的程序中的任何其他包含之前被包含。
# 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)); }' Attaching 1 probe... open path: .com.google.Chrome.ASsbu2 open path: .com.google.Chrome.gimc10 open path: .com.google.Chrome.R1234s
8. Other Options
--version 选项打印 bpftrace 版本。 --no-warnings 选项禁用警告。
# bpftrace --version bpftrace v0.8-90-g585e-dirty
9. Environment Variables
9.1 BPFTRACE_STRLEN
默认值:64,为 str() 返回的字符串在 BPF 堆栈上分配的字节数。如果您希望使用 str() 读取更大的字符串,请将其设置得更大。
请注意 BPF 堆栈很小(512 字节),并且您在 printf() 中再次使用(同时它组成了一个 perf 事件输出缓冲区)。 所以在实践中你只能将它增加到大约 200 字节。
正在讨论支持更大的字符串。
9.3 BPFTRACE_MAP_KEYS_MAX
默认值:4096,这是可以存储在映射中的最大键数。 增加该值将消耗更多内存并增加启动时间。 在某些情况下您会想要:例如,采样堆栈跟踪、记录每个页面的时间戳等。
9.4 BPFTRACE_MAX_PROBES
默认值:512,这是 bpftrace 可以附加的最大探测数。 增加该值将消耗更多内存,增加启动时间并可能导致高性能开销甚至冻结或崩溃系统。
9.5 BPFTRACE_CACHE_USER_SYMBOLS
默认值:如果在系统上启用了 ASLR 且未给出 -c 选项,则为 0; 否则为 1。
默认情况下,bpftrace 仅在禁用 ASLR(地址空间布局随机化)时才缓存符号解析结果。 这是因为每次使用 ASLR 执行时,符号地址都会发生变化。 但是,禁用缓存可能会降低性能。 将此环境变量设置为 1 以强制 bpftrace 进行缓存。 如果只跟踪一个程序的执行,这很好。
9.6 BPFTRACE_VMLINUX
默认值:None,这指定了将 kprobe 附加到偏移量时用于内核符号解析的 vmlinux 路径。 如果未给出此值,bpftrace 会从预定义的位置搜索 vmlinux。 有关详细信息,请参阅 src/attached_probe.cpp:find_vmlinux()。
9.7 BPFTRACE_BTF
默认值:None,BTF 文件的路径。 默认情况下,bpftrace 会搜索多个位置来查找 BTF 文件。 有关详细信息,请参阅 src/btf.cpp。
9.8 BPFTRACE_PERF_RB_PAGES
默认值:64,每个 CPU 为 perf 环形缓冲区分配的页数。 该值必须是 2 的幂。
bpftrace 可能无法足够快地处理环形缓冲区中的事件,这可能导致丢弃大量事件。 提高该值可能很有用,这样可以让更多事件排队。 代价是 bpftrace 将使用更多内存。
9.9 BPFTRACE_MAX_BPF_PROGS
默认值:512,这是 bpftrace 可以生成的 BPF 程序(函数)的最大数量。 此限制的主要目的是防止 bpftrace 挂死,因为生成大量探测会占用大量资源(不应经常发生)。
10. Clang Environment Variables
bpftrace 使用 libclang(Clang 的 C 接口)解析头文件。 因此环境变量可以影响被使用的 clang 工具链。 例如,如果从非默认目录包含头文件,则可以设置 CPATH 或 C_INCLUDE_PATH 环境变量以允许 clang 定位文件。 有关这些环境变量及其用法的更多信息,请参阅clang 文档。
四、Language
1. {...}: Action Blocks
语法:probe[,probe,...] /filter/ { action }
一个 bpftrace 程序可以有多个操作块。 过滤器是可选的。
例子:
# bpftrace -e 'kprobe:do_sys_open { printf("opening: %s\n", str(arg1)); }' Attaching 1 probe... opening: /proc/cpuinfo opening: /proc/stat opening: /proc/diskstats opening: /proc/stat opening: /proc/vmstat [...]
这是 bpftrace 的单行调用。 探测是 kprobe:do_sys_open。 当该探测器“触发”(检测事件发生)时,将执行由 print() 语句组成的操
作。 探测器和操作的说明在以下部分中。
2. /.../: Filtering
语法:/filter/
可以在探测器名称后添加过滤器(也称为谓词)。 探测器仍会触发,但它将跳过操作,除非过滤器为真。
例子:
# bpftrace -e 'kprobe:vfs_read /arg2 < 16/ { printf("small read: %d byte buffer\n", arg2); }' Attaching 1 probe... small read: 8 byte buffer small read: 12 byte buffer ^C # bpftrace -e 'kprobe:vfs_read /comm == "bash"/ { printf("read by %s\n", comm); }' Attaching 1 probe... read by bash ^C
3. //, /*: Comments
语法:
// single-line comment /* * multi-line comment */
这些可以在 bpftrace 脚本中用于注释您的代码。
4. Literals
支持整数、字符和字符串文字。 整数字面值是一个数字序列,带有可选的下划线 (_) 作为字段分隔符。 也支持科学记数法,但仅适用于整数值,因为 BPF 不支持浮点数。
# bpftrace -e 'BEGIN { printf("%lu %lu %lu", 1000000, 1e6, 1_000_000)}' Attaching 1 probe... ^C 1000000 1000000 1000000
字符文字用单引号括起来,例如 'a' 和 '@'。字符串文字用双引号括起来,例如 “一个字符串”。
5. ->: C Struct Navigation
tracepoint 例子:
# bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }' Attaching 1 probe... snmpd /proc/diskstats snmpd /proc/stat snmpd /proc/vmstat [...]
这是从 args 结构返回文件名成员,对于跟踪点探测器,它包含跟踪点参数。 有关此结构的内容,请参阅Static Tracing, Kernel-Level Arguments 部分。
kprobe 示例:
# cat path.bt #include <linux/path.h> #include <linux/dcache.h> kprobe:vfs_open { printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name)); } # bpftrace path.bt Attaching 1 probe... open path: dev open path: if_inet6 open path: retrans_time_ms [...]
这通过短脚本 path.bt 使用 vfs_open() 内核函数的动态跟踪。 需要包含一些内核头文件以理解 path 和 dentry 的结构。
6. struct: Struct Declaration
例子:
// from fs/namei.c: struct nameidata { struct path path; struct qstr last; // [...] };
您可以在需要时定义自己的结构。 在某些情况下,内核结构未在内核头文件包中声明,而是在 bpftrace 工具中手动声明(或部分结构:但需要足够解引用成员变量)。
7. ? :: ternary operators
例子:
# bpftrace -e 'tracepoint:syscalls:sys_exit_read { @error[args->ret < 0 ? - args->ret : 0] = count(); }' Attaching 1 probe... ^C @error[11]: 24 @error[0]: 78 # bpftrace -e 'BEGIN { pid & 1 ? printf("Odd\n") : printf("Even\n"); exit(); }' Attaching 1 probe... Odd
8. if () {...} else {...}: if-else statements
例子:
# bpftrace -e 'tracepoint:syscalls:sys_enter_read { @reads = count(); if (args->count > 1024) { @large = count(); } }' Attaching 1 probe... ^C @large: 72 @reads: 80
9. unroll () {...}: unroll
例子:
# bpftrace -e 'kprobe:do_nanosleep { $i = 1; unroll(5) { printf("i: %d\n", $i); $i = $i + 1; } }' Attaching 1 probe... i: 1 i: 2 i: 3 i: 4 i: 5 ^C
10. ++ and --: increment operators
++ 和 -- 可用于方便地递增或递减映射或变量中的计数器。请注意,如果尚未声明或定义,映射将被隐式声明并初始化为 0。 临时变量必须在使用这些运算符之前进行初始化。
示例 - 变量:
# bpftrace -e 'BEGIN { $x = 0; $x++; $x++; printf("x: %d\n", $x); }' Attaching 1 probe... x: 2 ^C
示例 - map:
bpftrace -e 'k:vfs_read { @++ }' Attaching 1 probe... ^C @: 12807
示例 - 带有key的map:
# bpftrace -e 'k:vfs_read { @[probe]++ }' Attaching 1 probe... ^C @[kprobe:vfs_read]: 13369
11. []: Array access
您可以使用数组访问运算符 [] 访问一维常量数组。
例子:
# bpftrace -e 'struct MyStruct { int y[4]; } uprobe:./testprogs/array_access:test_struct { $s = (struct MyStruct *) arg0; @x = $s->y[0]; exit(); }' Attaching 1 probe... @x: 1
测试代码:
struct my_struct { int a[4]; }; static __attribute__((__noinline__)) void test_struct(struct my_struct *p) { p->a[0]++; p->a[1]++; p->a[2]++; p->a[3]++; } void main() { struct my_struct ms = {1, 2, 3, 4}; test_struct(&ms); }
12. Integer casts
整数在内部表示为 64 位有符号数。 如果您需要其他表示,您可以转换为以下内置类型:
Type Explanation ------------------------------ uint8 unsigned 8 bit integer int8 signed 8 bit integer uint16 unsigned 16 bit integer int16 signed 16 bit integer uint32 unsigned 32 bit integer int32 signed 32 bit integer uint64 unsigned 64 bit integer int64 signed 64 bit integer
示例:
# bpftrace -e 'BEGIN { $x = 1<<16; printf("%d %d\n", (uint16)$x, $x); }' Attaching 1 probe... 0 65536 ^C
13. Looping constructs
实验性的,内核:5.3,bpftrace 支持 C 风格的 while 循环:
# bpftrace -e 'i:ms:100 { $i = 0; while ($i <= 100) { printf("%d ", $i); $i++} exit(); }'
可以使用 continue 和 break 关键字来缩短循环。
14. return: Terminate Early
return 关键字用于退出当前探测。 这与 exit() 的不同之处在于它不会退出 bpftrace。
自己添加的示例:
# if 语句中即使只有一条语句,也必须要有大括号 bpftrace -e 'i:ms:100 { $i = 0; while ($i <= 100) { printf("%d ", $i); if ($i == 50) { $i++; } $i++} exit(); }' # 死循环了,每次都加到50 bpftrace -e 'i:ms:100 { $i = 0; while ($i <= 100) { printf("%d ", $i); if ($i == 50) { return; } $i++} exit(); }' # 加到50就退出了 bpftrace -e 'i:ms:100 { $i = 0; while ($i <= 100) { printf("%d ", $i); if ($i == 50) { exit(); } $i++} exit(); }'
15. ( , ): Tuples
支持 N 元组,其中 N 是大于 1 的任何整数。索引支持使用 . 操作符。 元组一旦创建就不可变。
例子:
# bpftrace -e 'BEGIN { $t = (1, 2, "string"); printf("%d %s\n", $t.1, $t.2); }' Attaching 1 probe... 2 string ^C
五、Probes
kprobe - 内核函数开始 kretprobe - 内核函数返回 uprobe - 用户空间函数开始 uretprobe - 用户空间函数返回 tracepoint - 内核静态跟踪点 usdt - 用户空间静态跟踪点 profile - 定时采样 interval - 定时输出 software - 内核软件事件 hardware - 处理器级事件
一些探测类型允许通配符匹配多个探测,例如,kprobe:vfs_*。 您还可以使用逗号分隔列表为操作块指定多个附加点。
带引号的字符串(例如 uprobe:"/usr/lib/c++lib.so":foo)可用于转义附加点定义中的字符。
1. kprobe/kretprobe: Dynamic Tracing, Kernel-Level
语法:
kprobe:function_name[+offset] kretprobe:function_name
这些使用 kprobes(Linux 内核功能)。 kprobe 检测函数执行的开始,kretprobe 检测结束(它的返回)。
例子:
# bpftrace -e 'kprobe:do_nanosleep { printf("sleep by %d\n", tid); }' Attaching 1 probe... sleep by 1396 sleep by 3669 ^C
也可以在探测函数中指定偏移量:
# gdb -q /usr/lib/debug/boot/vmlinux-`uname -r` --ex 'disassemble do_sys_open' Reading symbols from /usr/lib/debug/boot/vmlinux-5.0.0-32-generic...done. Dump of assembler code for function do_sys_open: 0xffffffff812b2ed0 <+0>: callq 0xffffffff81c01820 <__fentry__> 0xffffffff812b2ed5 <+5>: push %rbp 0xffffffff812b2ed6 <+6>: mov %rsp,%rbp 0xffffffff812b2ed9 <+9>: push %r15 ... # bpftrace -e 'kprobe:do_sys_open+9 { printf("in here\n"); }' Attaching 1 probe... in here ...
如果地址与指令边界对齐并在函数内,则使用 vmlinux(带有调试符号)检查地址。 如果不是,我们将无法添加它:
# bpftrace -e 'kprobe:do_sys_open+1 { printf("in here\n"); }' Attaching 1 probe... Could not add kprobe into middle of instruction: /usr/lib/debug/boot/vmlinux-5.0.0-32-generic:do_sys_open+1
如果 bpftrace 是使用 ALLOW_UNSAFE_PROBE 选项编译的,则可以使用 --unsafe 选项跳过检查。 在这种情况下,linux 内核仍然检查指令对齐。
可以使用环境变量 BPFTRACE_VMLINUX 覆盖默认的 vmlinux 路径。
现场示例:(kprobe) search /tools (kretprobe) /tools,这两个超链接分别为:
https://github.com/iovisor/bpftrace/search?q=kprobe%3A+path%3Atools&type=Code
https://github.com/iovisor/bpftrace/search?q=kretprobe%3A+path%3Atools&type=Code
.bt脚本中可以同时列多个:
kprobe:vfs_read*, kprobe:vfs_write*, kprobe:vfs_fsync,
2. kprobe/kretprobe: Dynamic Tracing, Kernel-Level Arguments
语法:
kprobe: arg0, arg1, ..., argN
kretprobe: retval
可以通过这些变量名称访问参数。 arg0 是第一个参数,只能通过 kprobe 访问。 retval 是被监测函数的返回值,只能在 kretprobe 上访问。
例子:
# bpftrace -e 'kprobe:do_sys_open { printf("opening: %s\n", str(arg1)); }' Attaching 1 probe... opening: /proc/cpuinfo opening: /proc/vmstat [...] # bpftrace -e 'kprobe:do_sys_open { printf("open flags: %d\n", arg2); }' Attaching 1 probe... open flags: 557056 open flags: 32768 [...] # bpftrace -e 'kretprobe:do_sys_open { printf("returned: %d\n", retval); }' Attaching 1 probe... returned: 8 returned: -2 [...]
一个结构体作为参数的示例:
# cat path.bt #include <linux/path.h> #include <linux/dcache.h> kprobe:vfs_open { printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name)); } # bpftrace path.bt Attaching 1 probe... open path: dev open path: retrans_time_ms [...]
这里 arg0 被转换为 (struct path *),因为它是 vfs_open() 的第一个参数的类型。 对结构体的支持与 bcc 相同,并且基于可用的内核头文件。 这意味着许多(但不是全部)结构将可用,您也可能需要手动定义一些结构体。
如果内核有 BTF(BPF Type Format)数据,则所有内核结构始终可用,无需定义它们。 例如:
# bpftrace -e 'kprobe:vfs_open { printf("open path: %s\n", \ str(((struct path *)arg0)->dentry->d_name.name)); }' Attaching 1 probe... open path: cmdline open path: interrupts [...]
看 BTF Support 章节获取更多信息。现场示例见上面:(kprobe) search /tools (kretprobe) /tools
3. uprobe/uretprobe: Dynamic Tracing, User-Level
语法:
uprobe:library_name:function_name[+offset]
uprobe:library_name:offset
uretprobe:library_name:function_name
这些使用 uprobes(Linux 内核功能)。 uprobe 检测用户级函数执行的开始,uretprobe 检测结束(它的返回)。
要列出可用的 uprobe,您可以使用任何程序列出二进制文件中的代码段符号,例如 objdump 和 nm。 例如:
# objdump -tT /bin/bash | grep readline 00000000007003f8 g DO .bss 0000000000000004 Base rl_readline_state 0000000000499e00 g DF .text 00000000000001c5 Base readline_internal_char 00000000004993d0 g DF .text 0000000000000126 Base readline_internal_setup 000000000046d400 g DF .text 000000000000004b Base posix_readline_initialize 000000000049a520 g DF .text 0000000000000081 Base readline [...]
这列出了 /bin/bash 中包含“readline”的各个函数。 这些可以使用 uprobe 和 uretprobe 进行监测。注:被编译器优化而内联的函数是不会被显示出来的。
例子:
# bpftrace -e 'uretprobe:/bin/bash:readline { printf("read a line\n"); }' Attaching 1 probe... read a line ^C
在跟踪时,这在 /bin/bash 中捕获了一些 readline() 函数的执行。 此示例将在下一节中继续。
也可以为 uprobe 指定虚拟地址,例如:
# objdump -tT /bin/bash | grep main ... 000000000002ec00 g DF .text 0000000000001868 Base main ... # bpftrace -e 'uprobe:/bin/bash:0x2ec00 { printf("in here\n"); }' Attaching 1 probe...
并可以在探测函数中指定偏移量:
# objdump -d /bin/bash ... 000000000002ec00 <main@@Base>: 2ec00: f3 0f 1e fa endbr64 2ec04: 41 57 push %r15 2ec06: 41 56 push %r14 2ec08: 41 55 push %r13 ... # bpftrace -e 'uprobe:/bin/bash:main+4 { printf("in here\n"); }' Attaching 1 probe... ...
将会检查地址是否与指令边界对齐。 如果不是,我们将无法添加它:
# bpftrace -e 'uprobe:/bin/bash:main+1 { printf("in here\n"); }' Attaching 1 probe... Could not add uprobe into middle of instruction: /bin/bash:main+1
如果 bpftrace 是使用 ALLOW_UNSAFE_PROBE 选项编译的,您可以使用 --unsafe 选项跳过该检查:
# bpftrace -e 'uprobe:/bin/bash:main+1 { printf("in here\n"); } --unsafe' Attaching 1 probe... Unsafe uprobe in the middle of the instruction: /bin/bash:main+1
使用 --unsafe 选项,您还可以将 uprobe 放置在任意地址上。 当二进制文件被 stripped 时,这可能会派上用场。
$ echo 'int main(){return 0;}' | gcc -xc -o bin - $ nm bin | grep main ... 0000000000001119 T main ... $ strip bin # bpftrace --unsafe -e 'uprobe:bin:0x1119 { printf("main called\n"); }' Attaching 1 probe... WARNING: could not determine instruction boundary for uprobe:bin:4377 (binary appears stripped). Misaligned probes can lead to tracee crashes!
当跟踪库时,指定库名称而不是完整路径就足够了。 然后将使用 /etc/ld.so.cache 自动解析该路径。 //实测不行
# bpftrace -e 'uprobe:libc:malloc { printf("Allocated %d bytes\n", arg0); }' Allocated 4 bytes ...
现场示例:(uprobe) search /tools (uretprobe) /tools,两个超链接分别为:
https://github.com/iovisor/bpftrace/search?q=uprobe%3A+path%3Atools&type=Code
https://github.com/iovisor/bpftrace/search?q=uretprobe%3A+path%3Atools&type=Code
4. uprobe/uretprobe: Dynamic Tracing, User-Level Arguments
语法:
uprobe: arg0, arg1, ..., argN
uretprobe: retval
可以通过这些变量名称访问参数。 arg0 是第一个参数,只能通过 uprobe 访问。 retval 是检测函数的返回值,只能在 uretprobe 上访问。
例子:
# bpftrace -e 'uprobe:/bin/bash:readline { printf("arg0: %d\n", arg0); }' Attaching 1 probe... arg0: 19755784 ^C
/bin/bash 中 readline() 的 arg0 包含什么? 我不知道。 我需要查看 bash 源代码以找出它的参数是什么。
# bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc-2.23.so:fopen { printf("fopen: %s\n", str(arg0)); }' Attaching 1 probe... fopen: /proc/filesystems fopen: /usr/share/locale/locale.alias fopen: /proc/self/mountinfo ^C
在这种情况下,我知道 libc fopen() 的第一个参数是路径名(请参阅 fopen(3) 手册页),因此我使用 uprobe 对其进行了跟踪。 调整libc 的路径以匹配您的系统(它可能不是 libc-2.23.so)。 str() 调用是将 char * 指针转换为字符串所必需调用的,如后面部分所述。
# bpftrace -e 'uretprobe:/bin/bash:readline { printf("readline: \"%s\"\n", str(retval)); }' Attaching 1 probe... readline: "echo hi" readline: "ls -l" readline: "date" readline: "uname -r" ^C
回到bash readline()的例子:查看源码后发现返回值是读取的字符串。 所以我可以使用 uretprobe 和 retval 变量来查看读取的字符串。
如果被跟踪的二进制文件有可用的 DWARF,则可以通过名称访问 uprobe 参数。
例子:uprobe: args->NAME
可以通过解引用 args 和访问参数的名称来访问NAME。
可以使用详细列表选项检索函数参数列表:
# bpftrace -lv 'uprobe:/bin/bash:rl_set_prompt' uprobe:/bin/bash:rl_set_prompt const char* prompt
示例(需要安装了 /bin/bash 的 debuginfo):
# bpftrace -e 'uprobe:/bin/bash:rl_set_prompt { printf("prompt: %s\n", str(args->prompt)); }' Attaching 1 probe... prompt: [user@localhost ~]$ ^C
现场示例见上面:(uprobe) search /tools (uretprobe) /tools
5. tracepoint: Static Tracing, Kernel-Level
语法:tracepoint:name
这些使用跟踪点(Linux 内核功能)。
# bpftrace -e 'tracepoint:block:block_rq_insert { printf("block I/O created by %d\n", tid); }' Attaching 1 probe... block I/O created by 28922 block I/O created by 3949 [...]
现场示例:search /tools,超链接网址:https://github.com/iovisor/bpftrace/search?q=tracepoint%3A+path%3Atools&type=Code
6. tracepoint: Static Tracing, Kernel-Level Arguments
示例:
# bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }' Attaching 1 probe... irqbalance /proc/interrupts snmpd /proc/net/dev [...]
每个跟踪点的可用成员可以从 /sys 中的 /format 文件中列出。 例如:
# cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_open/format name: sys_enter_openat ID: 608 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:int dfd; offset:16; size:8; signed:0; field:const char * filename; offset:24; size:8; signed:0; field:int flags; offset:32; size:8; signed:0; field:umode_t mode; offset:40; size:8; signed:0; print fmt: "dfd: 0x%08lx, filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx", ((unsigned long)(REC->dfd)), ((unsigned long)(REC->filename)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->mode))
除了文件名成员,我们还可以打印flags, mode 等。 在首先列出"common"成员之后,剩下的这些成员特定于跟踪点。
现场示例见上面:search/tools
--------------------下面的待翻译验证------------------
7. usdt: Static Tracing, User-Level
8. usdt: Static Tracing, User-Level Arguments
9. profile: Timed Sampling Events
以99赫兹的频率分析内核调用栈并打印次数统计:
# bpftrace -e 'profile:hz:99 { @[kstack] = count(); }' Attaching 1 probe... ^C [...] @[ filemap_map_pages+181 __handle_mm_fault+2905 handle_mm_fault+250 __do_page_fault+599 async_page_fault+69 ]: 12 ...
10. interval: Timed Output
11. software: Pre-defined Software Events
12. hardware: Pre-defined Hardware Events
13. BEGIN/END: Built-in events
语法:
BEGIN
END
这些是 bpftrace 运行时提供的特殊内置事件。 BEGIN 在附加所有其他探测器之前被触发。 END 在所有其他探测器分离后触发。
现场示例: (BEGIN) search /tools (END) search /tools,两个超链接分别为:
https://github.com/iovisor/bpftrace/search?q=BEGIN+extension%3Abt+path%3Atools&type=Code
https://github.com/iovisor/bpftrace/search?q=END+extension%3Abt+path%3Atools&type=Code
14. watchpoint/asyncwatchpoint: Memory watchpoints
15. kfunc/kretfunc: Kernel Functions Tracing
16. kfunc/kretfunc: Kernel Functions Tracing Arguments
17. iter: Iterators Tracing
六、Variables
1. Builtins
pid - 进程 ID(内核 tgid) tid - 线程 ID(内核 pid) uid - 用户 ID gid - 组 ID nsecs - 纳秒时间戳 elapsed - 自 bpftrace 初始化以来的纳秒数 numaid - NUMA 节点 ID cpu - 处理器 ID comm - 进程名称 kstack - 内核堆栈trace ustack - 用户堆栈trace arg0, arg1, ..., argN - 跟踪函数的参数; 假定为 64 位宽 sarg0, sarg1, ..., sargN - 跟踪函数的参数(对于在堆栈上存储参数的程序); 假定为 64 位宽 retval - 跟踪函数的返回值 func - 跟踪函数的名称 probe - 探针的全名 curtask - Current task struct为 u64,应该是current指针吧 rand - 作为 u32 的随机数 cgroup - 当前进程的 Cgroup ID ############# TODO: 试一下 cpid - 子 pid(u32),仅在 -c 命令标志时有效 $1, $2, ..., $N, $#。 - bpftrace 程序的位置参数
其中许多在其他部分进行了讨论(使用搜索)。
2. @, $: Basic Variables
语法:
@global_name
@thread_local_variable_name[tid]
$scratch_name
bpftrace 支持全局和每线程变量(通过 BPF 映射)和临时变量。
例子:
2.1. Global 全局变量
语法:@name
例如,@start:
# bpftrace -e 'BEGIN { @start = nsecs; } kprobe:do_nanosleep /@start != 0/ { printf("at %d ms: sleep\n", (nsecs - @start) / 1000000); }' Attaching 2 probes... at 437 ms: sleep at 1098 ms: sleep at 1648 ms: sleep ^C @start: 4064438886907216
注:放在 BEGIN 段中,就是attach这个bpf程序时的时间戳。
2.2. Per-Thread 变量:
这些可以实现为以线程 ID 为键的关联数组。 例如,@start[tid]:
# bpftrace -e '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]); }' Attaching 2 probes... slept for 1000 ms slept for 1009 ms slept for 2002 ms [...]
注:放在函数体中,就是探测函数被命中时的时间戳。这个可以监控一个进程sleep了多长时间!
2.3. Scratch 临时变量:
语法:$name
例如,$delta:
# bpftrace -e 'kprobe:do_nanosleep { @start[tid] = nsecs; } kretprobe:do_nanosleep /@start[tid] != 0/ { $delta = nsecs - @start[tid]; printf("slept for %d ms\n", $delta / 1000000); delete(@start[tid]); }' Attaching 2 probes... slept for 1000 ms slept for 1000 ms slept for 1000 ms
补充:临时变量类似是C中的,全局变量可以在脚本任意位置使用
bpftrace -e '#include <linux/sched.h> kretprobe:pick_next_task_rt /@prio==99/ { @prio=((struct task_struct*)retval)->prio; printf("cpu=%d, prio=%d\n", cpu, @prio); }' //过滤处全局变量@prio可以,使用局部变量$prio不行(失败的,还没赋值,得用if)
3. @[]: Associative Arrays
语法:
@associative_array_name[key_name] = value
@associative_array_name[key_name, key_name2, ...] = value
这些是使用 BPF map 实现的。
例如,@start[tid]:
# bpftrace -e '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]); }' Attaching 2 probes... slept for 1000 ms slept for 1000 ms slept for 1000 ms [...] # bpftrace -e 'BEGIN { @[1,2] = 3; printf("%d\n", @[1,2]); clear(@); }' Attaching 1 probe... 3 ^C
4. count(): Frequency Counting
这是由 count() 函数提供的:请参阅下面 Count 部分。
5. hist(), lhist(): Histograms
这些由 hist() 和 lhist() 函数提供。 请参阅下面 Log2 Histogram 和 Linear Histogram 部分。
6. nsecs: Timestamps and Time Deltas
语法:nsecs
这些是使用 bpf_ktime_get_ns() 实现的。
例子:
# bpftrace -e 'BEGIN { @start = nsecs; } kprobe:do_nanosleep /@start != 0/ { printf("at %d ms: sleep\n", (nsecs - @start) / 1000000); }' Attaching 2 probes... at 437 ms: sleep at 1098 ms: sleep at 1438 ms: sleep ^C
含义为监测开始多长时间后进程进入sleep的。
7. kstack: Stack Traces, Kernel
语法:kstack
这个内置是函数 kstack() 的别名。
例子:
# bpftrace -e 'kprobe:ip_output { @[kstack] = count(); }' Attaching 1 probe... [...] @[ ip_output+1 tcp_transmit_skb+1308 ... sys_write+82 entry_SYSCALL_64_fastpath+30 ]: 1708 @[ ... ]: 9048
作用是以每一个kstack为键,统计各回溯了多少次。
8. ustack: Stack Traces, User
语法:ustack
这个内置是函数 ustack() 的别名。
例子:
# bpftrace -e 'kprobe:do_sys_open /comm == "bash"/ { @[ustack] = count(); }' Attaching 1 probe... ^C @[ __open_nocancel+65 ... read_command+207 reader_loop+391 main+2409 __libc_start_main+231 0x61ce258d4c544155 ]: 9 @[ ... ]: 18
请注意,要使此示例正常工作,必须使用帧指针重新编译 bash。
作用是以每一个ustack为键,统计各用户栈回溯了多少次。
9. $1, ..., $N, $#: Positional Parameters
语法:$1, $2, ..., $N, $#
这些是 bpftrace 程序的位置参数,也称为命令行参数。 如果参数是数字(全是数字),则可以作为数字使用。 如果它是非数字的,它必须在 str() 调用中用作字符串。 如果使用未提供的参数,则数字上下文默认为零,字符串上下文默认为“”。 位置参数也可以用在探测参数中,并将被视为字符串参数。
如果在 str() 中使用位置参数,它被解释为指向实际给定字符串文字的指针,这允许对其进行指针运算。 只允许添加一个小于或等于所提供字符串长度的常量。
$# 返回提供的位置参数的数量。
这允许编写使用基本参数来更改其脚本的行为。 如果您开发的脚本需要更复杂的参数处理,它可能更适合 bcc,它支持 Python 的 argparse 和完全自定义的参数处理。
单行示例:
# bpftrace -e 'BEGIN { printf("I got %d, %s (%d args)\n", $1, str($2), $#); }' 42 "hello" Attaching 1 probe... I got 42, hello (2 args) # bpftrace -e 'BEGIN { printf("%s\n", str($1 + 1)) }' "hello" Attaching 1 probe... ello
脚本示例,bsize.d:
#!/usr/local/bin/bpftrace BEGIN { printf("Tracing block I/O sizes > %d bytes\n", $1); } tracepoint:block:block_rq_issue /args->bytes > $1/ { @ = hist(args->bytes); }
使用 65536 参数运行时://脚本也可以传参
# ./bsize.bt 65536 Attaching 2 probes... Tracing block I/O sizes > 65536 bytes ^C @: [512K, 1M) 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
它已将参数作为 $1 传递,并将其用作过滤器。若没有指定参数,$1 默认为零:
# ./bsize.bt Attaching 2 probes... Tracing block I/O sizes > 0 bytes ^C @: [4K, 8K) 115 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [8K, 16K) 35 |@@@@@@@@@@@@@@@ | [16K, 32K) 5 |@@ | [32K, 64K) 3 |@ | [64K, 128K) 1 | | [128K, 256K) 0 | | [256K, 512K) 0 | | [512K, 1M) 1 | |
七、Functions
1. Builtins
printf(char *fmt, ...) - 格式化打印 time(char *fmt) - 打印格式化时间 join(char *arr[] [, char *delim]) - 打印数组 str(char *s [, int length]) - 返回 s 指向的字符串 ksym(void *p) - 解析内核地址 usym(void *p) - 解析用户空间地址 kaddr(char *name) - 解析内核符号名称 uaddr(char *name) - 解析用户级符号名称 reg(char *name) - 返回存储在命名寄存器中的值 system(char *fmt) - 执行 shell 命令 exit() - 退出 bpftrace cgroupid(char *path) - 解析 cgroup ID kstack([StackMode mode, ][int level]) - 内核堆栈跟踪 ustack([StackMode mode, ][int level]) - 用户堆栈跟踪 ntop([int af, ]int|char[4|16] addr) - 将 IP 地址数据转换为文本 pton(const string *addr) - 将文本 IP 地址转换为字节数组 cat(char *filename) - 打印文件内容 signal(char[] signal | u32 signal) - 向当前任务发送信号 strncmp(char *s1, char *s2, int length) - 比较两个字符串的前 n 个字符 override(u64 rc) - 覆盖返回值 buf(void *d [, int length]) - 返回 d 指向的数据的十六进制格式字符串 sizeof(...) - 返回类型或表达式的大小 print(...) - 使用默认格式打印非地图值 strftime(char *format, int nsecs) - 返回格式化的时间戳 path(struct path *path) - 返回完整路径 uptr(void *p) - 注释为用户空间指针 kptr(void *p) - 注释为内核空间指针 macaddr(char[6] addr) - 转换 MAC 地址数据 bswap(uint[8|16|32|64] n) - 反转字节顺序
其中一些是异步的:内核将事件排队,但一段时间后(毫秒)它在用户空间中处理。 异步操作是:printf()、time() 和 join()。 ksym() 和 usym() 以及变量 kstack 和 ustack 都同步地记录地址,但随后异步进行符号转换。
以下各节将讨论其中的一部分。
2. printf(): Print Formatted
语法:printf(fmt, args)
这与 C 和其他语言中的 printf() 类似,具有一组有限的格式字符。 例子:
# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s called %s\n", comm, str(args->filename)); }' Attaching 1 probe... bash called /bin/ls bash called /usr/bin/man man called /apps/nflx-bash-utils/bin/tbl [...]
3. time(): Time
语法:time(fmt)
这使用 libc strftime(3) 支持的格式字符串打印当前时间,最高精度是秒。
# bpftrace -e 'kprobe:do_nanosleep { time("%H:%M:%S\n"); }' 07:11:03 07:11:09 ^C
如果未提供格式字符串,则默认为“%H:%M:%S\n”。
请注意,此内置函数是异步的。 打印的时间戳是用户空间处理排队事件的时间,而不是 bpf prog 调用 time() 的时间。 有关更精确的时间戳,请参阅 strftime()。
4. join(): Join
语法:join(char *arr[] [, char *delim])
这会将字符串数组与空格字符连接起来,并打印出来,用定界符分隔。 默认分隔符(如果未提供)是空格字符。 当前版本不返回字符串,因此它不能用作 printf() 中的参数。 例子:
# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }' Attaching 1 probe... ls --color=auto man ls preconv -e UTF-8 preconv -e UTF-8 tbl [...] # bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv, ","); }' Attaching 1 probe... ls,--color=auto man,ls preconv,-e,UTF-8 preconv,-e,UTF-8 tbl [...]
5. str(): Strings
语法:str(char *s [, int length])
返回 s 指向的字符串。 length 可用于限制读取的大小,和/或引入空终止符。 默认情况下,字符串的大小为 64 字节(可使用环境变量 BPFTRACE_STRLEN 进行调整)。
例子:
我们可以获取 sys_enter_execve 的 args->filename(一个 const char *filename),并读取它指向的字符串。 该字符串可以作为 printf() 的参数:
# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s called %s\n", comm, str(args->filename)); }' Attaching 1 probe... bash called /bin/ls bash called /usr/bin/man man called /apps/nflx-bash-utils/bin/tbl [...]
我们可以跟踪显示在 bash shell 中的字符串。 采用了一些长度调整,因为:
sys_enter_write() 的 args->buf 不指向以 null 结尾的字符串,我们使用长度参数来限制读取指向的字符串的字节数。
sys_enter_write() 的 args->buf 包含大于 64 字节的消息,我们增加 BPFTRACE_STRLEN 以容纳大消息。
# BPFTRACE_STRLEN=200 bpftrace -e 'tracepoint:syscalls:sys_enter_write /pid == 23506/ { printf("<%s>\n", str(args->buf, args->count)); }' //这样写长度限制单次有小,export是每次都生效 # type pwd into terminal 23506 <p> <w> <d> # press enter in terminal 23506 < > </home/anon > <anon@anon-VirtualBox:~$ >
6. ksym(): Symbol Resolution, Kernel-Level
语法:ksym(addr)
例子:
# bpftrace -e 'kprobe:do_nanosleep { printf("%s\n", ksym(reg("ip"))); }' //aa64上不支持ip Attaching 1 probe... do_nanosleep do_nanosleep
7. usym(): Symbol Resolution, User-Level
语法:usym(addr)
例子:
# bpftrace -e 'uprobe:/bin/bash:readline { printf("%s\n", usym(reg("ip"))); }' Attaching 1 probe... readline readline readline ^C
8. kaddr(): Address Resolution, Kernel-Level
语法:kaddr(char *name)
例子:
# bpftrace -e 'BEGIN { printf("%s\n", str(*kaddr("usbcore_name"))); }' Attaching 1 probe... usbcore ^C
这是打印 drivers/usb/core/usb.c 中的 usbcore_name 指向的字符串:
const char *usbcore_name = "usbcore";
补充一个打印 drivers/usb/core/usb.c 中的 usb_autosuspend_delay 变量的值的实验:
static int usb_autosuspend_delay = CONFIG_USB_AUTOSUSPEND_DELAY; //2 usb.c root@localhost:/sys/kernel/tracing# bpftrace -e 'BEGIN { printf("%d\n", *kaddr("usb_autosuspend_delay")); }' Attaching 1 probe... 2 ^C
补充一个打印内核变量 usb_autosuspend_delay 的地址的实验:
root@localhost:/sys/kernel/tracing# bpftrace -e 'BEGIN { printf("%p\n", kaddr("usb_autosuspend_delay")); }' Attaching 1 probe... 0xffffffe0aa89e4a0 ^C
9. uaddr(): Address Resolution, User-Level
语法:
u64 *uaddr(symbol) (default) u64 *uaddr(symbol) u32 *uaddr(symbol) u16 *uaddr(symbol) u8 *uaddr(symbol)
支持的探测类型:
uprobes
uretprobes
USDT
不要和 ASLR 一起使用, 见 issue #75,地址:https://github.com/iovisor/bpftrace/issues/75
uaddr 函数返回指定符号的地址。 这种查找发生在程序编译期间,不能动态使用。
默认返回类型是 u64*。 如果 ELF 对象大小匹配已知整数大小(1、2、4 或 8 字节),则返回类型被修改以匹配宽度(分别为 u8*、u16*、u32* 或 u64*)。 由于 ELF 不包含类型信息,因此该类型始终被假定为无符号数。
例子:
# bpftrace -e 'uprobe:/bin/bash:readline { printf("PS1: %s\n", str(*uaddr("ps1_prompt"))); }' Attaching 1 probe... PS1: \[\e[34;1m\]\u@\h:\w>\[\e[0m\] PS1: \[\e[34;1m\]\u@\h:\w>\[\e[0m\] ^C
每当执行 readline() 函数时,都会从 /bin/bash 打印 ps1_prompt 字符串。
10. reg(): Registers
语法: reg(char *name)
例子:
# bpftrace -e 'kprobe:tcp_sendmsg { @[ksym(reg("ip"))] = count(); }' Attaching 1 probe... ^C @[tcp_sendmsg]: 7
寄存器名称列表见 src/arch/x86_64.cpp。
11. system(): System
语法:system(fmt)
这将在 shell 中运行提供的命令。 例如使用 ps -p 获取线程名:
# bpftrace --unsafe -e 'kprobe:do_nanosleep { system("ps -p %d\n", pid); }' Attaching 1 probe... PID TTY TIME CMD 2638 ? 00:00:00 wifi_diag PID TTY TIME CMD 1562 ? 00:00:14 charger@1.0-ser ^C
这对于在检测事件发生时执行命令或 shell 脚本很有用。请注意,这是一个不安全的功能。 要使用它,bpftrace 必须与 --unsafe 一起运行。
12. exit(): Exit
语法:exit()
这将退出 bpftrace,并且可以与间隔探测结合以记录一定持续时间内的统计信息。 例子:
# bpftrace -e 'kprobe:do_sys_openat2 { @opens = count(); } interval:s:10 { exit(); }' Attaching 2 probes... @opens: 337
统计 10s 内 do_sys_openat2() 的调用次数。
13. cgroupid(): Resolve cgroup ID
语法:cgroupid(char *path)
这将返回特定 cgroup 的 cgroup ID,并且可以结合 cgroup 内置函数来过滤属于特定 cgroup 的任务,例如:
# bpftrace -e 'tracepoint:syscalls:sys_enter_openat /cgroup == cgroupid("/sys/fs/cgroup/unified/mycg")/ { printf("%s\n", str(args->filename)); }': Attaching 1 probe... /etc/ld.so.cache /etc/shadow ^C
在其他终端:
# echo $$ > /sys/fs/cgroup/unified/mycg/cgroup.procs
# cat /etc/shadow
补充:
bpftrace -e 'kprobe:do_sys_openat2 { printf("%s\n", str(uptr(arg1))); }' //成功 bpftrace -e 'kprobe:do_sys_openat2 /cgroup == cgroupid("/dev/cpuctl/top-app")/ { printf("%s\n", str(uptr(arg1))); }' //失败 # bpftrace -e 'kprobe:do_sys_openat2 /cgroup == 1/ { printf("tid=%d, %s\n", tid, str(uptr(arg1))); }' //只有赋值为1有打印,其它 0-6 都没有打印 tid=1, /proc/oplus_init_watchdog/kick tid=271, /data/property/persistent_properties ^C
将 pid 1 echo 到其它cgroup分组中,也不影响上面的打印。。不太对。。
14. ntop(): Convert IP address data to text
语法:ntop([int af, ]int|char[4|16] addr)
这将返回 IPv4 或 IPv6 地址的字符串表示形式。 ntop 将根据地址类型和大小推断地址类型(IPv4 或 IPv6)。 如果给出一个整数或 char[4],ntop 假定 IPv4,如果给出一个 char[16],ntop 假定 IPv6。 您还可以将地址类型作为第一个参数显式传递。
例子:
带有 ipv4 十六进制编码文字的 ntop 的简单示例:
# bpftrace -e 'BEGIN { printf("%s\n", ntop(0x0100007f));}' 127.0.0.1 ^C
与之前相同的示例,但将地址类型显式传递给 ntop:
# bpftrace -e '#include <linux/socket.h> BEGIN { printf("%s\n", ntop(AF_INET, 0x0100007f));}' //头文件后必须要有一个回车 127.0.0.1 ^C
这种用法的一个不那么简单的例子,跟踪 tcp 状态变化,并打印目标 IPv6 地址:
# bpftrace -e 'tracepoint:tcp:tcp_set_state { printf("%s\n", ntop(args->daddr_v6)) }' Attaching 1 probe... ::ffff:216.58.194.164 ::ffff:216.58.194.164 ^C
并在另一个终端中启动到这个(或任何)地址的连接:curl www.google.com
15. kstack(): Stack Traces, Kernel
语法:kstack([StackMode mode, ][int limit])
这些是使用 BPF 堆栈映射实现的。
例子:
# bpftrace -e 'kprobe:ip_output { @[kstack()] = count(); }' Attaching 1 probe... [...] @[ ip_output+1 tcp_transmit_skb+1308 ... sys_write+82 entry_SYSCALL_64_fastpath+30 ]: 1708 @[ ... ]: 9048
仅从堆栈中采样三帧(limit = 3):
# bpftrace -e 'kprobe:ip_output { @[kstack(3)] = count(); }' Attaching 1 probe... [...] @[ ip_output+1 tcp_transmit_skb+1308 tcp_write_xmit+482 ]: 22186
您还可以选择不同的输出格式。 可用格式为 bpftrace 和 perf:
# bpftrace -e 'kprobe:do_mmap { @[kstack(perf)] = count(); }' Attaching 1 probe... [...] @[ ffffffffb4019501 do_mmap+1 ffffffffb401700a sys_mmap_pgoff+266 ffffffffb3e334eb sys_mmap+27 ffffffffb3e03ae3 do_syscall_64+115 ffffffffb4800081 entry_SYSCALL_64_after_hwframe+61 ]: 22186
也可以使用不同的输出格式并限制帧数:
# bpftrace -e 'kprobe:do_mmap { @[kstack(perf, 3)] = count(); }' Attaching 1 probe... [...] @[ ffffffffb4019501 do_mmap+1 ffffffffb401700a sys_mmap_pgoff+266 ffffffffb3e334eb sys_mmap+27 ]: 22186
16. ustack(): Stack Traces, User
语法:ustack([StackMode mode, ][int limit])
这些是使用 BPF stack maps 映射实现的。例子:
# bpftrace -e 'kprobe:do_sys_open /comm == "bash"/ { @[ustack()] = count(); }' Attaching 1 probe... ^C @[ __open_nocancel+65 command_word_completion_function+3604 ... reader_loop+391 main+2409 __libc_start_main+231 0x61ce258d4c544155 ]: 9 @[ ... ]: 18
从堆栈中仅采样六帧(limit = 6):
# bpftrace -e 'kprobe:do_sys_open /comm == "bash"/ { @[ustack(6)] = count(); }' Attaching 1 probe... ^C @[ __open_nocancel+65 command_word_completion_function+3604 rl_completion_matches+370 bash_default_completion+540 attempt_shell_completion+2092 gen_completion_matches+82 ]: 27 # bpftrace -e 'kprobe:do_sys_openat2 /comm == "sh"/ { @[ustack(6)] = count(); }' //成功 Attaching 1 probe... ^C @[ 0x7fa6d65fac ... 0x7fa6d0ae30 ]: 1 @[ ... ]: 1
您还可以选择不同的输出格式。 可用格式为 bpftrace 和 perf:
# bpftrace -e 'uprobe:bash:readline { printf("%s\n", ustack(perf)); }' Attaching 1 probe... 5649feec4090 readline+0 (/home/mmarchini/bash/bash/bash) ... 5649fee27dd6 parse_command+54 (/home/mmarchini/bash/bash/bash)
也可以使用不同的输出格式并限制帧数:
# bpftrace -e 'uprobe:bash:readline { printf("%s\n", ustack(perf, 3)); }' Attaching 1 probe... 5649feec4090 readline+0 (/home/mmarchini/bash/bash/bash) 5649fee2bfa6 yy_readline_get+451 (/home/mmarchini/bash/bash/bash) 5649fee2bdc6 yy_getc+13 (/home/mmarchini/bash/bash/bash)
请注意,为了使这些示例起作用,必须使用帧指针重新编译 bash。
17. cat(): Print file content
语法: cat(filename)
这将打印文件内容。 例如:
# bpftrace -e 't:syscalls:sys_enter_execve { printf("%s ", str(args->filename)); cat("/proc/loadavg"); }' Attaching 1 probe... /usr/libexec/grepconf.sh 3.18 2.90 2.94 2/977 30138 ... /bin/ps 3.18 2.90 2.94 2/979 30155 ^C
cat() 内置函数还支持格式字符串作为参数:
# bpftrace -e 'tracepoint:syscalls:sys_enter_sendmsg { printf("%s => ", comm); cat("/proc/%d/cmdline", pid); printf("\n") }' Attaching 1 probe... Gecko_IOThread => /usr/lib64/firefox/firefox ... Gecko_IOThread => /usr/lib64/firefox/firefox ^C root@localhost:/# bpftrace -e 'kprobe:do_nanosleep { printf("%d %s => ", tid, comm); cat("/proc/%d/cmdline", tid); printf("\n") }' //成功 Attaching 1 probe... 15696 Thread-513 => com.smile.gifmaker ... 5868 OplusAdTrackThr => /system/bin/audioserver ^C
18. signal(): Send a signal to the current task
语法:
signal(u32 signal) signal("SIG")
内核:5.3,支持的探测类型:
k(ret)probes
u(ret)probes
USDT
profile
signal 向当前任务发送指定的信号:
# bpftrace -e 'kprobe:__x64_sys_execve /comm == "bash"/ { signal(5); }' --unsafe $ ls Trace/breakpoint trap (core dumped)
也可以使用名称指定信号,类似于 kill(1) 命令:
# bpftrace -e 'k:f { signal("KILL"); }' # bpftrace -e 'k:f { signal("SIGINT"); }'
19. strncmp(): Compare first n characters of two strings
语法: strncmp(char *s1, char *s2, int length)
如果 s1 和 s2 中的第一个 length 长度字符相等,则返回零,否则返回非零。
例子:
bpftrace -e 't:syscalls:sys_enter_* /strncmp("mpv", comm, 3) == 0/ { @[comm, probe] = count() }' Attaching 320 probes... [...] @[mpv/vo, tracepoint:syscalls:sys_enter_rt_sigaction]: 238 ... @[mpv, tracepoint:syscalls:sys_enter_futex]: 403868
20. override(): Override return value
语法: override(u64 rc)
内核:4.16
支持的探针类型:kprobes
被探测的函数将不会被执行,而是会执行一个只返回 rc 的帮助程序。
# bpftrace -e 'k:__x64_sys_getuid /comm == "id"/ { override(2<<21); }' --unsafe -c id uid=4194304 gid=0(root) euid=0(root) groups=0(root)
此功能仅适用于使用 CONFIG_BPF_KPROBE_OVERRIDE 编译的内核,并且仅适用于标记为 ALLOW_ERROR_INJECTION 的函数。
bpftrace 不会测试被探测函数是否允许错误注入,而是如果不允许,将程序加载到内核中将失败:
ioctl(PERF_EVENT_IOC_SET_BPF): Invalid argument Error attaching probe: 'kprobe:vfs_read'
21. buf(): Buffers
语法: buf(void *d [, int length])
返回 d 指向的可安全打印的十六进制格式的数据字符串。 因为不能总是推断出缓冲区的长度,所以可以提供长度参数来限制读取的字节数。 默认情况下,最大字节数为 64,但这可以使用 BPFTRACE_STRLEN 环境变量进行调整。
值 >=32 和 <=126 的字节,使用它们的 ASCII 字符打印,其他字节以十六进制形式打印(例如 \x00)。
例如,我们可以取 sys_enter_sendto 的 buff 参数(void *),读取 len(size_t)指定的字节数,将字节格式化为十六进制,这样就不会破坏终端显示。 可以使用 %r 格式说明符将生成的字符串作为参数提供给 printf():
# bpftrace -e 'tracepoint:syscalls:sys_enter_sendto { printf("Datagram bytes: %r\n", buf(args->buff, args->len)); }' -c 'ping 8.8.8.8 -c1' Attaching 1 probe... PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. Datagram bytes: \x08\x00+\xb9\x06b\x00\x01Aen^\x00\x00\x00\x00KM\x0c\x00\x00\x00\x00\x00\x10\x11 \x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&'()*+,-./01234567 64 bytes from 8.8.8.8: icmp_seq=1 ttl=52 time=19.4 ms --- 8.8.8.8 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 19.426/19.426/19.426/0.000 ms
22. sizeof(): Size of type or expression
语法:
sizeof(TYPE)
sizeof(EXPRESSION)
以字节为单位返回参数的大小。 类似于 C/C++ sizeof 运算符。 请注意,表达式不会被计算。
例子:
# bpftrace -e 'struct Foo { int x; char c; } BEGIN { printf("%d\n", sizeof(struct Foo)); }' Attaching 1 probe... 8 # bpftrace -e 'struct Foo { int x; char c; } BEGIN { printf("%d\n", sizeof(((struct Foo*)0)->c)); }' Attaching 1 probe... 1 # bpftrace -e 'BEGIN { printf("%d\n", sizeof(1 == 1)); }' Attaching 1 probe... 8 # bpftrace -e ' #include <linux/sched.h> BEGIN { printf("%d\n", sizeof(struct task_struct)); exit(); }' //必须有换行,成功 Attaching 1 probe... 4376 # bpftrace -e 'BEGIN { $x = 3; printf("%d\n", sizeof($x)); }' Attaching 1 probe... 8
补充:
kheaders-`uname -r`/include/generated/uapi/linux/version.h:#define LINUX_VERSION_CODE 330350
而 LINUX_VERSION_CODE = LINUX_VERSION_MAJOR<<16 | LINUX_VERSION_PATCHLEVEL<<8 | LINUX_VERSION_SUBLEVEL,因此 330350=Linux-5.10.110
struct task_struct 的大小能否完全对的上,要看挂载的文件系统中的头文件是哪个内核的。
23. print(): Print Value
语法: print(value)
print() 函数可以使用默认格式打印非map值。例如,可以打印局部变量和大多数内置函数:
# bpftrace -e 'BEGIN { $t = (1, "string"); print(123); print($t); print(comm) }' Attaching 1 probe... 123 (1, string) bpftrace ^C
重要的是要注意打印 values 不同于打印 maps。 打印 maps 和打印 values 都是异步的:内核将事件排队,但一段时间后在用户空间中处理。 对于 values ,事件就包含了 memcopy 的值,因此 print() 打印的值就是print 调用时的值。但是对于 maps,只有 maps 的句柄在排队,因此打印的 maps 可能与 print() 调用时的 maps 不同。
24. strftime(): Formatted timestamp
语法:strftime(const char *format, int nsecs)
这将返回可使用 printf 打印的格式化时间戳(精度为ns!)。 字符串格式必须是 strftime(3) 所支持的。 nsecs 参数是自启动以来的纳秒数,通常来自 nsecs。
打印返回值时使用格式说明符“%s”。 请注意,strftime 实际上并不返回 bpf(内核)中的字符串,格式化发生在用户空间中。
bpftrace 还支持以下格式字符串扩展:
说明符 说明
--------------------------------------
%f 微秒为十进制数,在左侧补零
例子:
# bpftrace -e 'i:s:1 { printf("%s\n", strftime("%H:%M:%S", nsecs)); }' Attaching 1 probe... 13:11:22 13:11:23 13:11:24 13:11:25 13:11:26 ^C # bpftrace -e 'i:s:1 { printf("%s\n", strftime("%H:%M:%S:%f", nsecs)); }' Attaching 1 probe... 15:22:24:104033 ^C
25. path(): Return full path
语法:path(struct path *path)
返回参数中 path 结构指针引用的完整路径。 有允许的内核函数列表,可以在探测中使用这个助手。
例子:
# bpftrace -e 'kfunc:filp_close { printf("%s\n", path(args->filp->f_path)); }' Attaching 1 probe... /proc/sys/net/ipv6/conf/eno2/disable_ipv6 /proc/sys/net/ipv6/conf/eno2/use_tempaddr socket:[23276] /proc/sys/net/ipv6/conf/eno2/disable_ipv6 socket:[17655] /sys/devices/pci0000:00/0000:00:1c.5/0000:04:00.1/net/eno2/type socket:[38745] /proc/sys/net/ipv6/conf/eno2/disable_ipv6 # bpftrace -e 'kretfunc:dentry_open { printf("%s\n", path(retval->f_path)); }' Attaching 1 probe... /dev/pts/1 -> /dev/pts/1
26. uptr(): Annotate userspace pointer
语法:uptr(void *p)
将 p 解释为属于用户地址空间的指针。
bpftrace 通常可以推断出指针的地址空间。 但是,存在推理失败的极端情况。 例如,处理用户空间指针的内核函数(类似 const char __user *p 的参数)。 在这些情况下,您需要注释指针。
例子:
# bpftrace -e 'kprobe:do_sys_open { printf("%s\n", str(uptr(arg1))) }' Attaching 1 probe... . state ^C # bpftrace -e 'kprobe:do_sys_openat2 { printf("%s\n", str(uptr(arg1))) }' Attaching 1 probe... proc/859/cmdline /proc/21641/comm /dev/cpuset/foreground/cgroup.procs ^C
27. kptr(): Annotate kernelspace pointer
语法: kptr(void *p)
将 p 解释为属于内核地址空间的指针。
就像 uptr 一样,您通常只在 bpftrace 错误地推断出指针地址空间时才需要它。
28. macaddr(): Convert MAC address data to text
语法:macaddr(char[6] addr)
这将返回 MAC 地址的规范字符串表示形式。例子:
# bpftrace -e 'kprobe:arp_create { printf("SRC %s, DST %s\n", macaddr(sarg0), macaddr(sarg1)); }' SRC 18:C0:4D:08:2E:BB, DST 74:83:C2:7F:8C:FF ^C
29. cgroup_path(): Convert cgroup id to cgroup path
语法:cgroup_path(int cgroupid, string filter)
将给定的 cgroup id 转换为该 id 所在的每个 cgroup 层次结构对应的 cgroup 路径。由于转换是在用户空间中完成的,因此生成的对象只能用于打印。
可选地,可以将字符串文字作为第二个参数来过滤要查找的 cgroup 层次结构(解释为通配符表达式)。
例子:
# bpftrace -e 'BEGIN { print(cgroup_path(5386)); }' Attaching 1 probe... unified:/user.slice/user-1000.slice/session-3.scope
30. bswap: Reverse byte order
语法: bswap(uint[8|16|32|64] n)
反转整数 n 中字节的顺序。 如果是 8 位整数,则返回 n 而不进行修改。 返回类型是与 n 宽度相同的无符号整数。
例子:
# bpftrace -e 'BEGIN { $i = (uint32)0x12345678; printf("Reversing byte order of 0x%x ==> 0x%x\n", $i, bswap($i)); }' Attaching 1 probe... Reversing byte order of 0x12345678 ==> 0x78563412
31. skb_output: Write skb 's data section into a PCAP file
语法: uint32 skboutput(const string path, struct sk_buff *skb, uint64 length, const uint64 offset)
将 sk_buff skb 的数据段写入 path 中的一个 PCAP 文件,从 offset 开始到 offset + length。
PCAP 文件封装在 RAW IP 中,因此不包含以太网标头。 struct skb 中的数据部分在某些内核上下文中可能包含以太网头,您可以将偏移量设置为 14 字节以排除以太网头。
每个数据包的时间戳是通过添加 nsecs 和启动时间来确定的,精度在不同的内核上有所不同,请参见 nsecs。
此函数在成功时返回 0,在失败时返回负错误码。
应该增加环境变量 BPFTRACE_PERF_RB_PAGES 以捕获大数据包,否则这些数据包将被丢弃。
例子:
# cat dump.bt kfunc:napi_gro_receive { $ret = skboutput("receive.pcap", args->skb, args->skb->len, 0); } kfunc:dev_queue_xmit { // setting offset to 14, to exclude ethernet header $ret = skboutput("output.pcap", args->skb, args->skb->len, 14); printf("skboutput returns %d\n", $ret); } # export BPFTRACE_PERF_RB_PAGES=1024 # bpftrace dump.bt ... # tcpdump -n -r ./receive.pcap | head -3 reading from file ./receive.pcap, link-type RAW (Raw IP) dropped privs to tcpdump 10:23:44.674087 IP 22.128.74.231.63175 > 192.168.0.23.22: Flags [.], ack 3513221061, win 14009, options [nop,nop,TS val 721277750 ecr 3115333619], length 0 10:23:45.823194 IP 100.101.2.146.53 > 192.168.0.23.46619: 17273 0/1/0 (130) 10:23:45.823229 IP 100.101.2.146.53 > 192.168.0.23.46158: 45799 1/0/0 A 100.100.45.106 (60)
32. pton(): Convert text IP address to byte array
语法: pton(const string *addr)
这会将 IPv4 或 IPv6 地址的文本表示形式转换为字节数组。 pton 根据给定的参数中的'.'或':' 推断出地址族。 当我们需要选择具有特定 IP 地址的数据包时,pton 就派上用场了。
例子:
# bpftrace -e 'tracepoint:tcp:tcp_retransmit_skb { if (args->daddr_v6[0] == pton("::1")[0]) { printf("first octet matched\n"); } }' Attaching 1 probe... first octet matched ^C # bpftrace -e 'tracepoint:tcp:tcp_retransmit_skb { if (args->daddr[0] == pton("127.0.0.1")[0]) { printf("first octet matched\n"); } }' Attaching 1 probe... first octet matched ^C
33. strerror: Get error message for errno value
语法: strerror(uint64 error)
将给定的 errno 代码转换为描述错误的字符串。 结果只能用于打印,因为转换是在用户空间中完成的。
例子:
# bpftrace -e '#include <linux/errno.h> BEGIN { print(strerror(EPERM)); }' //报找不到 strerror 函数 Attaching 1 probe... Operation not permitted
八、Map Functions
映射是特殊的 BPF 数据类型,可用于存储计数、统计信息和直方图。 每当使用 @ 时,它们也用于上一节中讨论的某些变量类型:2.1. Global、2.2. Per-Thread、3. @[]: Associative Arrays
当 bpftrace 退出时,将打印所有maps。 例如(在以下部分中介绍的 count() 函数):
# bpftrace -e 'kprobe:vfs_read { @[comm] = count(); }' Attaching 1 probe... ^C @[systemd]: 6 @[snmpd]: 321 @[snmp-pass]: 374 ...
map是在 Ctrl-C 结束程序后打印的。 如果您不希望在退出时自动打印的map,您可以添加一个 END 块来清除maps。 例如:
END
{
clear(@start);
}
补充实验:
# bpftrace -e 'kprobe:vfs_read { @count = count(); }' Attaching 1 probe... ^C @count: 200 # bpftrace -e 'kprobe:vfs_read { @count = count(); } END { clear(@count); }' //成功 Attaching 2 probes... ^C @count: 0 # bpftrace -e 'kprobe:vfs_read { @[comm] = count(); } END { clear(@[comm]); }' //失败 stdin:1:46-60: ERROR: The map passed to clear() should not be indexed by a key kprobe:vfs_read { @[comm] = count(); } END { clear(@[comm]); } ~~~~~~~~~~~~~~ # bpftrace -e 'kprobe:vfs_read { @[comm] = count(); } END { clear(@); }' //成功但是什么都不打印 Attaching 2 probes... ^C
1. Builtins
count() - 计算此函数被调用的次数 sum(int n) - 对值求和 avg(int n) - 平均值 min(int n) - 记录看到的最小值 max(int n) - 记录看到的最大值 stats(int n) - 返回此值的计数、平均值和总计 hist(int n) - 生成 n 值的 log2 直方图 lhist(int n, int min, int max, int step) - 生成 n 值的线性直方图 delete(@x[key]) - 删除作为参数传入的map元素 print(@x[, top [, div]]) - 打印map,可选择仅顶部条目并带有除数 print(value) - 打印一个值 clear(@x) - 从map中删除所有键 zero(@x) - 将所有map值设置为零
其中一些是异步的:内核将事件排队,但一段时间后(毫秒)它在用户空间中处理。 异步操作是:map 上的 print()、clear() 和 zero()。
2. count(): Count
语法: @counter_name[optional_keys] = count()
这是使用 BPF 映射实现的。例如,@reads:
# bpftrace -e 'kprobe:vfs_read { @reads = count(); }' Attaching 1 probe... ^C @reads: 119 ...
这表明在跟踪时有 119 次调用 vfs_read()。
下一个示例包括 comm 变量作为键,以便该值按每个进程名称细分。 例如,@reads[comm]:
# bpftrace -e 'kprobe:vfs_read { @reads[comm] = count(); }' Attaching 1 probe... ^C @reads[sleep]: 4 @reads[bash]: 5 @reads[ls]: 7 @reads[snmp-pass]: 8 @reads[snmpd]: 14 @reads[sshd]: 14
3. sum(): Sum
语法: @counter_name[optional_keys] = sum(value)
这是使用 BPF 映射实现的。例如,@bytes[comm]:
# bpftrace -e 'kprobe:vfs_read { @bytes[comm] = sum(arg2); }' //arg2 是第3个参数 Attaching 1 probe... ^C @bytes[bash]: 7 @bytes[sleep]: 4160 @bytes[ls]: 6208 @bytes[snmpd]: 20480 @bytes[snmp-pass]: 65536 @bytes[sshd]: 262144
那是通过 vfs_read() 内核函数对请求的字节求和,它是读取系统调用的两个可能入口点之一。 要查看读取的实际字节数:
# bpftrace -e 'kretprobe:vfs_read /retval > 0/ { @bytes[comm] = sum(retval); }' //只有 kretprobe 才能和 retval 配合使用 Attaching 1 probe... ^C @bytes[bash]: 5 @bytes[sshd]: 1135 ... @bytes[snmp-pass]: 55681
现在使用过滤器来确保 sum() 只统计返回值为正的调用。 与其他函数一样,出错时返回值可能为负。 每当在 retval 上使用 sum() 时请记住这一点。
4. avg(): Average
语法: @counter_name[optional_keys] = avg(value)
这是使用 BPF 映射实现的。例如,@bytes[comm]:
# bpftrace -e 'kprobe:vfs_read { @bytes[comm] = avg(arg2); }' Attaching 1 probe... ^C @bytes[bash]: 1 ... @bytes[sshd]: 16384
这是请求读取的平均大小。
5. min(): Minimum
语法: @counter_name[optional_keys] = min(value)
这是使用 BPF 映射实现的。例如,@bytes[comm]:
# bpftrace -e 'kprobe:vfs_read { @bytes[comm] = min(arg2); }' Attaching 1 probe... ^C @bytes[bash]: 1 ... @bytes[sshd]: 16384
这显示了看到的最小值。
6. max(): Maximum
语法: @counter_name[optional_keys] = max(value)
这是使用 BPF 映射实现的。例如,@bytes[comm]:
# bpftrace -e 'kprobe:vfs_read { @bytes[comm] = max(arg2); }' Attaching 1 probe... ^C @bytes[bash]: 1 ... @bytes[sshd]: 16384
这显示了看到的最大值。
7. stats(): Stats
语法: @counter_name[optional_keys] = stats(value)
这是使用 BPF 映射实现的。例如,@bytes[comm]:
# bpftrace -e 'kprobe:vfs_read { @bytes[comm] = stats(arg2); }' Attaching 1 probe... ^C @bytes[bash]: count 7, average 1, total 7 @bytes[sleep]: count 5, average 832, total 4160 ... @bytes[sshd]: count 15, average 16384, total 245760
stats() 函数返回三个统计信息:事件计数、参数值的平均值和参数值的总和。 这类似于同时使用 count()、avg() 和 sum()。
8. hist(): Log2 Histogram
语法: @histogram_name[optional_key] = hist(value)
这是使用 BPF 映射实现的。例子:
8.1. Power-Of-2:
# bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); }' Attaching 1 probe... ^C @bytes: (..., 0) 117 |@@@@@@@@@@@@ | [0] 5 | | [1] 325 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [2, 4) 6 | | [4, 8) 3 | | [8, 16) 495 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [16, 32) 35 |@@@ | [32, 64) 25 |@@ | [64, 128) 21 |@@ | [128, 256) 1 | | [256, 512) 3 | | [512, 1K) 2 | | [1K, 2K) 1 | | [2K, 4K) 2 | |
8.2. Power-Of-2 By Key:
# bpftrace -e 'kretprobe:do_sys_open { @bytes[comm] = hist(retval); }' Attaching 1 probe... ^C @bytes[snmp-pass]: [4, 8) 6 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| @bytes[ls]: [2, 4) 9 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| @bytes[snmpd]: [1] 1 |@@@@ | [2, 4) 0 | | [4, 8) 0 | | [8, 16) 4 |@@@@@@@@@@@@@@@@@@ | [16, 32) 11 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| @bytes[irqbalance]: (..., 0) 15 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [0] 0 | | [1] 0 | | [2, 4) 0 | | [4, 8) 21 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
9. lhist(): Linear Histogram
语法: @histogram_name[optional_key] = lhist(value, min, max, step)
这是使用 BPF 映射实现的。 min 必须是非负数。例子:
# bpftrace -e 'kretprobe:vfs_read { @bytes = lhist(retval, 0, 10000, 1000); }' Attaching 1 probe... ^C @bytes: [0, 1000) 480 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [1000, 2000) 49 |@@@@@ | [2000, 3000) 12 |@ | [3000, 4000) 39 |@@@@ | [4000, 5000) 267 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
10. print(): Print Map
语法: print(@map [, top [, divisor]])
print()函数会打印一个map,类似于 bpftrace 结束时的自动打印。 可以提供两个可选参数:一个 top 成员,以便仅打印 top 条目数(若是0打印所有的),以及一个 divisor 除数,用于除以该值。 几个例子将解释它们的使用。
作为 top 的示例,跟踪 vfs 操作并打印前 5 个:
# bpftrace -e 'kprobe:vfs_* { @[func] = count(); } END { print(@, 5); clear(@); }' //的确是直接使用clear, print里面的map不能带key Attaching 54 probes... ^C @[vfs_getattr]: 91 @[vfs_getattr_nosec]: 92 @[vfs_statx_fd]: 135 @[vfs_open]: 188 @[vfs_read]: 405
最后的 clear() 用于防止在退出时自动打印map。
作为除数的例子,以毫秒为单位按进程名称对 vfs_read() 中的总时间求和:
# bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ {@ms[pid] = sum(nsecs - @start[tid]); delete(@start[tid]); } END { print(@ms, 0, 1000000); clear(@ms); clear(@start); }' ^C @ms[2960]: 0 @ms[1]: 0 @ms[2633]: 0 @ms[5088]: 2 @ms[5129]: 35 @ms[840]: 1676
这个一行将 vfs_read() 持续时间相加单位为纳秒,然后在打印时除以1000000转换为毫秒。 如果没有此功能,如果在求和时尝试除以毫秒(例如,sum((nsecs - @start[tid]) / 1000000)),该值通常会四舍五入为零,而不是按应有的方式累加。
请注意,打印 maps 不同于打印值。 参见 "23. print(): Print Value" 中的解释。
九、Output
1. printf(): Per-Event Output
语法: printf(char *format, arguments)
可以使用 print() 打印每个事件的详细信息。例子:
# bpftrace -e 'kprobe:do_nanosleep { printf("sleep by %d\n", tid); }' Attaching 1 probe... sleep by 3669 sleep by 1396 sleep by 3669 sleep by 1396 [...]
2. interval: Interval Output
语法: interval:s:duration_seconds
例子:
# bpftrace -e 'kprobe:do_sys_openat2 { @opens = @opens + 1; } interval:s:1 { printf("opens/sec: %d\n", @opens); @opens = 0; }' Attaching 2 probes... opens/sec: 16 opens/sec: 2 opens/sec: 2 ^C @opens: 2 //Ctrl+C后会打印全局变量@opens的值,下面是去掉的例子 # bpftrace -e 'kprobe:do_sys_openat2 { @opens = @opens + 1; } interval:s:1 { printf("opens/sec: %d\n", @opens); @opens = 0; } END { clear(@opens); }' Attaching 3 probes... opens/sec: 41 opens/sec: 3 opens/sec: 3 opens/sec: 92 ^C
3. hist(), printf(): Histogram Printing
声明的直方图在程序终止时自动打印出来。 参见 "5. Histograms" 直方图的声明。//但是文档上并没有"5. Histograms"
例子:
# bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); }' Attaching 1 probe... ^C @bytes: (..., 0) 117 |@@@@@@@@@@@@ | [0] 5 | | [1] 325 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [2, 4) 6 | | [4, 8) 3 | | [8, 16) 495 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [16, 32) 35 |@@@ | [32, 64) 25 |@@ | [64, 128) 21 |@@ | [128, 256) 1 | | [256, 512) 3 | | [512, 1K) 2 | | [1K, 2K) 1 | | [2K, 4K) 2 | |
也可以使用 print() 函数按需打印直方图。 例如:
# bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); } interval:s:1 { print(@bytes); clear(@bytes); }' //一秒打印一次,Ctrl+C后还打印一次 [...] # bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); } interval:s:1 { print(@bytes); clear(@bytes); } END { clear(@bytes);}' //去掉 Ctrl+C后的一次打印
4. SIGUSR1: On-Demand Output
收到 SIGUSR1 信号后,bpftrace 会将所有映射(maps)打印到标准输出。
例子:
# bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); }' & # kill -s USR1 $(pidof bpftrace)
一个在后台,一个在前台,每kill一次就会打印一次。
十、BTF Support
如果内核有 BTF,则内核类型自动可用,无需包含额外的头文件即可使用它们。 为了允许用户在脚本中检测到这种情况,如果检测到 BTF,则定义预处理器宏 BPFTRACE_HAVE_BTF。 有关其用法的示例,请参见 tools/。
将 BTF 用于 vmlinux 的要求:
Linux 4.18+ CONFIG_DEBUG_INFO_BTF=y;编译需要带有 pahole v1.13+ 的 dwarves;支持 BTF 的 bpftrace v0.9.3+(使用 libbpf v0.0.4+ 构建)
将 BTF 用于内核模块的附加要求:
Linux 5.11+ CONFIG_DEBUG_INFO_BTF_MODULES=y;编译需要带有 pahole v1.19+ 的 dwarves
看内核文档 "BPF Documentation" https://www.kernel.org/doc/html/latest/bpf/btf.html 这个关于 BPF&BTF 的章节查看更多关于BTF的信息。################
请注意,如果 bpftrace 程序包含重新定义某些 BTF 类型的用户定义类型,则 BTF 类型不可用于 bpftrace 程序。 在这里,“用户定义类型”也是通过包含的标头引入的类型。 因此,如果您在 bpftrace 程序中包含一个内核头文件,它很可能会定义一些内核类型并且 BTF 将无法用于您的程序(并且您必须手动定义/包含所有必要的类型) .
十一、Advanced Tools
# ./xfsdist.py -h usage: xfsdist.py [-h] [-T] [-m] [-p PID] [interval] [count] Summarize XFS operation latency positional arguments: interval output interval, in seconds count number of outputs optional arguments: -h, --help show this help message and exit -T, --notimestamp don't include timestamp on interval output -m, --milliseconds output in milliseconds -p PID, --pid PID trace this PID only examples: ./xfsdist # show operation latency as a histogram ./xfsdist -p 181 # trace PID 181 only ./xfsdist 1 10 # print 1 second summaries, 10 times ./xfsdist -m 5 # 5s summaries, milliseconds
bcc 版本是 131 行代码。 bpftrace 版本为 22。
十二、Errors
1. Looks like the BPF stack limit of 512 bytes is exceeded
对许多数据项进行操作的 BPF 程序可能会达到此限制。 您可以尝试将许多事情保持在限制范围内:
(1) 找到减少程序中使用的数据大小的方法。 例如,避免不必要的字符串:使用 pid 而不是 comm。 使用更少的map keys。
(2) 将您的程序拆分到多个探测器上。
(3) 检查 Linux 中 BPF 堆栈限制的状态(将来可能会增加,可能作为可调)。
(4) (高级):运行 -d 并检查 LLVM IR,并寻找优化 src/ast/codegen_llvm.cpp 的方法。
2. Kernel headers not found
bpftrace 的某些功能需要内核头文件,默认在以下位置搜索:
/lib/modules/$(uname -r)
可以使用环境变量 BPFTRACE_KERNEL_SOURCE 和 BPFTRACE_KERNEL_BUILD 覆盖默认搜索目录(如果它是 out-of-tree Linux 内核构建)。
注:Android设备中默认是没有导出的!
# bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); } interval:s:1 { print(@bytes); clear(@bytes); }' //一秒打印一次,Ctrl+C后还打印一次
[...]
# bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); } interval:s:1 { print(@bytes); clear(@bytes); } END { clear(@bytes);}' //去掉 Ctrl+C后的一次打印
4. SIGUSR1: On-Demand Output
收到 SIGUSR1 信号后,bpftrace 会将所有映射(maps)打印到标准输出。
例子: