目录
- 前言
- 一、名词解释
- 二、使用示范
- 三、语法
- 四、探针类型
- 五、变量
- 六、函数
- 1. 内置函数
- 2. printf():格式化打印
- 3. time():打印时间
- 4. join():打印字符串数组
- 5. str():打印字符串
- 6. ksym()
- 7. usym()
- 8. kaddr()
- 9. uaddr()
- 10. reg()
- 11. buf(void *d, int len)
- 12. system()
- 13. exit()
- 14. cgroupid()
- 15. ntop()
- 16. kstack()
- 17. ustack():
- 18. cat():查看文件内容
- 19. signal():向当前进程发送一个信号
- 20. strncmp():字符串比较
- 21. override():重写返回值
- 22. sizeof()
- 23. print()
- 24. strftime():格式化时间戳
- 25. path():返回完整路径
- 26. uptr():注解用户地址
- 27. kptr():注解内核地址
- 28. macaddr():将mac地址转为文本
- 29. cgroup_path():将cgroupid转为路径
- 七、映射表操作函数
前言
本文主要介绍bpftrace的使用及语法规则,主要内容来自于官网的文档,以及使用过程中遇到的一些问题;本文将不涉及ebpf概念、框架介绍等。
一、名词解释
术语 | 简介 |
---|---|
BPF | 伯克利数据包过滤器:最初开发用于优化数据包过滤器处理的核心技术(例如,tcpdump表达式) |
eBPF | 增强型BPF:一种扩展BPF的内核技术,它可以在任何事件上执行更通用的程序,如bpftrace程序,eBPF通常被称为BPF |
probe | 软件或硬件中的一种插装点,用于生成可执行bpftrace程序的事件 |
tracepoints | 内核用于提供静态插桩点的技术 |
kprobes | 内核用于进行函数动态追踪的技术 |
uprobes | 内核用于动态追踪用户态函数调用的技术 |
USDT | 用户程序自己定义的静态插桩点(User Statically-Defined Tracing) |
BPF map | BPF的内存对象,bpftrace用它来创建一些高级对象 |
BTF | BPF类型格式:对与BPF程序/映射相关的调试信息进行编码的元数据格式。 |
二、使用示范
1. help
使用bpftrace命令行输出帮助信息(bpftrace或bpftrace --help)
# 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
...
2. Hello World
注: bpftrace的执行(bpf程序的注入)需要root用户。
#bpftrace -e 'BEGIN { printf("hello world!\n"); }'
Attaching 1 probe...
hello world!
3. One-Liners程序
使用-e选项指定一个程序,用于构造单行程序,类似awk语法,下例打印了进入睡眠状态的进程:
# bpftrace -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s enter sleeping\n", comm); }'
Attaching 1 probe...
GoImcore enter sleeping
GoImcore enter sleeping
GoImcore enter sleeping
4. 列出可跟踪点
使用-l
选项列出当前可用追踪点
# bpftrace -l | more
software:alignment-faults:
software:bpf-output:
software:context-switches:
可使用通配符进行查询
# bpftrace -l '*sys_enter*' | more
tracepoint:syscalls:sys_enter_socket
tracepoint:syscalls:sys_enter_socketpair
tracepoint:syscalls:sys_enter_bind
tracepoint:syscalls:sys_enter_listen
使用-v
选项可以列出tracepoint类型跟踪点的参数
# bpftrace -lv tracepoint:syscalls:sys_enter_shmctl
tracepoint:syscalls:sys_enter_shmctl
int __syscall_nr;
int shmid;
int cmd;
struct shmid_ds * buf;
如果BTF可用
(内核选项CONFIG_DEBUG_INFO_BTF=y,查看有无/sys/kernel/btf/vmlinux验证),也可以查看结构体的定义,如:
# bpftrace -lv "struct path"
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
5. 调试输出-d
可以使用-d
选项调试bpftrace程序,此时程序不会运行,可以使用```-dd``获得更多调试信息:
# bpftrace -d -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s enter sleeping\n", comm); }'
#include <linux/types.h>
Program
tracepoint:syscalls:sys_enter_nanosleep
call: printf
string: %s enter sleeping\n
builtin: comm
; ModuleID = 'bpftrace'
source_filename = "bpftrace"
6. 输出详情
使用-v
选项获得更多程序运行时的信息:
# bpftrace -v -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s enter sleeping\n", comm); }'
Attaching 1 probe...
Program ID: 85
Bytecode:
0: (bf) r6 = r1
1: (b7) r1 = 0
2: (7b) *(u64 *)(r10 -40) = r1
last_idx 2 first_idx 0
regs=2 stack=0 before 1: (b7) r1 = 0
3: (7b) *(u64 *)(r10 -32) = r1
4: (7b) *(u64 *)(r10 -24) = r1
5: (7b) *(u64 *)(r10 -16) = r1
6: (7b) *(u64 *)(r10 -8) = r1
7: (bf) r1 = r10
8: (07) r1 += -16
9: (b7) r2 = 16
...
7. 预处理选项
使用 -I
选项帮助bpftrace程序寻找头文件位置(与gcc相似),使用--include
选项包含头文件,可多次使用:
#bpftrace -I /tmp/include test.bt
# bpftrace -e 'kprobe:vfs_open { printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name)); }'
stdin:1:45-66: ERROR: Unknown struct/union: 'struct path'
kprobe:vfs_open { printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name)); }
# 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: /
open path: status
open path: status
8. 环境变量
使用示例:
# BPFTRACE_MAP_KEYS_MAX=1024 bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s", comm); join(args->argv); }'
Attaching 1 probe...
- BPFTRACE_STRLEN
默认值64,使用str()获取BPF stack分配的字符串时返回的长度,当前可以设置的最大值为200,支持更大字符长度的问题仍在讨论中。 - BPFTRACE_NO_CPP_DEMANGLE
默认为0,默认启用了用户空间堆栈跟踪中的C++符号还原功能,将此环境变量设置为1,可以关闭此功能。 - BPFTRACE_MAP_KEYS_MAX
单个map中存储的最大key数量,默认4096。 - BPFTRACE_MAX_PROBES
bpftrace程序支持attach的钩子数量,默认512。 - BPFTRACE_CACHE_USER_SYMBOLS
默认情况下bpftrace缓存符号的解析结果,如果ASLR没有开启(Address Space Layout Randomization),仅仅跟踪一个程序的时候,开启此选项可以获得性能上的提升。 - BPFTRACE_BTF
BTF文件的路径,默认为None - BPFTRACE_MAX_BPF_PROGS
bpftrace可构造的最大BPF程序数量,默认值为512.
9. 其它选项
- 使用
--version
获取版本信息 - 使用
--no-warnings
关闭警告 - 使用
-f
选项指定输出信息格式,比如json
# bpftrace -f json -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s enter sleeping\n", comm); }'
{"type": "attached_probes", "data": {"probes": 1}}
{"type": "printf", "data": "GoImcore enter sleeping\n"}
{"type": "printf", "data": "GoImcore enter sleeping\n"}
- 使用
-o
输出到文本
# bpftrace -f json -o ./sleep.json -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s enter sleeping\n", comm); }'
^C
# cat sleep.json
{"type": "attached_probes", "data": {"probes": 1}}
{"type": "printf", "data": "GoImcore enter sleeping\n"}
{"type": "printf", "data": "GoImcore enter sleeping\n"}
三、语法
1. 程序结构{…}
格式:probe[, probe, ...] /filter/ { action }
一个bpftrace程序可以有多个动作块,可使用过滤器。
# bpftrace -e 'kprobe:do_sys_open { printf("opening: %s\n", str(arg1)); }'
Attaching 1 probe...
opening: /proc/1804/cmdline
...
2. 过滤/…/
格式: /filter/
在探针之后添加过滤器,探针仍然会触发
,在满足过滤条件之后才会执行动作。
# bpftrace -e 'kprobe:vfs_read /comm == "bash"/ { printf("read %d bytes\n", arg2); }'
Attaching 1 probe...
read 256 bytes
read 728 bytes
3. 注释//, /**/
// single-line comment
/*
* multi-line comment
*/
4. 常量
支持整数、字符和字符串常量:
# bpftrace -e 'BEGIN { printf("%lu %lu %lu", 1000000, 1e6, 1_000_000)}'
Attaching 1 probe...
1000000 1000000 1000000
5. c结构体访问:->
# bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
Attaching 1 probe...
Xorg /proc/1996/cmdline
tracepoint类型的跟踪点可使用args
参数中访问filename
成员,通过args->
格式;如果是kprobe类型跟踪点,则访问示例如下:
# cat path.bt
#!/usr/bin/bpftrace
#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
使用了动态跟踪点对内核函数vfs_open进行了追踪,为了访问path和dentry结构,需要包含一些内核头文件。
6. 结构体定义: struct
// from fs/namei.c:
struct nameidata {
struct path path;
struct qstr last;
// [...]
};
一些情况下,内核的头文件包中没有包含需要的结构体,你可以在bpftrace工具中手动定义结构体。
7. 三元操作符 ?::
语法同C语言,如下:
# bpftrace -e 'tracepoint:syscalls:sys_exit_read { @error[args->ret < 0 ? - args->ret : 0] = count(); }'
Attaching 1 probe...
^C
@error[11]: 51
@error[0]: 1744
8. 条件语句 if () {…} else {…}
bpftrace条件语句中目前仅支持if/else,暂不支持else if:
# bpftrace -e 'tracepoint:syscalls:sys_enter_read { @read = count(); if (args->count > 1024) { @large = count(); } }'
Attaching 1 probe...
^C
@large: 240
@read: 1206
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
10. 自增、自减++、–
++
和--
可以用于maps或者变量的自增/自减,需要注意的是maps没有定义的话值会被隐式的初始化为0。变量需要初始化之后才能使用这些操作符。
# bpftrace -e 'BEGIN { $x = 0; $x++; printf("x:%d\n", $x); }'
Attaching 1 probe...
x:1
# bpftrace -e 'k:vfs_read { @++ }'
Attaching 1 probe...
^C
@: 633
带关键词的map:
# bpftrace -e 'k:vfs_read { @[probe]++ }'
Attaching 1 probe...
^C
@[kprobe:vfs_read]: 131
11. 数组访问[]
可以使用数组操作符[]
访问一维常量数组;
12. 整形强转
整形内部为uint64,可以强制修改为以下内置类型:
(u)int8,(u)int16,(u)int32,(u)int64:
# bpftrace -e 'BEGIN { $x = 1<<16; printf("%d %d\n", (uint16)$x, $x); }'
Attaching 1 probe...
0 65536
13. while循环
内核版本>=5.3
,bpftrace支持while循环,循环可以使用continue
和break
来操作:
# bpftrace -e 'i:ms:100 { $i = 0; while ($i <= 100) { printf("%d ", $i); $i++} exit(); }'
14. 提前结束:return
return
关键字用于提前结束probe,而exit()
则用于退出bpftrace(包含一个或多个probe)。
15. 元组( ,)
使用.
+index来访问元组,元组一经定义就不可以改变,同样也需要高版本内核支持:
# bpftrace -e 'BEGIN { $t = (1, 2, "string"); printf("%d %s\n", $t.1, $t.2); }'
四、探针类型
1. kprobe/kretprobe
语法:
kprobe:function_name[ + offset]
kretprobe:function_name
使用了内核的kprobe能力(https://www.kernel.org/doc/Documentation/kprobes.txt),在进入函数时触发kprobe,函数退出时触发kretprobe。
示例:
# bpftrace -e 'kprobe:do_nanosleep { printf("%s enter sleep\n", comm); }'
Attaching 1 probe...
dockerd enter sleep
也可以在probe函数内部使用偏移量:
# 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编译的时候使用了ALLOW_UNSAFE_PROBE
选项,可以使用–unsafe选项来跳过此检查。
对于probe类型探针,可以使用argN
(从0开始)的方式来访问探测点参数,对于retprobe则使用retval来获取返回值。
# 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
[...]
对于结构体的访问如下:
# cat path.bt
#!/usr/bin/bpftrace
#include <linux/path.h>
#include <linux/dcache.h>
kprobe:vfs_open
{
printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name));
}
当内核支持BTF时,甚至不需要包含结构体的头文件。
2. uprobe/uretprobe
语法
uprobe:library_name:function_name[+offset]
uprobe:library_name:address
uretprobe:library_name:function_name
使用了内核的uprobe特性,可以用objdump或者bpftrace -l来获取探测点。
# objdump -tT /bin/bash | grep readline
0000000000139220 g DO .bss 0000000000000008 Base rl_readline_state
00000000000c0b20 g DF .text 0000000000000352 Base readline_internal_char
00000000000bfe90 g DF .text 000000000000019c Base readline_internal_setup
000000000008bf40 g DF .text 000000000000009a Base posix_readline_initialize
# # bpftrace -l 'u:/bin/bash' | grep readline
uprobe:/bin/bash:initialize_readline
uprobe:/bin/bash:pcomp_set_readline_variables
uprobe:/bin/bash:posix_readline_initialize
uprobe:/bin/bash:readline
uprobe也可以使用虚拟地址作为探测点:
# objdump -tT /bin/bash | grep main
000000000002fe90 g DF .text 000000000000199e Base main
# bpftrace -e 'uprobe:/bin/bash:0x2fe90 { printf("main called!\n"); }'
Attaching 1 probe...
也可以使用探测点加上偏移的方式:
# objdump -d /root/test
[...]
0000000000401132 <foo>:
401132: 55 push %rbp
401133: 48 89 e5 mov %rsp,%rbp
401136: 48 89 7d f8 mov %rdi,-0x8(%rbp)
40113a: 48 89 75 f0 mov %rsi,-0x10(%rbp)
40113e: 48 8b 45 f8 mov -0x8(%rbp),%rax
[...]
# bpftrace -e 'u:/root/test:foo+4 {printf("in here\n");}'
Attaching 1 probe...
in here
in here
地址的对齐会通过指令边界进行检查,如果不对齐,将会probe将会添加失败,如果bpftrace编译时使用了ALLOW_UNSAFE_PROBE选项,也可以使用–unsafe选项来跳过此检查。
# 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 -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选项,还可以在任意地址上放置uprobes。当二进制文件被strip时,这可能会派上用场。
3. tracepoint
使用了内核的静态探测点,对于参数的访问方式为args->
# bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
Attaching 1 probe...
vmware-vmx /proc/meminfo
每个跟踪点可用的成员可以在/sys目录下进行查看:
# cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_openat/format
name: sys_enter_openat
ID: 622
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))
4. usdt
USDT(user-level statically defined tracing),提供了用户空间版的跟踪点机制,linux对USDT的支持,最早来自于SytemTap项目的跟踪器;给用户程序添加USDT探针,有两种可选方式:
1)使用systemtap-sdt-dev包提供的头文件和工具
2)使用Facebook的Folly C++库
为应用程序添加USDT后,可使用bpftrace对跟踪点进行探测,语法:
usdt:binary_path:probe_name
usdt:binary_path:[probe_namespace]:probe_name
usdt:library_path:probe_name
usdt:library_path:[probe_namespace]:probe_name
如果探测名称是唯一的,也可以省略探测命名空间:
# bpftrace -e 'usdt:/root/tick:loop { printf("hi\n"); }'
Attaching 1 probe...
hi
hi
hi
参数使用argN
进行访问:
# bpftrace -e 'usdt:/root/tick:loop /arg1 > 2/ { printf("%s: %d\n", str(arg0), arg1); }'
my string: 3
my string: 4
my string: 5
my string: 6
^C
5. profile
使用profile进行事件采样:
profile:hz:rate
profile:s:rate
profile:ms:rate
profile:us:rate
profile使用了perf_events能力,如:
# bpftrace -e 'profile:hz:99 { @[tid] = count(); }'
Attaching 1 probe...
^C
@[1280]: 1
@[866]: 1
@[58278]: 1
6. interval
语法:
interval:ms:rate
interval:s:rate
interval:us:rate
interval:hz:rate
这只在一个CPU上启动,并可用于生成每间隔的输出,如每秒输出系统调用的数量:
# bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @syscalls = count(); } interval:s:1 { print(@syscalls); clear(@syscalls); }'
Attaching 2 probes...
@syscalls: 18141
@syscalls: 34272
@syscalls: 48646
7. software
语法:
software:event_name:count
software:event_name:
这些是Linux内核提供的预定义软件事件,通常通过perf实用程序进行跟踪。它们类似于跟踪点,但只有十几个,记录在perf_event_open(2)手册页中。事件名称如下:
- cpu-clock或cpu:报告CPU时钟,per-cpu的高分辨率定时器
- task-clock:指定正在运行的任务的时钟计数
- page-faults or faults:报告触发缺页异常的次数
- context-switches or cs:上下文切换,被报告为在内核发生的用户空间事件
- cpu-migrations:报告进程迁移CPU的次数
- minor-faults:报告小缺页中断(触发pagefault时,vma对应的地址空间存在disk中)的次数,不会上报磁盘I/O的情况
- major-faults:报告大缺页中断(触发pagefault时,vma对应的地址空间已经被内核加载到了Page Cache中)的次数
- alignment-faults:报告对齐异常的数量,某些架构支持,当发生未对齐的内存访问时触发
- emulation-faults:统计方针异常的数量,内核有时会捕获未实现的指令,并在用户空间模拟它们
- dummy:一个不重要的占位事件,允许在不需要计数事件的情况下收集此类记录
- bpf-output
下例对每一百个缺页异常的进程名称进行采样:
# bpftrace -e 'software:faults:100 { @[comm] = count(); }'
Attaching 1 probe...
^C
@[QThread]: 1
@[ping]: 1
8. hardware
语法:
hardware:event_name:count
hardware:event_name:
Linux内核提供的预定义硬件事件,通常由perf实用程序跟踪。它们是使用性能监视计数器(PMC)实现的:处理器上的硬件资源。记录在perf_event_open(2)手册页(https://man7.org/linux/man-pages/man2/perf_event_open.2.html)中,事件名称如下:
- cpu-cycles or cycles
- instructions
- cache-references
- cache-misses
- branch-instructions or branches
- branch-misses
- bus-cycles
- frontend-stalls
- backend-stalls
- ref-cycles
计数是探测器的触发器,它将为每个计数事件触发一次。如果未提供计数,则使用默认值。
# bpftrace -e 'hardware:cache-misses:1000000 { @[pid] = count(); }'
Attaching 1 probe...
^C
@[7679]: 1
@[2662]: 1
@[400842]: 1
五、变量
1. 内置变量
- pid - 进程号(kernel tgid)
- tid - 线程号 (kernel pid)
- uid - 用户ID
- gid - 组ID
- nsecs - 纳秒时间戳
- elapsed - 自bpftrace初始化流逝的纳秒数
- cpu - 处理器编号
- comm - 进程名称
- kstack - 内核栈回溯
- ustack - 用户栈回溯
- arg0, arg1, …, argN. - 跟踪函数的参数
- sarg0, sarg1, …, sargN. - 跟踪函数的参数 (for programs that store arguments on the stack); assumed to be 64 bits wide
- retval - 被跟踪函数的返回值
- func - 被跟踪函数的名称
- probe - 跟踪点全名
- curtask - 当前的task_struct(u64)
- rand - 随机数(u32)
- cgroup - 当前进程的cgroup ID
- cpid - Child pid(u32),仅
-c command
使用时有效 - $1, $2, …, $N, $#. bpftrace程序的入参变量
2. 基本变量: @、$
@全局变量
@线程局部变量[tid]
$临时变量
2.1 全局变量
# bpftrace -e 'BEGIN { @start = nsecs; }
kprobe:do_nanosleep /@start != 0/ { printf("at %d ms: sleep\n", (nsecs - @start) / 1000000); }'
Attaching 2 probes...
at 42 ms: sleep
at 43 ms: sleep
at 314 ms: sleep
^C
@start: 601563424957305
2.2 线程局部变量
# 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
slept for 1009 ms
slept for 2002 ms
[...]
2.3 临时变量
如$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
3. 关联数组@[ ]
语法:
@关联数组名[key_name] = value
@关联数组名[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
[...]
5. 时间戳nsecs
通过 bpf_ktime_get_ns()实现,如上例
6. 内核栈kstack
也可通过kstack()使用:
# bpftrace -e 'kprobe:ip_output { @[kstack] = count(); }'
Attaching 1 probe...
^C
@[
ip_output+1
__ip_queue_xmit+378
ip_queue_xmit+16
__tcp_transmit_skb+1335
__tcp_send_ack.part.0+203
tcp_send_ack+28
__tcp_ack_snd_check+60
tcp_rcv_established+1426
tcp_v4_do_rcv+320
tcp_v4_rcv+3063
ip_protocol_deliver_rcu+48
ip_local_deliver_finish+72
ip_local_deliver+115
ip_rcv_finish+133
ip_rcv+188
__netif_receive_skb_one_core+135
__netif_receive_skb+24
netif_receive_skb_internal+69
napi_gro_receive+255
e1000_receive_skb+207
e1000_clean_rx_irq+523
e1000e_poll+122
net_rx_action+314
__softirqentry_text_start+225
irq_exit+174
do_IRQ+90
ret_from_intr+0
cpuidle_enter_state+197
cpuidle_enter+46
call_cpuidle+35
do_idle+477
cpu_startup_entry+32
start_secondary+359
secondary_startup_64+164
]: 1
@[
ip_output+1
__ip_queue_xmit+378
ip_queue_xmit+16
__tcp_transmit_skb+1335
tcp_write_xmit+962
__tcp_push_pending_frames+55
tcp_push+253
tcp_sendmsg_locked+3189
tcp_sendmsg+45
inet_sendmsg+67
sock_sendmsg+94
sock_write_iter+147
new_sync_write+293
__vfs_write+41
vfs_write+185
ksys_write+103
__x64_sys_write+26
do_syscall_64+87
entry_SYSCALL_64_after_hwframe+68
]: 2
7. 用户栈追踪ustack
也可通过ustack()使用:
# bpftrace -e 'kprobe:do_sys_open { @[ustack] = count(); }'
Attaching 1 probe...
^C
@[
__open64+212
0x75746174732f3535
]: 1
@[
__open64+212
0x75746174732f3331
]: 1
8. 位置参数
格式: $1,$2,...,$N,$#
- bpftrace程序的位置参数,也称为命令行参数。如果参数(完全)是数字,则可以将其用作数字。否则必须在str()调用中用作字符串。如果使用了未提供的参数,则数字上下文默认为零,字符串上下文默认为“”。位置参数也可以在探测参数中使用,并将被视为字符串参数。
- 如果在str()中使用位置参数,它将被解释为指向实际给定字符串文字的指针,从而允许对其执行指针算术。只允许添加一个小于或等于所提供字符串长度的常数
- 这允许编写使用基本参数来更改其行为的脚本。如果开发的脚本需要更复杂的参数处理,那么它可能更适合bcc,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)
在脚本中使用:
#!/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);
}
六、函数
1. 内置函数
- 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) - 执行系统命令
- exit() - 退出bpftrace
- cgroupid(char *path) - 解析cgroupID
- kstack([StackMode mode, ][int level]) - 内核栈回溯
- ustack([StackMode mode, ][int level]) - 用户栈回溯
- ntop([int af, ]int|char[4|16] 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指向的16进制内容
- sizeof(…) - 返回一个类型或语句的尺寸Return size of a type or expression
- print(…) - 使用默认格式打印一个非map的值
- strftime(char *format, int nsecs) - 返回格式化的时间戳
- path(struct path *path) - 返回完整路径
- uptr(void *p) - 注释为用户空间指针
- kptr(void *p) - 注释为内核空间指针
- macaddr(char[6] addr) - 转换mac地址
2. printf():格式化打印
类似于C风格的打印函数:
# 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/preconv
man called /usr/local/sbin/preconv
man called /usr/local/bin/preconv
man called /usr/sbin/preconv
man called /usr/bin/preconv
man called /apps/nflx-bash-utils/bin/tbl
[...]
3. time():打印时间
使用指定格式打印时间,需要libc strftime(3)
支持。
需要注意的是此时间打印的是用户空间程序处理时间队列的时间,而不是bpf程序调用时的时间
# bpftrace -e 'kprobe:do_nanosleep { time("%H:%M:%S\n"); }'
07:11:03
07:11:09
4. join():打印字符串数组
join()会将字符串数组与一个空格字符连接起来,并将其打印出来,以分隔符分隔。默认的分隔符(如果没有提供)是空格字符。当前版本不返回字符串,因此不能在printf()中用作参数。
# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'
Attaching 1 probe...
ls --color=auto
5. str():打印字符串
str(char *s, [int length])
返回字符串指针,length参数可选,用于限制s的长度;字符串默认长度为64,可使用BPFTRACE_STRLEN
环境变量进行更改;
# 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
6. ksym()
bpftrace -e 'kprobe:do_nanosleep { printf("%s\n", ksym(reg("ip"))); }'
Attaching 1 probe...
do_nanosleep
do_nanosleep
7. usym()
# bpftrace -e 'uprobe:/bin/bash:readline { printf("%s\n", usym(reg("ip"))); }'
Attaching 1 probe...
readline
readline
readline
8. kaddr()
格式: kaddr(const char *name)
bpftrace -e 'BEGIN { printf("%s\n", str(*kaddr("usbcore_name"))); }'
Attaching 1 probe...
usbcore
9. uaddr()
uaddr函数返回指定符号的地址,在程序编译期间查找符号,不能动态使用。
格式:
- u64 *uaddr(符号名) (默认)
- u64 *uaddr(符号名)
- u32 *uaddr(符号名)
- u16 *uaddr(符号名)
- u8 *uaddr(符号名)
支持的探针类型:u(ret)probe、USDT
# 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
10. reg()
# bpftrace -e 'kprobe:tcp_sendmsg { @[ksym(reg("ip"))] = count(); }'
Attaching 1 probe...
^C
@[tcp_sendmsg]: 8
11. buf(void *d, int len)
返回一个十六进制字符串,由于缓冲区长度不可预测,因此总是需要长度参数来限制读取的字节数。默认最大读取字节数为64,也可使用BPFTRACE_STRLEN环境变量进行调整;
如果字节数在[32, 126]之间,使用他们的ASCII字符进行输出,其余的字节使用16进制(如\x00
)。
# 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
12. system()
让bpftrace执行一个系统命令,此行为不安全,因此使用时需要指定--unsafe
选项:
# bpftrace --unsafe -e 'kprobe:do_nanosleep { system("ps -p %d\n", pid); }'
Attaching 1 probe...
PID TTY TIME CMD
1339 ? 00:00:15 iscsid
PID TTY TIME CMD
1339 ? 00:00:15 iscsid
PID TTY TIME CMD
1518 ? 00:01:07 irqbalance
PID TTY TIME CMD
1339 ? 00:00:15 iscsid
^C
13. exit()
退出bpftrace,可以与interval间隔探针相结合,以记录特定持续时间内的统计信息:
# bpftrace -e 'kprobe:do_sys_open { @opens=count(); } interval:s:1 { exit(); }'
Attaching 2 probes...
@opens: 46
14. cgroupid()
返回特定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
/lib64/libc.so.6
/usr/lib/locale/locale-archive
/etc/shadow
^C
另一个终端的行为如下:
# echo $$ > /sys/fs/cgroup/unified/mycg/cgroup.procs
# cat /etc/shadow
15. ntop()
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
::ffff:216.58.194.164
::ffff:216.58.194.164
::ffff:216.58.194.164
bpftrace -e '#include <linux/socket.h>
BEGIN { printf("%s\n", ntop(AF_INET, 0x0100007f));}'
127.0.0.1
^C
16. kstack()
同kstack关键字,可以选择输出格式和栈最大深度,如:
# 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
17. ustack():
同ustack变量,ustack变量
18. cat():查看文件内容
可使用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
19. signal():向当前进程发送一个信号
- 内核版本>=5.3
- 支持的探针类型:k(ret)probes,u(ret)probes,USDT,profile
# bpftrace -e 'kprobe:__x64_sys_execve /comm == "bash"/ { signal(5); }' --unsafe
$ ls
Trace/breakpoint trap (core dumped)
20. strncmp():字符串比较
同C语法格式,如果两个字符串的前n个字节相同,则返回0,否则返回非0:
# 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:gdrv0, tracepoint:syscalls:sys_enter_futex]: 680
@[mpv/ao, tracepoint:syscalls:sys_enter_write]: 1022
@[mpv, tracepoint:syscalls:sys_enter_ioctl]: 2677
21. override():重写返回值
- 内核版本>=4.16
- 探针类型: kprobes
该特性需要内核配置了CONFIG_BPF_KPROBE_OVERRIDE选项,并且目标函数使用ALLOW_ERROR_INJECTION标签,bpftrace不测试被探测函数是否允许错误注入,而是测试是否无法将程序加载到内核。
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)
ioctl(PERF_EVENT_IOC_SET_BPF): Invalid argument
Error attaching probe: 'kprobe:vfs_read'
22. 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 'BEGIN { printf("%d\n", sizeof(struct task_struct)); }'
Attaching 1 probe...
13120
# bpftrace -e 'BEGIN { $x = 3; printf("%d\n", sizeof($x)); }'
Attaching 1 probe...
8
23. print()
使用print打印一个非map变量,如大多数的内置变量和局部变量:
# bpftrace -e 'BEGIN { $t = (1, "string"); print(123); print($t); print(comm) }'
Attaching 1 probe...
123
(1, string)
bpftrace
^C
24. strftime():格式化时间戳
语法:
strftime(const char *format, int nsecs)
返回一个可使用printf打印的格式化时间戳,此时间戳的格式必须被strftime所支持(并不是在内核bpf程序中返回,而是用户空间的时间
)。nsecs
参数为自启动以来的纳秒数。
# 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():返回完整路径
格式:
path(struct 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():注解用户地址
格式:
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
27. kptr():注解内核地址
格式:
kptr(void *p)
类似于uptr,将p注解为内核态地址,通常只有在bpftrace错误地推断出指针地址空间的情况下需要。
28. macaddr():将mac地址转为文本
格式:
macaddr(char [6]addr)
# 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():将cgroupid转为路径
格式:
cgroup_path(int cgroupid, string filter)
将给定的cgroup id转换为id出现在其中的每个cgroup层次结构的相应cgroup路径。因为转换是在用户空间中完成的,所以生成的对象只能用于打印。
# bpftrace -e 'BEGIN { print(cgroup_path(5386)); }'
Attaching 1 probe...
unified:/user.slice/user-1000.slice/session-3.scope
七、映射表操作函数
1. 内置函数
- count() - 统计函数调用次数
- sum(int n) - 求和
- avg(int n) - 求平均值
- min(int n) - 记录变量出现的最小值
- max(int n) - 记录变量出现的最大值
- stats(int n) - 返回变量出现的次数,平均值,总和
- hist(int n) -将值保存为直方图
- lhist(int n, int min, int max, int step) -将值保存为线性直方图
- delete(@x[key]) - 从映射表中删除一个键值对
- print(@x[, top [, div]]) - 打印映射表,可选top(只打印最高的top个)和div(将数值整除后再输出)参数
- print(value) - 打印一个变量
- clear(@x) - 删除映射表中全部键值对
- zero(@x) - 将全部值置为0
2. count()
格式:
@counter_name[optional_keys] = count()
示例:
# bpftrace -e 'kprobe:vfs_* { @vfs_op = count(); }'
Attaching 65 probes...
^C
@vfs_op: 7185
# 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()
格式
@counter_name[optional_keys] = sum(value)
示例:
# bpftrace -e 'kretprobe:vfs_read /retval > 0/ { @bytes[comm] = sum(retval); }'
Attaching 1 probe...
^C
@bytes[bash]: 5
@bytes[sshd]: 1135
@bytes[systemd-journal]: 1699
@bytes[sleep]: 2496
@bytes[ls]: 4583
@bytes[snmpd]: 35549
@bytes[snmp-pass]: 55681
4. avg()
语法:
@counter_name[optional_keys] = avg(value)
示例:
# bpftrace -e 'kprobe:vfs_read { @bytes[comm] = avg(arg2); }'
Attaching 1 probe...
^C
@bytes[bash]: 1
@bytes[sleep]: 832
@bytes[ls]: 886
@bytes[snmpd]: 1706
@bytes[snmp-pass]: 8192
@bytes[sshd]: 16384
5. min()
格式:
@counter_name[optional_keys] = min(value)
示例:
# bpftrace -e 'kprobe:vfs_read { @bytes[comm] = min(arg2); }'
Attaching 1 probe...
^C
@bytes[bash]: 1
@bytes[systemd-journal]: 8
@bytes[snmpd]: 64
@bytes[ls]: 832
@bytes[sleep]: 832
@bytes[snmp-pass]: 8192
@bytes[sshd]: 16384
6. max()
格式:
@counter_name[optional_keys] = max(value)
示例:
# bpftrace -e 'kprobe:vfs_read { @bytes[comm] = max(arg2); }'
Attaching 1 probe...
^C
@bytes[bash]: 1
@bytes[systemd-journal]: 8
@bytes[sleep]: 832
7. stats()
格式:
@counter_name[optional_keys] = stats(value)
示例:
# 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[ls]: count 7, average 886, total 6208
@bytes[snmpd]: count 18, average 1706, total 30718
@bytes[snmp-pass]: count 12, average 8192, total 98304
@bytes[sshd]: count 15, average 16384, total 245760
8. hist()
格式:
@histogram_name[optional_key] = hist(value)
示例:
# 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 |@@
9. lhist()
格式:
@histogram_name[optional_key] = lhist(value, min, max, step)
示例:
# 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 [, top [, divisor]])
print()函数将打印映射表,类似于bpftrace结束时的自动打印。可以提供两个可选参数:一个是top number,这样只打印top number个数目;另一个是divisor,用于分割值。
示例:
# bpftrace -e 'kprobe:vfs_* { @[func] = count(); } END { print(@, 5); clear(@); }'
Attaching 54 probes...
^C
@[vfs_getattr]: 91
@[vfs_getattr_nosec]: 92
@[vfs_statx_fd]: 135
@[vfs_open]: 188
@[vfs_read]: 405
最后使用clear来防止bpftrace结束是对映射表的自动打印。
使用divisor的示例:
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); }'
@ms[2429]: 0
@ms[7679]: 0
@ms[2662]: 1
@ms[7633]: 1
@ms[343161]: 1
@ms[58241]: 1
@ms[7727]: 8503
原文: https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md