本文主要介绍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程序/映射相关的调试信息进行编码的元数据格式。 |
使用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
...
注: bpftrace的执行(bpf程序的注入)需要root用户。
#bpftrace -e 'BEGIN { printf("hello world!\n"); }'
Attaching 1 probe...
hello world!
使用-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
使用-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;
};
可以使用-d
选项调试bpftrace程序,此时程序不会运行,可以使用```-dd``获得更多调试信息:
# bpftrace -d -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s enter sleeping\n", comm); }'
#include
Program
tracepoint:syscalls:sys_enter_nanosleep
call: printf
string: %s enter sleeping\n
builtin: comm
; ModuleID = 'bpftrace'
source_filename = "bpftrace"
使用-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
...
使用 -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
使用示例:
# BPFTRACE_MAP_KEYS_MAX=1024 bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s", comm); join(args->argv); }'
Attaching 1 probe...
--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"}
格式:probe[, probe, ...] /filter/ { action }
一个bpftrace程序可以有多个动作块,可使用过滤器。
# bpftrace -e 'kprobe:do_sys_open { printf("opening: %s\n", str(arg1)); }'
Attaching 1 probe...
opening: /proc/1804/cmdline
...
格式: /filter/
在探针之后添加过滤器,探针仍然会触发
,在满足过滤条件之后才会执行动作。
# bpftrace -e 'kprobe:vfs_read /comm == "bash"/ { printf("read %d bytes\n", arg2); }'
Attaching 1 probe...
read 256 bytes
read 728 bytes
// single-line comment
/*
* multi-line comment
*/
支持整数、字符和字符串常量:
# bpftrace -e 'BEGIN { printf("%lu %lu %lu", 1000000, 1e6, 1_000_000)}'
Attaching 1 probe...
1000000 1000000 1000000
# 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
#include
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结构,需要包含一些内核头文件。
// from fs/namei.c:
struct nameidata {
struct path path;
struct qstr last;
// [...]
};
一些情况下,内核的头文件包中没有包含需要的结构体,你可以在bpftrace工具中手动定义结构体。
语法同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
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
使用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
++
和--
可以用于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
可以使用数组操作符[]
访问一维常量数组;
整形内部为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
内核版本>=5.3
,bpftrace支持while循环,循环可以使用continue
和break
来操作:
# bpftrace -e 'i:ms:100 { $i = 0; while ($i <= 100) { printf("%d ", $i); $i++} exit(); }'
return
关键字用于提前结束probe,而exit()
则用于退出bpftrace(包含一个或多个probe)。
使用.
+index来访问元组,元组一经定义就不可以改变,同样也需要高版本内核支持:
# bpftrace -e 'BEGIN { $t = (1, 2, "string"); printf("%d %s\n", $t.1, $t.2); }'
语法:
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
#include
kprobe:vfs_open
{
printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name));
}
当内核支持BTF时,甚至不需要包含结构体的头文件。
语法
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 :
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时,这可能会派上用场。
使用了内核的静态探测点,对于参数的访问方式为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))
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
使用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
语法:
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
语法:
software:event_name:count
software:event_name:
这些是Linux内核提供的预定义软件事件,通常通过perf实用程序进行跟踪。它们类似于跟踪点,但只有十几个,记录在perf_event_open(2)手册页中。事件名称如下:
# bpftrace -e 'software:faults:100 { @[comm] = count(); }'
Attaching 1 probe...
^C
@[QThread]: 1
@[ping]: 1
语法:
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)中,事件名称如下:
# bpftrace -e 'hardware:cache-misses:1000000 { @[pid] = count(); }'
Attaching 1 probe...
^C
@[7679]: 1
@[2662]: 1
@[400842]: 1
-c command
使用时有效@全局变量
@线程局部变量[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
语法:
@关联数组名[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
[...]
通过 bpf_ktime_get_ns()实现,如上例
也可通过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
也可通过ustack()使用:
# bpftrace -e 'kprobe:do_sys_open { @[ustack] = count(); }'
Attaching 1 probe...
^C
@[
__open64+212
0x75746174732f3535
]: 1
@[
__open64+212
0x75746174732f3331
]: 1
格式: $1,$2,...,$N,$#
# 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);
}
类似于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
[...]
使用指定格式打印时间,需要libc strftime(3)
支持。
需要注意的是此时间打印的是用户空间程序处理时间队列的时间,而不是bpf程序调用时的时间
# bpftrace -e 'kprobe:do_nanosleep { time("%H:%M:%S\n"); }'
07:11:03
07:11:09
join()会将字符串数组与一个空格字符连接起来,并将其打印出来,以分隔符分隔。默认的分隔符(如果没有提供)是空格字符。当前版本不返回字符串,因此不能在printf()中用作参数。
# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'
Attaching 1 probe...
ls --color=auto
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
bpftrace -e 'kprobe:do_nanosleep { printf("%s\n", ksym(reg("ip"))); }'
Attaching 1 probe...
do_nanosleep
do_nanosleep
# bpftrace -e 'uprobe:/bin/bash:readline { printf("%s\n", usym(reg("ip"))); }'
Attaching 1 probe...
readline
readline
readline
格式: kaddr(const char *name)
bpftrace -e 'BEGIN { printf("%s\n", str(*kaddr("usbcore_name"))); }'
Attaching 1 probe...
usbcore
uaddr函数返回指定符号的地址,在程序编译期间查找符号,不能动态使用。
格式:
# 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
# bpftrace -e 'kprobe:tcp_sendmsg { @[ksym(reg("ip"))] = count(); }'
Attaching 1 probe...
^C
@[tcp_sendmsg]: 8
返回一个十六进制字符串,由于缓冲区长度不可预测,因此总是需要长度参数来限制读取的字节数。默认最大读取字节数为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
让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
退出bpftrace,可以与interval间隔探针相结合,以记录特定持续时间内的统计信息:
# bpftrace -e 'kprobe:do_sys_open { @opens=count(); } interval:s:1 { exit(); }'
Attaching 2 probes...
@opens: 46
返回特定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
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
BEGIN { printf("%s\n", ntop(AF_INET, 0x0100007f));}'
127.0.0.1
^C
同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
同ustack变量,ustack变量
可使用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
# bpftrace -e 'kprobe:__x64_sys_execve /comm == "bash"/ { signal(5); }' --unsafe
$ ls
Trace/breakpoint trap (core dumped)
同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
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'
# 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
使用print打印一个非map变量,如大多数的内置变量和局部变量:
# bpftrace -e 'BEGIN { $t = (1, "string"); print(123); print($t); print(comm) }'
Attaching 1 probe...
123
(1, string)
bpftrace
^C
语法:
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
格式:
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
格式:
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
格式:
kptr(void *p)
类似于uptr,将p注解为内核态地址,通常只有在bpftrace错误地推断出指针地址空间的情况下需要。
格式:
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
格式:
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
格式:
@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
格式
@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
语法:
@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
格式:
@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
格式:
@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
格式:
@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
格式:
@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 |@@
格式:
@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 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
格式:
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