最近接触到bpf的概念,简单做一些练习,做以下整理,学习笔记。
0:总结
使用ebpf可以实现的功能打开新世界,可以不改变源码的情况下,实现内存,网络,对应接口等各种期望内容的监控。
可以使用bpf直接进行开发,使用clang编译成ebpf字节序后加载进行使用。
使用bcc进行开发实现功能,多是python/lua,对应的demo可以参考githuab.
使用bpftool工具集可以进行加载,查看,卸载等一系列的功能,没有过多研究。
使用bpftrace一行指令已经可以方便实现很多的功能,比如监控文件打开,监控内存申请,监控函数调用次数,在内存,网络,sql调用,以及接口相关都有很多很方便的功能,进行简单的使用测试。
1:了解什么是ebpf,bpf,bcc, bpftool,bpftrace
直接问ai,简单又一些认知。
eBPF 是底层技术,所有这些工具(BPF, BCC, bpftool, BPFtrace)都基于 eBPF 工作。
BPF 是 eBPF 的前身,如今被 eBPF 所取代。
BCC 提供了一套方便的 API 和工具,帮助开发者快速编写和运行 eBPF 程序。
bpftool 用于管理和调试 eBPF 程序,可以查看运行的 eBPF 程序及其相关资源。
BPFtrace 提供了一个高级脚本语言,用于快速编写和运行简单的 eBPF 程序,主要用于追踪和分析。
总之,这些工具和框架都围绕 eBPF 工作,帮助开发者更方便地利用 eBPF 的强大功能。
eBPF(Extended Berkeley Packet Filter)及其相关工具(BPF, BCC, bpftool, BPFtrace)都是用于动态追踪、性能分析和网络调试的技术和工具,尤其是在 Linux 环境中。以下是对它们的介绍以及它们之间的关系:
1. eBPF (Extended Berkeley Packet Filter)
eBPF 是 Linux 内核中的一个扩展,它允许用户在内核中运行自定义的字节码程序,而无需修改或重启内核。
最初用于网络数据包过滤,但现在 eBPF 的应用范围已经扩展到了性能监控、安全分析、网络流量控制等多个领域。
eBPF 程序在内核中运行时是安全的,因为它们在执行前会经过内核的验证器验证,以确保不会损坏系统。
2. BPF
BPF 是 Berkeley Packet Filter 的缩写,是 eBPF 的前身。
最早设计用于网络数据包过滤,但功能较为有限,eBPF 则是其增强版。
BPF 程序可以通过系统调用加载到内核中执行,但随着时间的推移,eBPF 逐渐取代了传统 BPF。
3. BCC (BPF Compiler Collection)
BCC 是一个工具集,用于简化 eBPF 程序的编写和运行。
它提供了高级语言(如 Python 和 Lua)的 API,以便用户能够方便地编写 eBPF 程序。
BCC 中包含了许多现成的 eBPF 工具,用于追踪系统事件、网络流量、文件系统等。
使用 BCC 时,用户不需要直接编写 eBPF 字节码,而是通过高级语言和宏来生成和管理 eBPF 程序。
4. bpftool
bpftool 是 eBPF 的管理工具,主要用于查看、加载、卸载和调试 eBPF 程序。
它允许用户列出内核中加载的 eBPF 程序、地图(maps)和统计信息。
bpftool 提供了对内核 eBPF 子系统的细粒度控制和信息查看功能,通常用于开发和调试 eBPF 程序。
5. BPFtrace
BPFtrace 是一个简化的 eBPF 追踪工具,专注于动态追踪和性能分析。
它的语法受 DTrace 启发,更为简洁,用户可以通过简单的脚本编写复杂的追踪逻辑。
相比 BCC,BPFtrace 更加适合快速编写和运行短小的 eBPF 程序,适合于即时性能分析和诊断。
2:使用bpf进行测试,用户在终端上使用ls等指令时,测试demo能进行监控并展示。
使用22.04进行测试时,和已有demo不适配,高版本直接用bcc,bpftrace等进行直接开发吧。
这里用18.04进行环境安装以及demo测试。
2.1 在18.04上安装bpf
不支持直接安装,没有libbpf-dev
ubuntu@ubuntu:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 18.04.6 LTS
Release: 18.04
Codename: bionic
ubuntu@ubuntu:~$ sudo apt-get install build-essential
#可以看到 不支持这个库的直接安装
ubuntu@ubuntu:~$ sudo apt install libbpf-dev
E: Unable to locate package libbpf-dev
使用源码安装的方式
#使用源码安装的方式 去linux内核官网找对应系统的源码下载也可以 ()
#方案一 下载linux内核源码,转到对应的版本分治 这个在网络不稳定的情况下,太耗时,分支太多,太大了。
git clone https://github.com/torvalds/linux.git
cd linux
git checkout v$(uname -r | cut -d'-' -f1)
#方案二 发现一个很容易获取系统对应内核源码的方式 这种方式可能获取到的版本比较旧 与当前内核版本有差异 (可以下载指定内核的)
sudo apt-get install linux-source
cd /usr/src
tar xjf linux-source-$(uname -r).tar.bz2
cd linux-source-$(uname -r)
实际操作如下:
ubuntu@ubuntu:~$ cd /usr/src/
ubuntu@ubuntu:/usr/src$ ls
linux-headers-4.15.0-213 linux-headers-4.15.0-213-generic
ubuntu@ubuntu:/usr/src$ sudo apt-cache search linux-source
ubuntu@ubuntu:/usr/src$ sudo apt-get install linux-source
ubuntu@ubuntu:/usr/src$ ls
linux-headers-4.15.0-213 linux-headers-4.15.0-213-generic linux-source-4.15.0 linux-source-4.15.0.tar.bz2
ubuntu@ubuntu:/usr/src$ sudo tar xf linux-source-4.15.0.tar.bz2
#遇到问题 处理问题 这里直到最后编译成功 连bpftool也编译成功了。
ubuntu@ubuntu:/usr/src/linux-source-4.15.0/tools/bpf$ sudo apt-get install binutils-dev
ubuntu@ubuntu:/usr/src/linux-source-4.15.0/tools/bpf$ sudo apt-get install libreadline-dev
ubuntu@ubuntu:/usr/src/linux-source-4.15.0/tools/bpf$ sudo apt-get install bison
ubuntu@ubuntu:/usr/src/linux-source-4.15.0/tools/bpf$ sudo apt-get install binutils elfutils
root@ubuntu:/usr/src/linux-source-4.15.0/tools/lib/bpf# make
Auto-detecting system features:
... libelf: [ on ]
... bpf: [ on ]
Warning: Kernel ABI header at 'tools/include/uapi/linux/bpf.h' differs from latest version at 'include/uapi/linux/bpf.h'
CC libbpf.o
CC bpf.o
LD libbpf-in.o
LINK libbpf.a
LINK libbpf.so
root@ubuntu:/usr/src/linux-source-4.15.0/tools/lib/bpf# make install
Warning: Kernel ABI header at 'tools/include/uapi/linux/bpf.h' differs from latest version at 'include/uapi/linux/bpf.h'
INSTALL libbpf.a
INSTALL libbpf.so
#看起来默认安装在 /usr/local/lib64
root@ubuntu:/# find -name libbpf.a
./usr/local/lib64/libbpf.a
./usr/src/linux-source-4.15.0/tools/lib/bpf/libbpf.a
2.2 进行测试,首先要实现ebpf程序。(目标:使用clang把该程序编译成ebpf字节码。)
eBPF 程序通常是内核态的代码,使用 clang
编译为特定格式的字节码(BPF 字节码),然后加载到内核中。
//bpf_program.c
#include <linux/bpf.h>
#define SEC(NAME) __attribute__((section(NAME), used))
static int (*bpf_trace_printk)(const char *fmt, int fmt_size, ...) = (void *)BPF_FUNC_trace_printk;
SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(void *ctx) {
char msg[] = "Hello, BPF World!";
bpf_trace_printk(msg, sizeof(msg));
return 0;
}
char _license[] SEC("license") = "GPL";
使用clang把该程序编译成ebpf字节码:
ubuntu@ubuntu:~/bpf_test/bpf$ sudo apt-get install clang
ubuntu@ubuntu:~/bpf_test/bpf$ clang -O2 -target bpf -c bpf_program.c -I /usr/include/x86_64-linux-gnu/ -o bpf_program.o
2.3 编写用户空间程序,加载上面的ebpf程序,并执行。
这里的内核版本提供了bpf_load.h,使用这个接口进行使用。(高版本已经不提供了,实现可能有差异,不研究了)
#include "bpf_load.h"
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#define DEBUGFS "/sys/kernel/debug/tracing/"
void myread_trace_pipe(void)
{
int trace_fd;
trace_fd = open(DEBUGFS "trace_pipe", O_RDONLY, 0);
if (trace_fd < 0)
return;
while (1) {
static char buf[4096];
ssize_t sz;
sz = read(trace_fd, buf, sizeof(buf) - 1);
if (sz > 0) {
buf[sz] = 0;
puts(buf);
}
}
}
int main(int argc, char **argv) {
if (load_bpf_file("bpf_program.o") != 0) {
printf("The kernel didn't load the BPF program\\n");
return -1;
}
myread_trace_pipe();
return 0;
}
2.4 借助对应makefile进行编译
注意:这里使用了内核下的 bpf_load.c
CLANG = clang
EXECABLE = monitor-exec
#这里之际使用了内核提供给的bpf_load.c 借助libbpf
BPFCODE = bpf_program
BPFTOOLS = /usr/src/linux-source-4.15.0/samples/bpf
BPFLOADER = $(BPFTOOLS)/bpf_load.c
BPFLIB = /usr/src/linux-source-4.15.0/tools/lib/bpf/libbpf.c
#/usr/include/linux/types.h:5:10: fatal error: 'asm/types.h' file not found 这个与我ubuntu有关吧
CCINCLUDE += -I/usr/include/x86_64-linux-gnu/
CCINCLUDE += -I/usr/src/linux-source-4.15.0/tools/testing/selftests/bpf
LOADINCLUDE += -I/usr/src/linux-source-4.15.0/samples/bpf
LOADINCLUDE += -I/usr/src/linux-source-4.15.0/tools/lib
LOADINCLUDE += -I/usr/src/linux-source-4.15.0/tools/perf
LOADINCLUDE += -I/usr/src/linux-source-4.15.0/tools/include
LIBRARY_PATH = -L/usr/local/lib64
BPFSO = -lbpf
CFLAGS += $(shell grep -q "define HAVE_ATTR_TEST 1" /usr/src/linux-source-4.15.0/tools/perf/perf-sys.h && echo "-DHAVE_ATTR_TEST=0")
.PHONY: clean $(CLANG) bpfload build
clean:
rm -f *.o *.so $(EXECABLE)
build: ${BPFCODE.c} ${BPFLOADER}
$(CLANG) -O2 -target bpf -c $(BPFCODE:=.c) $(CCINCLUDE) -o ${BPFCODE:=.o}
bpfload: build
clang $(CFLAGS) -o $(EXECABLE) -lelf $(LOADINCLUDE) $(LIBRARY_PATH) $(BPFSO) $(BPFLIB) $(BPFLOADER) loader.c
$(EXECABLE): bpfload
.DEFAULT_GOAL := $(EXECABLE)
2.4 运行测试(运行ls时触发到监控打印Hello, BPF World)
ubuntu@ubuntu:~/bpf_test/bpf$ make
clang -O2 -target bpf -c bpf_program.c -I/usr/include/x86_64-linux-gnu/ -I/usr/src/linux-source-4.15.0/tools/testing/selftests/bpf -o bpf_program.o
clang -o monitor-exec -lelf -I/usr/src/linux-source-4.15.0/samples/bpf -I/usr/src/linux-source-4.15.0/tools/lib -I/usr/src/linux-source-4.15.0/tools/perf -I/usr/src/linux-source-4.15.0/tools/include -L/usr/local/lib64 -lbpf /usr/src/linux-source-4.15.0/tools/lib/bpf/libbpf.c /usr/src/linux-source-4.15.0/samples/bpf/bpf_load.c loader.c
#默认安装在/usr/local/lib64
ubuntu@ubuntu:~/bpf_test/bpf$ sudo ./monitor-exec
./monitor-exec: error while loading shared libraries: libbpf.so: cannot open shared object file: No such file or directory
ubuntu@ubuntu:~/bpf_test/bpf$ ldconfig /usr/local/lib64
/sbin/ldconfig.real: Can't create temporary cache file /etc/ld.so.cache~: Permission denied
ubuntu@ubuntu:~/bpf_test/bpf$ sudo ldconfig /usr/local/lib64
ubuntu@ubuntu:~/bpf_test/bpf$ sudo ./monitor-exec
<...>-11425 [000] .... 4464.404847: 0x00000001: Hello, BPF World!
<...>-11426 [000] .... 4466.860991: 0x00000001: Hello, BPF World!
bash-11427 [001] .... 4470.069820: 0x00000001: Hello, BPF World!
#这里执行后,在另外的终端上 执行ls 就会触发上面的逻辑,其他的逻辑可以扩展。
3:bcc和bpftool
基于libbpf进行调用外,可以使用bcc调用python/lua脚本进行实现。 安装后,bcc其实有一些可以直接使用的功能。可以参考开源库bcc下对应的example。
bpftool是一个工具,可以用于查看、加载、卸载、和调试 eBPF 程序的功能。(最好从github上源码入手) ubuntu@ubuntu:/$ sudo apt install -y linux-tools-$(uname -r)
ubuntu@ubuntu:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.2 LTS
Release: 22.04
Codename: jammy
ubuntu@ubuntu:~$ sudo apt-get install build-essential
#可以使用源码进行安装 git clone https://github.com/libbpf/libbpf.git
#可以在内核源码中直接安装吧?
ubuntu@ubuntu:/usr/lib$ sudo apt install libbpf-dev
ubuntu@ubuntu:~$ cd /usr/lib
ubuntu@ubuntu:/usr/lib$ sudo find /usr/lib -name libbpf.so
/usr/lib/x86_64-linux-gnu/libbpf.so
ubuntu@ubuntu:/usr/lib$ ls -ls /usr/include/bpf/
total 288
20 -rw-r--r-- 1 root root 17992 Dec 1 2022 bpf_core_read.h
4 -rw-r--r-- 1 root root 3750 Dec 1 2022 bpf_endian.h
12 -rw-r--r-- 1 root root 10066 Dec 1 2022 bpf.h
148 -rw-r--r-- 1 root root 149473 Dec 1 2022 bpf_helper_defs.h
8 -rw-r--r-- 1 root root 7679 Dec 1 2022 bpf_helpers.h
20 -rw-r--r-- 1 root root 19194 Dec 1 2022 bpf_tracing.h
16 -rw-r--r-- 1 root root 12856 Dec 1 2022 btf.h
4 -rw-r--r-- 1 root root 1421 Dec 1 2022 libbpf_common.h
36 -rw-r--r-- 1 root root 33279 Dec 1 2022 libbpf.h
4 -rw-r--r-- 1 root root 1525 Dec 1 2022 libbpf_legacy.h
4 -rw-r--r-- 1 root root 2894 Dec 1 2022 skel_internal.h
12 -rw-r--r-- 1 root root 8608 Dec 1 2022 xsk.h
#终于搞清楚了 ,内核一些低版本,提供了使用bpf_load.h对ebpf的一些封装,可以使用,高一些的版本,是没有这个文件的,可以直接用ebpf来实现。
#bpf_load.h 是内核中的一个头文件,常见于 samples/bpf 和 tools/testing/selftests/bpf 目录中,提供了简化加载 eBPF 程序并管理 eBPF maps 的 API。#这使得用户空间程序可以更轻松地与 eBPF 程序交互并加载到内核中
#本来想基于22.04试试bpf的安装后运行,发现和18.04有点差异,18.04有已有demo,可正常运行,但22.04相关高版本内核不提供bpf_load.h,主要了解逻辑,不关注了。
sudo apt install build-essential \
libelf-dev \
libz-dev \
libcap-dev \
binutils-dev \
pkg-config
#终于知道这几个库用在哪里用了 make报错依次解决 手动编译流程~ git clone https://github.com/libbpf/bpftool.git 下载?
root@ubuntu:/usr/src/linux-source-5.15.0/tools/bpf/bpftool# sudo apt-get install binutils-dev
root@ubuntu:/usr/src/linux-source-5.15.0/tools/bpf/bpftool# sudo apt-get install libcap-dev
root@ubuntu:/usr/src/linux-source-5.15.0/tools/bpf/bpftool# sudo apt-get install llvm
#直接在刚才下载linux源码的位置处编译bpftool
root@ubuntu:/usr/src/linux-source-5.15.0/tools/bpf/bpftool# make
Auto-detecting system features:
... libbfd: [ on ]
... disassembler-four-args: [ on ]
... zlib: [ on ]
... libcap: [ on ]
... clang-bpf-co-re: [ on ]
。。。
root@ubuntu:/usr/src/linux-source-5.15.0/tools/bpf/bpftool# ./bpftool --version
./bpftool v5.15.163
features: libbfd, skeletons
#上面bpf的测试在22.04 编译不过啊 高版本已经不提供用bpf_load.h 了
4:bpftrace的安装练习
bpftrace 一行指令已经实现很多功能,可以参考github进行学习。
bpftrace 可以说实现监控网络,内存等各种功能,根据业务可以思考。
思考探测内核函数,用户可执行程序中接口,so接口,静态库接口。
4.1 安装bpftrace (ubuntu 22.04)
#基于前面已经安装过一系列的组件 可以参考网络或者源码安装 这里初入门了解
root@ubuntu:/usr/src/linux-source-5.15.0/tools/bpf/bpftool# apt install bpftrace
root@ubuntu:/usr/src/linux-source-5.15.0/tools/bpf/bpftool# bpftrace --version
bpftrace v0.14.0
4.2 简单了解bpftrace 相关语法。
4.2.1 结构语法及参数介绍
4.2.2 常用内置变量
内置变量
bpftrace 脚本常用变量如下:
uid:用户 id。
tid:线程 id
pid:进程 id。
cpu:cpu id。
cgroup:cgroup id.
probe:当前的 trace 点。
comm:进程名字。
nsecs:纳秒级别的时间戳。
kstack:内核栈描述
curtask:当前进程的 task_struct 地址。
args:获取该 kprobe 或者 tracepoint 的参数列表
arg0:获取该 kprobe 的第一个变量,tracepoint 不可用
arg1:获取该 kprobe 的第二个变量,tracepoint 不可用
arg2:获取该 kprobe 的第三个变量,tracepoint 不可用
retval: kretprobe 中获取函数返回值
args->ret: kretprobe 中获取函数返回值
自定义变量
以'$'标志起来定义与引用变量,例如:$idx = 0;
Map 变量
Map 变量是用于内核向用户空间传递数据的一种存储结构,定义方式是以'@'符号作为标志
@path[tid] = nsecs;
@path[pid, $fd] = nsecs;
Bpftrace 默认在结束时会打印从内核接收到的 map 变量
内置函数
exit():退出 bpftrace 程序
str(char *):转换一个指针到 string 类型
system(format[, arguments ...]):运行一个 shell 命令
join(char *str[]):打印一个字符串列表并在每个前面加上空格,比如可以用 来输出 args->argv
ksym(addr):用于转换一个地址到内核 symbol
kaddr(char *name):通过 symbol 转换为内核地址
print(@m [, top [, div]]):可选择参数打印 map 中的 top n 个数据,数据可选择除以一个 div 值
buf(void *p [, int length])
strncmp(char *s1, char *s2, int length)
sizeof(expression)
ustack([limit])
ksym(void *p)
kstack([limit])
usym(void *p)
kaddr(char *name)
uaddr(char *name)
ntop([int af,]int|char[4:16] addr)
reg(char *name)
cgroupid(char *path)
bpftrace 内置了 map 对象操作函数,用于传递数据给 map 变量。
count():用于计算次数
sum(int n):用于累加计算
avg(int n):用于计算平均值
min(int n):用于计算最小值
max(int n):用于计算最大值
hist(int n):数据分布直方图(范围为 2 的幂次增长)
lhist(int n):数据线性直方图
delete(@m[key]):删除 map 中的对应的 key 数据
clear(@m):删除 map 中的所有数据
zero(@m):map 中的所有值设置为 0
4.3 简单练习bpf的相关一行指令
#统计调用read的次数
root@ubuntu:/home/ubuntu# bpftrace -e 't:syscalls:sys_enter_read {@[probe]=count(); }'
Attaching 1 probe...
^C
@[tracepoint:syscalls:sys_enter_read]: 94
#统计write调用的次数
root@ubuntu:/home/ubuntu# bpftrace -e 't:syscalls:sys_enter_write {@[probe]=count();}'
Attaching 1 probe...
^C
@[tracepoint:syscalls:sys_enter_write]: 22
#统计调用read 按参数 输出 数据分布直方图展示 ssize_t read(int fd, void *buf, size_t count);
root@ubuntu:/home/ubuntu# bpftrace -e 't:syscalls:sys_enter_read {@=hist(args->count);}'
Attaching 1 probe...
^C
@:
[1K, 2K) 29 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[2K, 4K) 0 | |
[4K, 8K) 0 | |
[8K, 16K) 0 | |
[16K, 32K) 4 |@@@@@@@ |
[32K, 64K) 0 | |
[64K, 128K) 0 | |
[128K, 256K) 0 | |
[256K, 512K) 4 |@@@@@@@ |
#追踪openat 系统调用 打开文件的入口 统计进程名和打开的文件
root@ubuntu:/home/ubuntu# bpftrace -e 't:syscalls:sys_enter_openat { printf("%s–> %s\n", comm,str(args->filename)); }'
Attaching 1 probe...
irqbalance–> /proc/interrupts
irqbalance–> /proc/stat
vmtoolsd–> /proc/meminfo
vmtoolsd–> /proc/vmstat
vmtoolsd–> /proc/stat
vmtoolsd–> /proc/zoneinfo
vmtoolsd–> /proc/uptime
vmtoolsd–> /proc/diskstats
#需要查找文件时触发 追踪内核函数 lookup_fast 的调用次数
root@ubuntu:/home/ubuntu# bpftrace -e 'kprobe:lookup_fast { @[comm, pid] = count(); }'
Attaching 1 probe...
^C
@[systemd-network, 847]: 3
@[systemd, 1]: 6
@[irqbalance, 879]: 8
@[cron, 871]: 15
@[vmtoolsd, 762]: 24
@[snapd, 885]: 148
#统计阻塞 io 事件 表示捕获所有与块设备相关的 tracepoint 事件 并记录每个 tracepoint 被触发的次数
root@ubuntu:/home/ubuntu# bpftrace -e 't:block:* { @[probe] = count(); }'
Attaching 18 probes...
^C
@[tracepoint:block:block_rq_complete]: 17
@[tracepoint:block:block_rq_issue]: 17
#统计 阻塞 io 操作数据大小 block_rq_issue用于捕获块设备的 I/O 请求被发出的事件
root@ubuntu:/home/ubuntu# bpftrace -e 't:block:block_rq_issue { @bytes = hist(args->bytes); }'
Attaching 1 probe...
^C
@bytes:
[8, 16) 4 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
#追踪块设备的 I/O 请求完成事件 block_rq_complete,并打印出那些包含错误的请求的相关信息
root@ubuntu:/home/ubuntu# bpftrace -e 't:block:block_rq_complete /args->error/ {printf("dev %d type %s error %d\n", args->dev, args->rwbs, args->error); }'
Attaching 1 probe...
^C
root@ubuntu:/home/ubuntu# bpftrace -e 't:block:block_rq_complete {printf("dev %d type %s error %d\n", args->dev, args->rwbs, args->error); }'
Attaching 1 probe...
dev 0 type N error 0
dev 0 type N error 0
dev 0 type N error 0
#启动的进程名与命令行参数
root@ubuntu:/home/ubuntu# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'
Attaching 1 probe...
ls --color=auto
ls --color=auto
ls --color=auto
ls --color=auto
cat 1
#追踪所有系统调用的进入点 统计次数
root@ubuntu:/home/ubuntu# bpftrace -e 'tracepoint:raw_syscalls:sys_enter{ @[pid, comm] = count(); }'
Attaching 1 probe...
^C
@[882, rsyslogd]: 3
@[871, cron]: 6
@[889, pool-udisksd]: 6
@[889, udisksd]: 12
@[887, systemd-logind]: 15
@[749, systemd-timesyn]: 15
@[849, systemd-resolve]: 15
@[847, systemd-network]: 20
@[1316, sshd]: 24
@[1627, bash]: 33
@[1, systemd]: 46
@[762, HangDetector]: 52
@[879, irqbalance]: 70
@[1593, sshd]: 74
@[1594, bash]: 76
@[1627, ls]: 121
@[885, snapd]: 181
@[569, multipathd]: 324
@[1620, bpftrace]: 525
@[762, vmtoolsd]: 3969
#追踪进程上下文切换事件,并统计每个上下文切换期间的内核堆栈信息的出现次数
root@ubuntu:/home/ubuntu# bpftrace -e 'tracepoint:sched:sched_switch { @[kstack] = count(); }'
Attaching 1 probe...
^C
@[
__schedule+855
__schedule+855
schedule+105
schedule_timeout+135
msleep+45
usb_port_suspend+908
usb_generic_driver_suspend+67
usb_suspend_both+506
usb_runtime_suspend+47
__rpm_callback+74
rpm_callback+103
rpm_suspend+356
__pm_runtime_suspend+73
usb_runtime_idle+58
rpm_idle+195
pm_runtime_work+141
process_one_work+552
worker_thread+83
kthread+295
ret_from_fork+31
]: 1
bpftrace -e 'u:/lib/x86_64-linux-gnu/libpthread2.31.so:pthread_create { printf("%s by %s (%d)\n", probe, comm, pid ); }'
sudo find -name libpthread*
ldd ./usr/lib32/libpthread.so.0
ldd ./usr/lib/x86_64-linux-gnu/libpthread.so.0
nm -D ./usr/lib/x86_64-linux-gnu/libpthread.so.0
ldd ./usr/lib/x86_64-linux-gnu/libpthread.so.0
nm -D lib/x86_64-linux-gnu/libc.so.6|grep thread_create
root@ubuntu:/# ldconfig -p | grep libpthread
libpthread.so.0 (libc6,x86-64, OS ABI: Linux 3.2.0) => /lib/x86_64-linux-gnu/libpthread.so.0
libpthread.so.0 (libc6, OS ABI: Linux 3.2.0) => /lib32/libpthread.so.0
#进程创建 pthread_create 探测 (写了个测试代码调用pthread_create)
root@ubuntu:/# bpftrace -e 'u:/lib/x86_64-linux-gnu/libc.so.6:pthread_create { printf("%s by %s (%d)\n", probe, comm, pid ); }'
Attaching 1 probe...
uprobe:/lib/x86_64-linux-gnu/libc.so.6:pthread_create by pthread (17916)
uprobe:/lib/x86_64-linux-gnu/libc.so.6:pthread_create by pthread (17918)
uprobe:/lib/x86_64-linux-gnu/libc.so.6:pthread_create by pthread (17920)
#内核堆栈跟踪的内存分配情况 统计内存分配总字节数
root@ubuntu:/# bpftrace -e 't:kmem:kmem_cache_alloc { @bytes[kstack] = sum(args->bytes_alloc); }'
Attaching 1 probe...
^C^C
@bytes[
kmem_cache_alloc+623
kmem_cache_alloc+623
__sigqueue_alloc+123
__send_signal+614
send_signal+233
do_send_sig_info+96
__kill_pgrp_info+133
kill_pgrp+53
isig+150
n_tty_receive_signal_char+27
n_tty_receive_char_special+1086
n_tty_receive_buf_standard+326
__receive_buf+507
n_tty_receive_buf_common+139
n_tty_receive_buf2+20
tty_ldisc_receive_buf+31
tty_port_default_receive_buf+66
flush_to_ldisc+170
process_one_work+552
worker_thread+83
kthread+295
ret_from_fork+31
]: 80
#统计malloc的调用次数
root@ubuntu:/# bpftrace -e 'u:/lib/x86_64-linux-gnu/libc.so.6:malloc {@[ustack, comm] = sum(arg0); }'
Attaching 1 probe...
^C
@[
__libc_malloc+0
, bpftrace]: 4
@[
__libc_malloc+0
0x55e8989dbcc0
0x55e8989d7400
0x8148535554415541
, bpftrace]: 8
@[
__libc_malloc+0
, bpftrace]: 8
@[
__libc_malloc+0
, bpftrace]: 24
@[
__libc_malloc+0
0x5618bcac89f0
0x20
, sshd]: 448
#统计进程发生缺页中断 写了个mmap 分配内存并访问 触发缺页中断
root@ubuntu:/# bpftrace -e 't:exceptions:page_fault_user { @[ustack, comm] = count(); }'
Attaching 1 probe...
^C
ERROR: failed to look up stack id -17 (pid -1): -1
@[
0x7fc0b22a57e1
, bash]: 1
@[
0x7f5cafa99610
0x69622f3d4c4c4548
, mmap]: 1
4.4 使用脚本进行练习
4.4.1 探测打开文件 输出对应文件名
#脚本进行测试 首先测试 探测打开文件的名称或者路径
root@ubuntu:/# bpftrace -e '
kprobe:vfs_open {
$path = (struct path *)arg0;
$dentry = $path->dentry;
printf("open path: %s\n", str($dentry->d_name.name));
}'
#写入文件
ubuntu@ubuntu:~/test$ cat fs.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); //不能直接打印 要提取到用户空间
$path = (struct path *)arg0;
$dentry = $path->dentry;
printf("open path: %s\n", str($dentry->d_name.name));
}
root@ubuntu:/home/ubuntu/test# bpftrace fs.bt
Attaching 1 probe...
open path: interrupts
open path: stat
open path: smp_affinity
open path: smp_affinity
4.4.2 探测tcp连接的网络五元组。
//探测五元组 除了accept 还可以其他相关 比如tcp的状态,比如connect recv send按需求处理
#include <linux/socket.h>
#include <net/sock.h>
#include <linux/types.h>
// BEGIN
// {
// printf("hello\n");
// }
//梳理 kprobe和kretprobe区别
kprobe:inet_csk_accept
{
printf("inet_csk_accept\n");
}
kretprobe:inet_csk_accept
{
$sk = (struct sock*)retval;
$inet_family = $sk->__sk_common.skc_family;
$daddr = ntop(0);
$saddr = ntop(0);
if ($inet_family == AF_INET) {
$daddr = ntop($sk->__sk_common.skc_daddr);
$saddr = ntop($sk->__sk_common.skc_rcv_saddr);
}
$sport = $sk->__sk_common.skc_num;
$dport = $sk->__sk_common.skc_dport; //大小端转换 不提供类型 自处理
$dport_num = ((($dport <<8) &0xFF00 )| (($dport>>8)&0xFF)) &0xFFFF;
printf(" tcp_accept: %-16s:%d %d--> %-16s:%d\n", $daddr, $dport, $dport_num, $saddr, $sport);
}
使用网络助手进行连接 测试结果:
测试结果: 写了一个tcp的服务端程序 用网络助手进行连接,测试可以打印: 注意大小端转换。
root@ubuntu:/home/ubuntu/test# bpftrace accept.bt
Attaching 2 probes...
inet_csk_accept
tcp_accept: 192.168.40.1 :1782 62982--> 192.168.40.137 :6666
inet_csk_accept
tcp_accept: 192.168.40.1 :2038 62983--> 192.168.40.137 :6666
inet_csk_accept
tcp_accept: 192.168.40.1 :6902 63002--> 192.168.40.137 :6666
4.4.3 探测mysql执行的语句 以及对应的耗时。
mysql.bt
uprobe:/usr/sbin/mysqld:*dispatch_command*
{
@start = nsecs;
@sql = str(*arg1);
}
uretprobe:/usr/sbin/mysqld:*dispatch_command*
/@start/
{
$dur = nsecs - @start;
printf("%u %s \n", $dur, @sql);
}
mysql执行结果: 可以看到对应的mysql执行语句 以及对应的耗时
root@ubuntu:/home/ubuntu/test# bpftrace mysql.bt
WARNING: Addrspace is not set
Attaching 2 probes...
1175223 show databases
12474975 CREATE DATABASE `mytest2`
12438296 CREATE DATABASE mytest3
541816 CREATE DATABASE 'mytest3' #这种正常的单引号 创建会失败
#mysql创建后,第一次登录mysql可以直接登录,要给root手动设置密码。 set password for root@localhost = password('123456');
#这次用的这个密码
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';
FLUSH PRIVILEGES;
参考,手头已有文档以及网络:
eBPF 入门开发实践教程一:Hello World,基本框架和开发流程 - eunomia
如何在 Ubuntu 上配置 eBPF 开发环境 | YaoYao’s Blog