2024年最全eBPF学习 - 入门,2024年最新阿里牛逼

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取


#!/usr/bin/env python3
# 1. 导入BCC库
from bcc import BPF

# 2. 加载BPF程序
b = BPF(src_file="hello.c")
# 3. 将 BPF 程序挂载到内核探针(简称 kprobe)
b.attach_kprobe(event="do\_sys\_openat", fn_name="hello\_world")
# 4. 读取内核调试文件 /sys/kernel/debug/tracing/trace\_pipe 的内容,并打印到标准输出中
b.trace_print()

  1. 执行hellp.py
# python3 hello.py
b' ps-2098 [002] d... 307.515292: bpf\_trace\_printk: Hello, World!'
b' sh-2099 [001] d... 307.529035: bpf\_trace\_printk: Hello, World!'
b' sh-2099 [001] d... 307.529155: bpf\_trace\_printk: Hello, World!'
b' cpuUsage.sh-2100 [002] d... 307.531789: bpf\_trace\_printk: Hello, World!'
b' cpuUsage.sh-2100 [002] d... 307.531938: bpf\_trace\_printk: Hello, World!'

trace_print输出的格式可由 /sys/kernel/debug/tracing/trace_options 来修改。比如前面这个默认的输出中,每个字段的含义如下所示:

  • ps-2098:表示进程名字和PID
  • [002]:表示CPU编号
  • [d…]:表示一系列选项
  • 307.515292:表示时间戳
  • bpf_trace_printk:表示函数名
  • Hello, World!: bpf_trace_printk传入的字符串
使用BPF映射来获取文件信息

BPF 程序可以利用 BPF 映射(map)进行数据存储,而用户程序也需要通过 BPF 映射,同运行在内核中的 BPF 程序进行交互。BCC 定义了一系列的库函数和辅助宏定义。比如,你可以使用 BPF_PERF_OUTPUT 来定义一个 Perf 事件类型的 BPF 映射。
我们可以通过这些辅助函数将我们需要的数据映射到用户态程序中

  1. 使用bpf辅助函数映射数据

// 包含头文件
#include <uapi/linux/openat2.h>
#include <linux/sched.h>

// 定义数据结构
struct data_t {
  u32 pid;
  u64 ts;
  char comm[TASK_COMM_LEN];
  char fname[NAME_MAX];
};

// 定义性能事件映射
BPF_PERF_OUTPUT(events);


// 定义kprobe处理函数
int hello_world(struct pt_regs \*ctx, int dfd, const char __user \* filename, struct open_how \*how)
{
  struct data_t data = { };

  // 获取进程的 TGID 和 PID,这儿定义的 data.pid 数据类型为 u32,所以高 32 位舍弃掉后就是进程的 PID
  data.pid = bpf_get_current_pid_tgid();
  // 获取系统自启动以来的时间,单位是纳秒
  data.ts = bpf_ktime_get_ns();

  // 获取进程名
  if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0)
  {
    //从中指针处读取固定大小的数据
    bpf_probe_read(&data.fname, sizeof(data.fname), (void \*)filename);
  }

  // 提交性能事件
  events.perf_submit(ctx, &data, sizeof(data));
  return 0;
}

  1. 在用户态中读取内核提交的性能事件信息

from bcc import BPF

# 加载
b = BPF(src_file="hello.c")
b.attach_kprobe(event="do\_sys\_openat2", fn_name="hello\_world")

# 打印头 TIME(s) COMM PID FILE 
print("%-18s %-16s %-6s %-16s" % ("TIME(s)", "COMM", "PID", "FILE"))

# 定义性能事件回调函数
start = 0
def print\_event(cpu, data, size):
    global start
    event = b["events"].event(data)
    if start == 0:
            start = event.ts
    time_s = (float(event.ts - start)) / 1000000000
    print("%-18.9f %-16s %-6d %-16s" % (time_s, event.comm, event.pid, event.fname))

# 注册性能事件回调函数
b["events"].open_perf_buffer(print_event)
# 循环等待性能事件回调
while 1:
    try:
        #读取映射内容,并执行回调函数
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

  1. 执行程序
# python3 hello.py

<command line>:3:9: note: previous definition is here
#define \_\_HAVE\_BUILTIN\_BSWAP16\_\_ 1
        ^
3 warnings generated.
TIME(s)            COMM             PID    FILE            
0.000000000        b'node'          1429   b'/root/.vscode-server/data/User/workspaceStorage/f610d604c637ea4c98b5ca477b61ef94/vscode.lock'
0.013446614        b'node'          1265   b'/proc/6209/cmdline'
0.191411986        b'node'          1421   b'/proc/meminfo'

eBPF工作原理

eBPF是一个运行在内核的虚拟机,为了确保在内核中安全地执行,eBPF 只提供了非常有限的指令集。这些指令集可用于完成一部分内核的功能,但却远不足以模拟完整的计算机。为了更高效地与内核进行交互,eBPF 指令还有意采用了 C 调用约定,其提供的辅助函数可以在 C 语言中直接调用,极大地方便了 eBPF 程序的开发。eBPF运行时内部结构如图,主要由5大模块组成

  • 第一个模块是eBPF 辅助函数。它提供了一系列用于 eBPF 程序与内核其他模块进行交互的函数。这些函数并不是任意一个 eBPF 程序都可以调用的,具体可用的函数集由 BPF 程序类型决定。
  • 第二个模块是 eBPF 验证器。它用于确保 eBPF 程序的安全。验证器会将待执行的指令创建为一个有向无环图(DAG),确保程序中不包含不可达指令;接着再模拟指令的执行过程,确保不会执行无效指令。
  • 第三个模块是由 11 个 64 位寄存器、一个程序计数器和一个 512 字节的栈组成的存储模块。这个模块用于控制 eBPF 程序的执行。其中,R0 寄存器用于存储函数调用和 eBPF 程序的返回值,这意味着函数调用最多只能有一个返回值;R1-R5 寄存器用于函数调用的参数,因此函数调用的参数最多不能超过 5 个;而 R10 则是一个只读寄存器,用于从栈中读取数据。
  • 第四个模块是即时编译器,它将 eBPF 字节码编译成本地机器指令,以便更高效地在内核中执行。
  • 第五个模块是 BPF 映射(map),它用于提供大块的存储。这些存储可被用户空间程序用来进行访问,进而控制 eBPF 程序的运行状态。

![image.png](https://img-blog.csdnimg.cn/img_convert/49115ab566bc7336d0cbbd4149fdbaba.png#clientId=u441bcb81-25ff-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u73987dec&margin=[object Object]&name=image.png&originHeight=503&originWidth=915&originalType=url&ratio=1&rotation=0&showTitle=false&size=302847&status=done&style=none&taskId=u6042e61e-f352-4bc6-8bf6-6eaa05ab7df&title=)

bpftool

Linux内核在4.15之后添加了bpftool这个工具可以用来查看和操作BPF对象,包括BPF程序和对应的映射表。它的源码位于Linux源代码的tools/bpf/bpftool中

命令简述

bpftool由对象和命令组成,在内核5.13中bpftool的对象包括 prog | map | link | cgroup | perf | net | feature | btf | gen | struct_ops | iter
选项支持:

  • -j、–json
  • -p、–pretty
  • -f、–bpffs
  • -m、–mapcompat
  • -n、-nomount
bpftool [OPTIONS] OBJECT {COMMAND | help}

对于每一类对象都有一个帮助文档,比如

# bpftool prog help
Usage: /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog { show | list } [PROG]
       /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog dump xlated PROG [{ file FILE | opcodes | visual | linum }]
       /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog dump jited  PROG [{ file FILE | opcodes | linum }]
       /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog pin   PROG FILE
       /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog { load | loadall } OBJ  PATH \
                         [type TYPE] [dev NAME] \
                         [map { idx IDX | name NAME } MAP]\
                         [pinmaps MAP_DIR]
       /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog attach PROG ATTACH_TYPE [MAP]
       /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog detach PROG ATTACH_TYPE [MAP]
       /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog run PROG \
                         data_in FILE \
                         [data_out FILE [data_size_out L]] \
                         [ctx_in FILE [ctx_out FILE [ctx_size_out M]]] \
                         [repeat N]
       /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog profile PROG [duration DURATION] METRICs
       /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog tracelog
       /usr/lib/linux-tools/5.13.0-39-generic/bpftool prog help

       MAP := { id MAP_ID | pinned FILE | name MAP_NAME }
       PROG := { id PROG_ID | pinned FILE | tag PROG_TAG | name PROG_NAME }
       TYPE := { socket | kprobe | kretprobe | classifier | action |
                 tracepoint | raw_tracepoint | xdp | perf_event | cgroup/skb |
                 cgroup/sock | cgroup/dev | lwt_in | lwt_out | lwt_xmit |
                 lwt_seg6local | sockops | sk_skb | sk_msg | lirc_mode2 |
                 sk_reuseport | flow_dissector | cgroup/sysctl |
                 cgroup/bind4 | cgroup/bind6 | cgroup/post_bind4 |
                 cgroup/post_bind6 | cgroup/connect4 | cgroup/connect6 |
                 cgroup/getpeername4 | cgroup/getpeername6 |
                 cgroup/getsockname4 | cgroup/getsockname6 | cgroup/sendmsg4 |
                 cgroup/sendmsg6 | cgroup/recvmsg4 | cgroup/recvmsg6 |
                 cgroup/getsockopt | cgroup/setsockopt | cgroup/sock_release |
                 struct_ops | fentry | fexit | freplace | sk_lookup }
       ATTACH_TYPE := { msg_verdict | stream_verdict | stream_parser |
                        flow_dissector }
       METRIC := { cycles | instructions | l1d_loads | llc_misses | itlb_misses | dtlb_misses }
       OPTIONS := { {-j|--json} [{-p|--pretty}] | {-f|--bpffs} |
	            {-m|--mapcompat} | {-n|--nomount} }

bpftool perf

perf子命令用来显示哪些BPF程序正在通过perf_event_open进行挂载。

# bpftool perf
pid 16377  fd 5: prog_id 32  kprobe  func do_sys_openat2  offset 0

bpftool prog show

bpftool prog show会列出所有的程序

# bpftool prog show
3: cgroup_device  tag e3dbd137be8d6168  gpl
	loaded_at 2022-04-17T08:33:56+0000  uid 0
	xlated 504B  jited 309B  memlock 4096B
4: cgroup_skb  tag 6deef7357e7b4530  gpl
	loaded_at 2022-04-17T08:33:56+0000  uid 0
	xlated 64B  jited 54B  memlock 4096B
5: cgroup_skb  tag 6deef7357e7b4530  gpl
	loaded_at 2022-04-17T08:33:56+0000  uid 0
	xlated 64B  jited 54B  memlock 4096B
6: cgroup_device  tag 0ecd07b7b633809f  gpl
	loaded_at 2022-04-17T08:34:00+0000  uid 0
	xlated 496B  jited 307B  memlock 4096B
7: cgroup_skb  tag 6deef7357e7b4530  gpl
	loaded_at 2022-04-17T08:34:00+0000  uid 0
	xlated 64B  jited 54B  memlock 4096B
8: cgroup_skb  tag 6deef7357e7b4530  gpl
	loaded_at 2022-04-17T08:34:00+0000  uid 0
	xlated 64B  jited 54B  memlock 4096B
9: cgroup_device  tag ee0e253c78993a24  gpl
	loaded_at 2022-04-17T08:34:04+0000  uid 0
	xlated 416B  jited 255B  memlock 4096B
10: cgroup_device  tag e3dbd137be8d6168  gpl
	loaded_at 2022-04-17T08:34:06+0000  uid 0
	xlated 504B  jited 309B  memlock 4096B
11: cgroup_device  tag c8b47a902f1cc68b  gpl
	loaded_at 2022-04-17T08:34:06+0000  uid 0
	xlated 464B  jited 288B  memlock 4096B
12: cgroup_device  tag 8b9c33f36f812014  gpl
	loaded_at 2022-04-17T08:34:08+0000  uid 0
	xlated 744B  jited 447B  memlock 4096B
13: cgroup_skb  tag 6deef7357e7b4530  gpl
	loaded_at 2022-04-17T08:34:08+0000  uid 0
	xlated 64B  jited 54B  memlock 4096B
14: cgroup_skb  tag 6deef7357e7b4530  gpl
	loaded_at 2022-04-17T08:34:08+0000  uid 0
	xlated 64B  jited 54B  memlock 4096B
32: kprobe  name hello_world  tag 38dd440716c4900f  gpl
	loaded_at 2022-04-17T11:09:18+0000  uid 0
	xlated 104B  jited 70B  memlock 4096B
	btf_id 66

bpftool prog dump xlated

xlated将BPF指令翻译为汇编指令打印出来。

# bpftool prog dump xlated id 32
int hello_world(void * ctx):
; int hello_world(void *ctx)
   0: (b7) r1 = 33
; ({ char _fmt[] = "Hello, World!"; bpf_trace_printk_(_fmt, sizeof(_fmt)); });
   1: (6b) *(u16 *)(r10 -4) = r1
   2: (b7) r1 = 1684828783
   3: (63) *(u32 *)(r10 -8) = r1
   4: (18) r1 = 0x57202c6f6c6c6548
   6: (7b) *(u64 *)(r10 -16) = r1
   7: (bf) r1 = r10
; 
   8: (07) r1 += -16
; ({ char _fmt[] = "Hello, World!"; bpf_trace_printk_(_fmt, sizeof(_fmt)); });
   9: (b7) r2 = 14
  10: (85) call bpf_trace_printk#-61904
; return 0;
  11: (b7) r0 = 0
  12: (95) exit


正是我们前面写的 C 代码,而其他行则是具体的 BPF 指令。具体每一行的 BPF 指令又分为三部分:

  • 第一部分,冒号前面的数字 0-12 ,代表 BPF 指令行数;
  • 第二部分,括号中的 16 进制数值,表示 BPF 指令码。它的具体含义你可以参考 IOVisor BPF 文档,比如第 0 行的 0xb7 表示为 64 位寄存器赋值。
  • 第三部分,括号后面的部分,就是 BPF 指令的伪代码。

BPF系统调用

在用户态与内核进行交互必须要使用系统调用来完成,在eBPF程序中使用的系统调用

// cmd代表操作命令,比如BPF_PROG_LOAD是加载eBPF程序
// attr代表bpf_attr类型的eBPF属性指针,不同类型的操作命令需要传入不同的属性参数
// size代表属性的大小
int bpf(int cmd, union bpf_attr *attr, unsigned int size);

在内核5.13中已经支持了以下命令

enum bpf_cmd {
	BPF_MAP_CREATE,
	BPF_MAP_LOOKUP_ELEM,
	BPF_MAP_UPDATE_ELEM,
	BPF_MAP_DELETE_ELEM,
	BPF_MAP_GET_NEXT_KEY,
	BPF_PROG_LOAD,
	BPF_OBJ_PIN,
	BPF_OBJ_GET,
	BPF_PROG_ATTACH,
	BPF_PROG_DETACH,
	BPF_PROG_TEST_RUN,
	BPF_PROG_GET_NEXT_ID,
	BPF_MAP_GET_NEXT_ID,
	BPF_PROG_GET_FD_BY_ID,
	BPF_MAP_GET_FD_BY_ID,
	BPF_OBJ_GET_INFO_BY_FD,
	BPF_PROG_QUERY,
	BPF_RAW_TRACEPOINT_OPEN,
	BPF_BTF_LOAD,
	BPF_BTF_GET_FD_BY_ID,
	BPF_TASK_FD_QUERY,
	BPF_MAP_LOOKUP_AND_DELETE_ELEM,
	BPF_MAP_FREEZE,
	BPF_BTF_GET_NEXT_ID,
	BPF_MAP_LOOKUP_BATCH,
	BPF_MAP_LOOKUP_AND_DELETE_BATCH,
	BPF_MAP_UPDATE_BATCH,
	BPF_MAP_DELETE_BATCH,
	BPF_LINK_CREATE,
	BPF_LINK_UPDATE,
	BPF_LINK_GET_FD_BY_ID,
	BPF_LINK_GET_NEXT_ID,
	BPF_ENABLE_STATS,
	BPF_ITER_CREATE,
	BPF_LINK_DETACH,
	BPF_PROG_BIND_MAP,
};

其常用的命令如下:

命令说明
BPF_MAP_CREATE创建一个BPF映射
BPF_MAP_LOOKUP_ELEM

BPF_MAP_UPDATE_ELEM
BPF_MAP_DELETE_ELEM
F_MAP_LOOKUP_AND_DELETE_ELEM
BPF_MAP_GET_NEXT_KEY | BPF映射相关的操作命令 |
| BPF_PROG_LOAD | 验证并加载BPF程序 |
| BPF_PROG_ATTACH
BPF_PROG_DETACH | 挂载/协助BPF程序 |
| BPF_OBJ_PIN | 把BPF程序或映射挂载到sysfs中的/sys/fs/bpf目录中 |
| BPF_OBJ_GET | 从/sys/fs/bpf目录中从查找BPF程序 |
| BPF_BTF_LOAD | 验证并加载BTF信息 |

BPF辅助函数

eBPF 程序并不能随意调用内核函数,因此,内核定义了一系列的辅助函数,用于 eBPF 程序与内核其他模块进行交互。比如 bpf_trace_printk() 就是最常用的一个辅助函数,用于向调试文件系统(/sys/kernel/debug/tracing/trace_pipe)写入调试信息。
eBPF 内部的内存空间只有寄存器和栈。所以,要访问其他的内核空间或用户空间地址,就需要借助 bpf_probe_read 这一系列的辅助函数。这些函数会进行安全性检查,并禁止缺页中断的发生。
而在 eBPF 程序需要大块存储时,就不能像常规的内核代码那样去直接分配内存了,而是必须通过 BPF 映射(BPF Map)来完成。
并不是所有的辅助函数都可以在 eBPF 程序中随意使用,不同类型的 eBPF 程序所支持的辅助函数是不同的。比如,对于 Hello World 示例这类内核探针(kprobe)类型的 eBPF 程序,你可以在命令行中执行 bpftool feature probe ,来查询当前系统支持的辅助函数列表:

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

程序所支持的辅助函数是不同的。比如,对于 Hello World 示例这类内核探针(kprobe)类型的 eBPF 程序,你可以在命令行中执行 bpftool feature probe ,来查询当前系统支持的辅助函数列表:

[外链图片转存中…(img-nJeafLp7-1715671826784)]
[外链图片转存中…(img-J7cPGG4o-1715671826784)]
[外链图片转存中…(img-j0yXhPWc-1715671826784)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 20
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值