搜索

ebpf-4——reference_guide.md翻译与实验 - Hello-World3 -


发布时间: 2022-11-24 20:33:02    浏览次数:104 次

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

bpftrace 可用于创建一些强大的单行代码和一些简单的工具。 对于可能涉及命令行选项、位置参数、参数处理和自定义输出的复杂工具,请考虑切换到 bcc。 bcc 提供 Python(和其他)前端,支持使用所有其他 Python 库(包括 argparse),以及直接控制内核 BPF 程序。 不利的一面是 bcc 的编程更加冗长和费力。 bpftrace 和 bcc 一起是免费的。
预期的开发路径是使用 bpftrace 单行程序进行探索,然后使用 bpftrace 进行临时脚本编写,最后在需要时使用 bcc 编写高级工具。
作为 bpftrace 与 bcc 差异的示例,bpftrace xfsdist.bt 工具也作为 xfsdist.py 存在于 bcc 中。 两者测量相同的功能并产生相同的信息摘要。 但是,bcc版本支持各种参数:
# ./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)打印到标准输出。
例子:
免责声明 ebpf-4——reference_guide.md翻译与实验 - Hello-World3 - ,资源类别:文本, 浏览次数:104 次, 文件大小:-- , 由本站蜘蛛搜索收录2022-11-24 08:33:02。此页面由程序自动采集,只作交流和学习使用,本站不储存任何资源文件,如有侵权内容请联系我们举报删除, 感谢您对本站的支持。 原文链接:https://www.cnblogs.com/hellokitty2/p/16915953.html