Linux | BPF实用工具使用方式
2022-9-16 18:31:49 Author: TahirSec(查看原文) 阅读量:33 收藏

BPF 程序具有超越普通程序的能力。

利用BPF大致分为以下几种方式:

  • 利用现有的 BPF 程序,例如,有 sslsniff-bpcc、bpftool 。

  • 编写少量代码的程序并使用 bpftrace 运行它们。

  • 使用 libbpf 和其他框架编写更复杂的程序。通过 clang 等工具编译成BPF字节码。

  • 直接使用BPF字节码进行开发。

本文主要介绍已有的BPF相关的工具和框架的使用方式。

1.BPFTool

BPFTool是一个用于检查BPF程序和映射的内核工具。

1.1 安装

安装需要下载内核源码

git clone https://github.com/torvalds/linuxgit checkout v4.19cd tools/bpf/bpftoolmake && sudo make install

1.2 特征查看

bpftool featur

查看jit是否开启

echo 1 > /proc/sys/net/core/bpf_jit_enable

1.3 检查BPF程序

bpftool可以提供内核与BPF程序相关的信息。通过bpftool,我们可以查看系统中已经运行的BPF程序信息。

查看系统中已经加载了一些BPF程序。

bpftool prog show

例如,目前系统已加载了某些kprobes。

如下命令可以格式化输出BPF程序相关信息。

bpftool prog show --json id 16 | jq

当知道程序id后,还可以使用BPFTool获取整个程序的数据。可以反编译为BPF字节码。

bpftool prog dump xlated id 16

可以加上visual关键字,产生特定格式输出。

bpftool prog dump xlated id 16 visual

1.4 检查BPF映射

显示映射信息

bpftool map show

map帮助信息,可以对映射做一系列操作,比如更新删除添加等等。

bpftool map help

1.5 查看附加到特定接口的程序

BPF可以加载运行在cgroup、Pref事件和网络数据包程序上,bpftool可以查看跟踪在这些接口的附加程序。

bpftool perf showbpftool net showbpftool cgroup tree

1.6 批量加载命令

可以将命令写入文件,批量处理,比如写入文件test.txt。

map showperf show

运行命令,执行批处理。

bpftool batch file ./test.txt

1.7 显示BTF信息

显示任何给定的二进制对象的BPF类型格式(BTF)信息。

bpftool btf dump id 35
  1. BPFTrace

BPFTrace 是一个BPF开发的前端工具,可用来创建自定义的 BPF 程序,是一种用于 eBPF 的高级跟踪语言,使用 LLVM 作为后端,将脚本编译为 BPF 字节码,语言的灵感来自于awk 和 C,以及诸如 DTrace 和 SystemTap 这样的跟踪器。

2.1 安装

yum install bpftrace

2.2 语法

BPFTrace程序语法简洁。程序分为3个部分:

  • 头部(header)

  • 操作块(action block)

  • 尾部(footer)

头部是在加载程序时BPFTrace执行的特殊块。它通常用来打印在输出顶部的一些信息。

尾部是在程序终止前BPFTrace执行的特殊块。

头部和尾部都是BPFTrace程序可选部分。

一个BPFTrace程序必须至少有一个操作块。操作块是指定我们要跟踪的探针的位置,以及基于探针内核触发事件执行的操作。

BEGIN{    printf("starting BPFTrace program\n")}kprobe:do_sys_open{    printf("opening file descriptor: %s\n", str(arg1))}END{    printf("exiting BPFTrace program\n")}

头部用关键字BEGIN标记,尾部调用用关键字END标记。这些关键字是BPFTrace保留关键字。操作块标识符定义要附加的BPF操作的探针。上面示例中,每次内核打开一个文件,都会打印一条日志。

bpftrace test.bt

也可以直接写一行:

bpftrace -e "kprobe:do_sys_open {@opens[str(arg1)] = count()}"

2.3 过滤

过滤器封装在操作头部后面的两个斜杠内:

BEGIN{    printf("starting BPFTrace program\n")}kprobe:do_sys_open /str(arg1) == "~/test.bt"/{    printf("opening file descriptor: %s\n", str(arg1))}END{    printf("exiting BPFTrace program\n")}

2.4 动态映射

所有映射关联都以字符@开头,后面跟着要创建的映射名。可以通过指定元素的值来更新映射中的元素。

将open系统调用的次数,保存在映射中。

kprobe:do_sys_open{    @opens[str(arg1)] = count()}

当程序停止执行时,BPFTrace将打印映射的内容。它聚合计算了内核系统中打开文件的频率。默认情况下,当BPFTrace终止时,总是会打印创建的每个映射的内容。如果需要清除,可以通过使用内置函数clear来清除END块中的映射。

2.5 BPFTrace 单行程序实例

1. CPU跟踪新进程,包括进程参数:bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'按进程统计系统调用的数量:bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[pid, comm] = count(); }'99Hz的频率采样正在运行的进程名:bpftrace -e 'profile:hz:99 { @[comm] = count(); }'49Hz的频率采样,进程ID为1134的用户态调用栈的信息:bpftrace -e 'profile:hz:49 /pid == 1134 / { @[ustack] = count(); }'跟踪通过pthread_create()创建的新线程:bpftrace -e 'u:/lib/x86_64-linux-gnu/libpthread-2.27.so:pthread_create { printf("%s by %s (%d)\n", probe, comm, pid); }'
2. 内存根据用户态调用栈信息统计进程堆内存扩展(brk()):bpftrace -e 'tracepoint:syscalls:sys_enter_brk { @[ustack, comm] = count(); }'按进程统计缺页错误:bpftrace -e 'software:page-fault:1 { @[comm, pid] = count(); }'根据用户态调用栈信息统计缺页错误:bpftrace -e 'tracepoint:exceptions:page_fault_user { @[comm, pid] = count(); }'通过跟踪点来统计vmscan操作:bpftrace -e 'tracepoint:vmscan:* { @[probe]++; }'
3. 安全为PID为1234的进程统计安全审计事件数:bpftrace -e 'k:security_* /pid == 1234/ { @[func] = count(); }'跟踪可插入身份验证模块(PAM)会话的开始:bpftrace -e 'u:/lib/x86_64-linux-gnu/libppam.so.0:pam_start { printf("%s: %s\n", str(arg0), str(arg1)); }'跟踪内核模块加载:bpftrace -e 't:module:module_load { printf("load: %s\n", str(args->name)); }'
4. 内核按系统调用函数对系统调用进行计数:bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[ksym(*(kaddr("sys_call_table") + args->id * 8))] = count(); }'对以 attach 开头的内核函数调用进行计数:bpftrace -e 'kprobe:attach* { @[probe] = count(); }'为内核函数vfs_read()计时并画直方图:bpftrace -e 'k:vfs_read { @ts[tid] = nsecs; } kr:vfs_read /@ts[tid]/ { @ = hist(nsecs - @ts[tid]); delete(@ts[tid]); }'对内核函数 func1 的第一个参数出现的频率进行计数:bpftrace -e 'kprobe:func1 { @[arg0] = count(); }'对内核函数 func1 的返回值出现的频率进行计数:bpftrace -e 'kprobe:func1 { @[retval] = count(); }'
5. 文件系统按进程名统计通过open()打开的文件:bpftrace -e 't:syscalls:sys_enter_open { printf("%s %s\n", comm, str(args->filename)); }'显示read()系统调用的请求大小分布:bpftrace -e 'tracepoint:syscalls:sys_enter_read { @ = hist(args->count); }'显示read()系统调用的实际读取字节数(以及错误):bpftrace -e 'tracepoint:syscalls:sys_exit_read { @ = hist(args->ret); }'统计VFS调用:bpftrace -e 'kprobe:vfs_* { @[probe] = count(); }'统计ext4跟踪点:bpftrace -e 'tracepoint:ext4_* { @[probe] = count(); }'
6. 网络按PID和进程名统计套接字accept调用:bpftrace -e 't:syscalls:sys_enter_accept* { @[pid, comm] = count(); }'按PID和进程名统计套接字connect调用:bpftrace -e 't:syscalls:sys_enter_connect { @[pid, comm] = count(); }'按在CPU上运行的PID和进程名统计套接字发送和接受的字节数:bpftrace -e 'kr:sock_sendmsg,kr:sock_recvmsg /retval >0/{ @[pid, comm, retval] = sum(retval); }'统计TCP的发送和接收次数:bpftrace -e 'k:tcp_sendmsg,k:tcp*recvmsg { @[func] = count(); }'以直方图形式统计TCP发送的字节数:bpftrace -e 'k:tcp_sendmsg { @send_bytes = hist(arg2); }'以直方图形式统计TCP接收的字节数:bpftrace -e 'k:tcp*recvmsg /retval >= 0/ { @recv_bytes = hist(retval); }'按类型与远程主机统计TCP重传:bpftrace -e 'k:tcp_retransmit_* { @[probe, ntop(2, args->addr)] = count(); }'统计发送数据包时的内核态调用栈:bpftrace -e 't:net:net_dev_xmit { @[kstack] = count(); }'
7. 应用程序按用户态调用栈计算malloc()请求的字节总数(高开销):bpftrace -e 'u:/lib/libc-2.27so:malloc { @[ustack(5)] = sum(arg0); }'跟踪kill()信号,显示发送进程名称、目标PID和信号:bpftrace -e 't:syscalls:sys_enter_kill { printf("%s -> PID %d SIG %d\n", comm, args->pid, args->sig); }'

reference

《Linux内核观测技术BPF》

《BPF之巅洞悉Linux系统和应用性能》


文章来源: http://mp.weixin.qq.com/s?__biz=MzkzNjIwMzM5Nw==&mid=2247484086&idx=1&sn=7b605298114816b38e57cec71ff00fe2&chksm=c2a3075cf5d48e4abe1f66eb012a1f7378dfdba54c07941d0343d904f6bc1c2974b7faf36aa4#rd
如有侵权请联系:admin#unsafe.sh