BLOG

Record, summarize, and improve.

bcc & bpftrace

bpftrace内置变量

  • pid 进程 ID(内核 tgid)
  • tid 线程 ID(内核 pid)
  • uid 用户身份
  • gid 组 ID
  • nsecs 纳秒时间戳
  • elapsed 自 bpftrace 初始化以来的纳秒
  • cpu 处理器 ID
  • comm 进程名称
  • kstack 内核堆栈跟踪
  • ustack 用户堆栈跟踪
  • arg0 arg1 argN 跟踪函数的参数;假定为 64 位宽
  • sarg0 sarg1 sargN 跟踪函数的参数(对于在堆栈上存储参数的程序);假定为 64 位宽
  • retval 跟踪函数的返回值
  • func 跟踪函数的名称
  • probe 探头的全名
  • curtask 当前任务结构为 u64
  • rand 随机数作为 u32
  • cgroup 当前进程的cgroup ID
  • cpidc command 子pid(u32),仅对标志有效
  • $1 $2 $N $# bpftrace 程序的位置参数

特殊变量:

  • @global_name 全局变量
  • @thread_local_variable_name[tid] 每线程变量
  • $scratch_name 临时变量

bpftrace 内置接口

count(): 频率计数

hist()lhist(): 直方图

nsecs: 时间戳和时间增量

kstack:堆栈跟踪,内核

ustack:堆栈跟踪,用户

$1, ..., $N$#: 位置参数 如果它是非数字的,则它必须在str()调用中用作字符串。$#返回提供的位置参数的数量。

  • 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 地址数据转换为文本
  • 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 地址数据

其中一些是异步的:内核将事件排队,但一段时间后(毫秒)它在用户空间中处理。异步操作是:printf()time()join()ksym() 和usym()以及变量kstack和都ustack同步记录地址,然后异步进行符号转换。

bpftrace Map Functions

  • 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])删除作为参数传入的地图元素
  • print(@x[, top [, div]])打印地图,可选择仅顶部条目和除数
  • print(value)打印一个值
  • clear(@x)从地图中删除所有键
  • zero(@x)将所有地图值设置为零

其中一些是异步的:内核将事件排队,但一段时间后(毫秒)它在用户空间中处理。异步操作是:print()在地图上clear()、 和zero()

bpftrace Output

printf(): 每事件输出

interval.:间隔输出

hist()print(): 直方图打印

bpftrace探针

kprobe检测函数执行的开始,kretprobe检测结束(返回)。

kprobe: arg0, arg1, ..., argN
kretprobe: retval

可以通过这些变量名称访问参数。arg0是第一个参数,只能使用kprobe

retval是检测函数的返回值,只能在kretprobe使用

uprobe检测用户级函数执行的开始,并uretprobe检测结束(它的返回)。

要列出可用的 uprobes,您可以使用任何程序从二进制文件中列出文本段符号,例如objdumpnm。例如:

# objdump -tT /bin/bash | grep readline

例子:

# bpftrace -e 'uretprobe:/bin/bash:readline { printf("read a line\n"); }'

tracepoint

可以从 /sys 中的 /format 文件中列出每个跟踪点的可用成员。例如:

# cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_open/format

profile: 定时采样事件

profile:hz:rate

profile:s:rate

profile:ms:rate

profile:ms:rate

interval: 定时输出

interval:ms:rate

interval:s:rate

interval:us:rate

interval:hz:rate

software: 预定义的软件事件

software:event_name:count

software:event_name:

这些是 Linux 内核提供的预定义软件事件,通常通过 perf 实用程序进行跟踪。它们类似于跟踪点,但只有十几个,它们记录在 perf_event_open(2) 手册页中。事件名称是:

  • cpu-clockcpu
  • task-clock
  • page-faultsfaults
  • context-switchescs
  • cpu-migrations
  • minor-faults
  • major-faults
  • alignment-faults
  • emulation-faults
  • dummy
  • bpf-output

hardware: 预定义的硬件事件

hardware:event_name:count

hardware:event_name:

这些是 Linux 内核提供的预定义硬件事件,通常由 perf 实用程序跟踪。它们是使用性能监控计数器 (PMC) 实现的:处理器上的硬件资源。其中大约有十个,它们记录在 perf_event_open(2) 手册页中。

BCC探针

kprobe

kprobe__ kernel_function_name

  • struct pt_regs *ctx寄存器和 BPF 上下文。
  • struct sock *sktcp_v4_connect() 的第一个参数。

第一个参数总是struct pt_regs *,其余的是函数的参数(如果您不打算使用它们,则不需要指定它们)。pt_regs 结构具体架构具体分析,获取其中的参数有一些宏

#define PT_REGS_ARM64 const volatile struct user_pt_regs
#define PT_REGS_PARM1(x) (((PT_REGS_ARM64 *)(x))->regs[0])
#define PT_REGS_PARM2(x) (((PT_REGS_ARM64 *)(x))->regs[1])
#define PT_REGS_PARM3(x) (((PT_REGS_ARM64 *)(x))->regs[2])
#define PT_REGS_PARM4(x) (((PT_REGS_ARM64 *)(x))->regs[3])
#define PT_REGS_PARM5(x) (((PT_REGS_ARM64 *)(x))->regs[4])
#define PT_REGS_RET(x) (((PT_REGS_ARM64 )(x))->regs[30])
/ Works only with CONFIG_FRAME_POINTER */
#define PT_REGS_FP(x) (((PT_REGS_ARM64 *)(x))->regs[29])
#define PT_REGS_RC(x) (((PT_REGS_ARM64 *)(x))->regs[0])
#define PT_REGS_SP(x) (((PT_REGS_ARM64 *)(x))->sp)
#define PT_REGS_IP(x) (((PT_REGS_ARM64 *)(x))->pc)

kretprobe

kretprobe__ kernel_function_name

tracepoint

TRACEPOINT_PROBE(类别事件

这是一个检测由category : event定义的跟踪点的宏。

跟踪点名称是<category>:<event>。探测函数名称为tracepoint__<category>__<event>.

参数在args结构中可用,它们是跟踪点参数。列出这些的一种方法是在/sys/kernel/debug/tracing/events/category/event/format 下分类相关格式文件。

args结构可用于代替ctx需要上下文作为参数的每个函数。

TRACEPOINT_PROBE (random, urandom_read)

args 来自 /sys/kernel/debug/tracing/events/random/urandom_read/format

BCC内置接口

bpf_probe_read_kernel()

这会将 size 字节从内核地址空间复制到 BPF 堆栈,以便 BPF 稍后可以对其进行操作。为了安全起见,所有内核内存读取都必须通过 bpf_probe_read_kernel()。

bpf_probe_read_kernel_str()

这会将一个终止的字符串从内核地址空间复制NULL到 BPF 堆栈,以便 BPF 稍后可以对其进行操作。如果字符串长度小于大小,则不会用更多NULL字节填充目标。如果字符串长度大于大小,size - 1则只复制字节并将最后一个字节设置为NULL.

bpf_ktime_get_ns()

u64 纳秒数。在系统引导时启动,但在挂起期间停止。

bpf_get_current_pid_tgid()

返回低 32 位的进程 ID(内核的 PID 视图,在用户空间中通常表示为线程 ID)和高 32 位的线程组ID(用户空间通常认为 PID) . 通过直接将其设置为 u32,我们丢弃了高 32 位。

bpf_get_current_uid_gid()

返回用户 ID 和组 ID

bpf_get_current_comm()

使用当前进程名称填充第一个参数地址。它应该是一个指向至少大小为 TASK_COMM_LEN 的 char 数组的指针,该数组在 linux/sched.h 中定义。

bpf_get_current_task()

返回指向当前任务的 task_struct 对象的指针。该帮助器可用于计算进程的 CPU 运行时间、识别内核线程、获取当前 CPU 的运行队列或检索许多其他信息。

bpf_log2l()

返回所提供值的 log-2。这通常用于为直方图创建索引,以构建 2 的幂直方图。

bpf_get_prandom_u32()

返回一个伪随机 u32。

bpf_probe_read_user()

这会尝试从用户地址空间安全地读取 size 字节到 BPF 堆栈,以便 BPF 稍后可以对其进行操作。为安全起见,所有用户地址空间内存读取都必须通过 bpf_probe_read_user()。

bpf_probe_read_user_str()

这会将终止的字符串从用户地址空间复制NULL到 BPF 堆栈,以便 BPF 稍后可以对其进行操作。如果字符串长度小于大小,则不会用更多NULL字节填充目标。如果字符串长度大于大小,size - 1则只复制字节并将最后一个字节设置为NULL.

bpf_get_ns_current_pid_tgid()

从当前命名空间看到的pidtgid的值将在nsdata中返回。

BCC输出

bpf_trace_printk()

用于 printf() 到公共 trace_pipe (/sys/kernel/debug/tracing/trace_pipe) 的简单内核工具。这对于一些简单的示例来说是可以的,但有限制:最多 3 个 args,仅 1 %s,并且 trace_pipe 是全局共享的,因此并发程序会有冲突的输出。更好的接口是通过 BPF_PERF_OUTPUT()。

BPF_PERF_OUTPUT

创建一个 BPF 表,用于通过 perf 环形缓冲区将自定义事件数据推送到用户空间。这是将每个事件数据推送到用户空间的首选方法。

perf_submit()

BPF_PERF_OUTPUT 表的一种方法,用于向用户空间提交自定义事件数据。请参阅BPF_PERF_OUTPUT条目。(这最终会调用 bpf_perf_event_output())

perf_submit_skb()

网络程序类型中可用的 BPF_PERF_OUTPUT 表的一种方法,用于向用户空间提交自定义事件数据,以及packet_size数据包缓冲区的第一个字节。请参阅 BPF_PERF_OUTPUT 条目。(这最终会调用 bpf_perf_event_output()。)

BPF_RINGBUF_OUTPUT

创建一个 BPF 表,用于通过 ringbuf 环形缓冲区将自定义事件数据推送到用户空间。 BPF_RINGBUF_OUTPUT有几个优点BPF_PERF_OUTPUT,总结如下:

  • 缓冲区在所有 CPU 之间共享,这意味着没有每个 CPU 分配
  • 支持 BPF 程序的两个 API
    • map.ringbuf_output()map.perf_submit()(在ringbuf_output中涵盖)
    • map.ringbuf_reserve()//将预留缓冲区空间map.ringbuf_submit()map.ringbuf_discard()提交事件的过程分成两步(在ringbuf_reserve,ringbuf_submit,ringbuf_discard中涉及)
  • BPF API 不需要访问 CPU ctx 参数
  • 由于共享环形缓冲区管理器,用户空间的卓越性能和延迟
  • 支持两种在用户空间消费数据的方式

从 Linux 5.8 开始,这应该是将每个事件数据推送到用户空间的首选方法。

ringbuf_output()

BPF_RINGBUF_OUTPUT 表的一种方法,用于向用户空间提交自定义事件数据。此方法perf_submit()的工作方式类似于 ,尽管它不需要 ctx 参数。

ringbuf_reserve()

BPF_RINGBUF_OUTPUT 表的一种方法,用于在环形缓冲区中保留空间并同时为输出分配数据结构。必须与ringbuf_submit或之一一起使用ringbuf_discard

ringbuf_submit()

BPF_RINGBUF_OUTPUT 表的一种方法,用于向用户空间提交自定义事件数据。必须在调用之前 ringbuf_reserve()为数据保留空间。

ringbuf_discard()

BPF_RINGBUF_OUTPUT 表的一种方法,用于丢弃自定义事件数据;用户空间忽略与丢弃事件相关的数据。必须在调用之前 ringbuf_reserve()为数据保留空间。

BCC Maps

BPF_TABLE

创建一个名为_name 的地图。大多数情况下,这将通过更高级别的宏来使用,例如 BPF_HASH、BPF_ARRAY、BPF_HISTOGRAM 等。BPF_F_TABLE是一个变体,它在最后一个参数中带有一个标志。

BPF_HASH

创建一个名为 name的哈希映射(关联数组),带有可选参数。

BPF_ARRAY

创建一个 int 索引数组,该数组针对最快的查找和更新进行了优化,命名为name,带有可选参数。

BPF_HISTOGRAM

创建一个名为name 的直方图,带有可选参数

BPF_STACK_TRACE

创建名为 name的堆栈跟踪映射,并提供最大条目数。这些映射用于存储堆栈跟踪。

BPF_PERF_ARRAY

创建名为name 的 perf 数组,并提供最大条目数,该数必须等于系统 cpu 的数量。这些映射用于获取硬件性能计数器。

BPF_PERCPU_HASH

使用可选参数创建名为name 的 NUM_CPU 整数索引哈希映射(关联数组) 。每个 CPU 将拥有该数组的单独副本。副本不会以任何方式保持同步。

请注意,由于内核中定义的限制(在 linux/mm/percpu.c 中),leaf_type大小不能超过 32KB。换句话说,BPF_PERCPU_HASH元素的大小不能大于 32KB。

BPF_PERCPU_ARRAY

创建 NUM_CPU 整数索引数组,这些数组已针对最快的查找和更新进行了优化,命名为name,带有可选参数。每个 CPU 将拥有该数组的单独副本。副本不会以任何方式保持同步。

请注意,由于内核中定义的限制(在 linux/mm/percpu.c 中),leaf_type大小不能超过 32KB。换句话说,BPF_PERCPU_ARRAY元素的大小不能大于 32KB。

BPF_LPM_TRIE

创建一个名为name 的最长前缀匹配树映射,带有可选参数。

BPF_PROG_ARRAY

这将创建一个namesize条目命名的程序数组。数组的每个条目要么是 bpf 程序的文件描述符,要么是NULL. 该数组充当跳转表,以便 bpf 程序可以“尾调用”其他 bpf 程序。

BPF_DEVMAP

这将创建一个namesize条目命名的设备映射。映射的每个条目都是ifindex一个网络接口。此地图仅在 XDP 中使用。

BPF_CPUMAP

这将创建一个namesize条目命名的 cpu 映射。map 的索引代表 CPU id,每个条目是分配给 CPU 的环形缓冲区的大小。此地图仅在 XDP 中使用。

BPF_XSKMAP

这将创建一个namesize条目命名的 xsk 映射。每个条目代表一个 NIC 的队列 ID。此映射仅在 XDP 中用于将数据包重定向到 AF_XDP 套接字。如果 AF_XDP 套接字绑定到与当前数据包的队列 ID 不同的队列,则该数据包将被丢弃。对于内核 v5.3 及更高版本,lookup方法可用,可用于检查 AF_XDP 套接字是否可用于当前数据包的队列 id。更多细节在AF_XDP

BPF_ARRAY_OF_MAPS

这将创建一个数组映射,其映射类型 (BPF_MAP_TYPE_HASH_OF_MAPS) 映射以name条目size命名。内部映射元数据由映射提供inner_map_name,可以是除BPF_MAP_TYPE_PROG_ARRAY,BPF_MAP_TYPE_CGROUP_STORAGE和之外的大多数数组或哈希映射BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE

BPF_HASH_OF_MAPS

这将创建一个哈希映射,其映射类型 (BPF_MAP_TYPE_HASH_OF_MAPS) 映射以name条目size命名。内部映射元数据由映射提供inner_map_name,可以是除BPF_MAP_TYPE_PROG_ARRAY,BPF_MAP_TYPE_CGROUP_STORAGE和之外的大多数数组或哈希映射BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE

BPF_STACK

创建一个以namevalue typeleaf_type和 max entries命名的堆栈max_entries

BPF_QUEUE

创建一个以namevalue typeleaf_type和 max entries命名的队列max_entries

BPF_SOCKHASH

创建一个名为 的哈希name,带有可选参数。

map.lookup()

在映射中查找键,如果存在则返回指向其值的指针,否则返回 NULL。我们将密钥作为地址传递给指针。

map.lookup_or_try_init()

在映射中查找键,如果存在则返回指向其值的指针,否则将键的值初始化为第二个参数。这通常用于将值初始化为零。如果无法插入密钥(例如地图已满),则返回 NULL。

map.delete()

从哈希中删除密钥。

map.update()

将第二个参数中的值与键关联,覆盖之前的任何值。

map.insert()

仅当没有先前的值时,才将第二个参数中的值与键相关联。

map.increment()

将键的值增加increment_amount,默认为 1。用于直方图。

map.get_stackid()

这将遍历通过 struct pt_regs in 找到的堆栈ctx,将其保存在堆栈跟踪映射中,并返回堆栈跟踪的唯一 ID。

map.perf_read()

这将返回5. BPF_PERF_ARRAY中配置的硬件性能计数器

map.call()

这将调用bpf_tail_call()尾调用BPF_PROG_ARRAYindex中的条目指向的 bpf 程序。尾随呼叫与正常呼叫不同。它在跳转到另一个 bpf 程序后重用当前堆栈帧并且永不返回。如果该条目为空,则不会跳转到任何地方,程序继续正常执行。

map.redirect_map()

这会根据index条目重定向传入的数据包。如果映射为BPF_DEVMAP,则数据包将被发送到入口指向的网络接口的传输队列。如果映射为BPF_CPUMAP,则数据包将被发送到 CPU 的环形缓冲区,index稍后由 CPU 处理。如果映射是BPF_XSKMAP,数据包将被发送到附加到队列的 AF_XDP 套接字

map.push()

将元素推送到堆栈或队列表上。将 BPF_EXIST 作为标志传递会导致队列或堆栈在最旧的元素已满时丢弃它。成功返回 0,失败返回负错误。

map.pop()

从 Stack 或 Queue 表中弹出一个元素。*val填充有结果。与偷看不同,弹出会删除元素。成功返回 0,失败返回负错误。

map.peek()

查看 Stack 或 Queue 表头部的元素。*val填充有结果。与弹出不同,窥视不会删除元素。成功返回 0,失败返回负错误。

map.sock_hash_update()

添加条目或更新引用套接字的 sockhash 映射。skops 用作与键关联的条目的新值。如果地图有 eBPF 程序(解析器和判定),这些程序将被添加的套接字继承。如果套接字已经附加到 eBPF 程序,这会导致错误。

map.msg_redirect_hash()

此帮助程序用于在套接字级别实现策略的程序中。如果消息 msg 被允许通过(即如果判决 eBPF 程序返回 SK_PASS),则使用哈希键将其重定向到 map 引用的套接字(类型为 BPF_MAP_TYPE_SOCKHASH)。入口和出口接口都可用于重定向。flags 中的 BPF_F_INGRESS 值用于区分(如果存在标志,则选择入口路径,否则选择出口路径)。这是目前唯一支持的标志。

map.sk_redirect_hash()

此帮助程序用于在 skb 套接字级别实现策略的程序中。如果 sk_buff skb 被允许通过(即如果判决 eBPF 程序返回 SK_PASS),则使用哈希键将其重定向到 map 引用的套接字(类型为BPF_MAP_TYPE_SOCKHASH)。入口和出口接口都可用于重定向。flags 中的 BPF_F_INGRESS 值用于区分(如果存在标志,则选择入口路径,否则选择出口)。这是目前唯一支持的标志