《Learning eBPF》读书笔记

news2024/9/23 1:29:05

文章目录

  • 章节内容简介
  • 第1章 什么是ebpf,为什么它很重要?
  • 第2章 ebpf版hello world
  • 第3章 ebpf程序剖析
    • ebpf虚拟机
    • 一个xdp的hello word例子
      • c语言程序
      • 编译为ebpf字节码
      • 加载bpf程序到内核
      • 检查加载的程序
      • 运行时编译机器码
      • 附加到事件
      • 分离程序
      • 卸载程序
  • 第4章 bpf()系统调用
  • 第5章 CO-RE, BTF, and Libbpf
    • CO-RE概览
    • BTF例子
    • libbpf
      • 生成内核头文件
      • ebpf程序的节
      • CO-RE 内存访问
      • 编译CO-RE ebpf程序
      • 为何要生成CO-RE用户态代码
      • bpf skeletons
      • 用户态程序
  • 第6章 ebpf验证器
  • 第7章 ebpf程序和挂接类型
    • Kfuncs
    • 追踪程序
      • kprobe/ketprobe
      • fentry/fexit
      • tracepoint
      • btf-enabled tracepoint
      • 用户态挂接点
      • LSM
    • 网络程序
      • sockets
      • tc:traffic control
      • xdp
      • flow dissector
      • lightweight tunnels
      • cgroups
      • infrared controllers
      • bpf挂接类型
  • 第8章 ebpf网络编程
  • 第9章 ebpf安全
  • 第10章 ebpf编程
  • 第11章 ebpf的未来演变

由于英文不好,看得过程中还要查单词,注定是要略读了,而且不知道能不能坚持下来。

章节内容简介

相关章节,标黑的是准备看下的:
第1章:介绍
第2章:hello world例子以及ebpf程序和ebpf map概念的介绍
第3章:ebpf程序以及他们是如何运行在内核中的
第4章:探索用户态进程和ebpf程序之间的接口
第5章:介绍co-re,即一次编译,任何地方运行
第6章:ebpf验证器
第7章:介绍许多不同类型的ebpf程序和他们的挂载点
第8章:网络特性的ebpf程序介绍
第9章:ebpf安全特性应用
第10章:许多编程语言应用的概览,用户态进程和epbf程序交互使用的库和框架。
第11章:展望

第1章 什么是ebpf,为什么它很重要?

一些基础知识介绍,ebpf相比与内核模块的优势等

第2章 ebpf版hello world

一个bcc的hello world程序:

#!/usr/bin/python3
from bcc import BPF

program = r"""
int hello(void *ctx) {
    bpf_trace_printk("Hello World!");
    return 0;
}
"""

b = BPF(text=program)#一旦执行,则bpf程序立即被加载
syscall = b.get_syscall_fnname("execve")#屏蔽不同版本内核差异
b.attach_kprobe(event=syscall, fn_name="hello")#挂接

b.trace_print()#读取内容

bpf_trace_printk()输出内容查看:cat /sys/kernel/debug/tracing/trace_pipe,需要注意的是,所有ebpf的输出都会在这里显示,因此需要ebpf map来存储打印,数据结构等。

第3章 ebpf程序剖析

ebpf虚拟机

真正的epbf程序运行于ebpf虚拟机,用受限的c写的程序会先被编译成ebpf字节码,然后被解释成机器码code
ebpf虚拟机使用10个通用寄存器0-9,而寄存器10被用作栈帧指针,是只读的。这些寄存器是软件实现的,不是真实的。0存放返回值,1~5存放参数。
ebpf指令是用这样的结构体实现的:

struct bpf_insn {
 __u8 code; /* opcode */ 
 __u8 dst_reg:4; /* dest register */ 
 __u8 src_reg:4; /* source register */
 __s16 off; /* signed offset */ 
 __s32 imm; /* signed immediate constant */
};

一个xdp的hello word例子

c语言程序

这个例子是用c写的,如下:

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

int counter = 0;

SEC("xdp") //定义一个叫xdp的段,后面将会被编译进obj文件中。
int hello(struct xdp_md *ctx) {//函数名就是ebpf程序名
    bpf_printk("Hello World %d", counter);//像之前bcc一样,打印信息
    counter++;
    return XDP_PASS;//让内容正常处理这个包
}

char LICENSE[] SEC("license") = "Dual BSD/GPL";//验证器好像会来检查这个

编译为ebpf字节码

在开发工具章节中有提到:

LLVM是目前唯一提供BPF后端的编译器套件。gcc目前不支持BPF。
典型的工作流程是用C语言编写BPF程序,由LLVM编译成object / ELF文件,由用户空间BPF ELF加载程序解析,并通过BPF系统调用推入内核。内核验证BPF指令并将其jit,为程序返回一个新的文件描述符,然后可以将其附加到子系统(例如网络)。如果得到支持,子系统可以进一步将BPF程序卸载到硬件(例如网卡),即xdp。

编译上面代码的makefile如下:

hello.bpf.o: %.o: %.c
 	clang \
 	-target bpf \
 	-I/usr/include/$(shell uname -m)-linux-gnu \
 	-g \
 	-O2 -c $< -o $@

实际执行如下:

clang \
    -target bpf \
        -I/usr/include/aarch64-linux-gnu \
        -g \
    -O2 -o hello.bpf.o -c hello.bpf.c

编译生成的obj文件:

#file hello.bpf.o
hello.bpf.o: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), with debug_info, not stripped

真正的c程序编出来是这样的:

# file a.out
a.out: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=c9e265eba9305dc511e73fbdabebaabd5e1242cd, for GNU/Linux 3.7.0, not stripped

使用readelf -a查看hello.bpf.o:

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Linux BPF
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          3968 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         27
  Section header string table index: 1

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .strtab           STRTAB           0000000000000000  00000e73
       0000000000000109  0000000000000000           0     0     1
  [ 2] .text             PROGBITS         0000000000000000  00000040
       0000000000000000  0000000000000000  AX       0     0     4
  [ 3] xdp               PROGBITS         0000000000000000  00000040
       0000000000000060  0000000000000000  AX       0     0     8

可以看到有一个xdp的节。
看下对应汇编,直接使用objdump不认识:

# objdump -S hello.bpf.o

hello.bpf.o:     file format elf64-little

objdump: can't disassemble for architecture UNKNOWN!

使用llvm-objdump:

# llvm-objdump -S hello.bpf.o

hello.bpf.o:    file format elf64-bpf

Disassembly of section xdp:#xdp节的汇编,对应了c源码中用SEC()定义的

0000000000000000 <hello>:#这个节包含了hello这个函数
;     bpf_printk("Hello World %d", counter);
       0:       18 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r6 = 0 ll
       2:       61 63 00 00 00 00 00 00 r3 = *(u32 *)(r6 + 0)
       3:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       5:       b7 02 00 00 0f 00 00 00 r2 = 15
       6:       85 00 00 00 06 00 00 00 call 6
;     counter++;
       7:       61 61 00 00 00 00 00 00 r1 = *(u32 *)(r6 + 0)
       8:       07 01 00 00 01 00 00 00 r1 += 1
       9:       63 16 00 00 00 00 00 00 *(u32 *)(r6 + 0) = r1
;     return XDP_PASS;
      10:       b7 00 00 00 02 00 00 00 r0 = 2
      11:       95 00 00 00 00 00 00 00 exit

上面的就是ebpf虚拟机的字节码

加载bpf程序到内核

在这一小节中,完成这个任务的是使用bpftool,使用如下命令:

bpftool prog load hello.bpf.o /sys/fs/bpf/hello

命令执行后没有输出,不过可以通过ls来确认:

# ls /sys/fs/bpf/
hello

这样,ebpf程序已经被成功加载了。

检查加载的程序

使用如下命令:

# bpftool prog list
85: cgroup_device  tag 134b8a301991f6b7  gpl
        loaded_at 2024-01-06T05:15:20+0000  uid 0
        xlated 504B  jited 464B  memlock 4096B
        pids systemd(1)
...
123: xdp  name hello  tag d35b94b4c0c10efb  gpl
        loaded_at 2024-01-06T15:46:54+0000  uid 0
        xlated 96B  jited 140B  memlock 4096B  map_ids 19,20
        btf_id 126

可以看到刚刚的程序已经被加载了。
这个ebpf进程被分配的id是123,使用这个id,可以用bpftool查询更多信息。
其中jited表示程序已被jit-编译。
而map_ids表示程序使用了19和20这两个ebpf map。分别是ebpf的bss段和rodata段:

# bpftool map list
19: array  name hello.bss  flags 0x400
        key 4B  value 4B  max_entries 1  memlock 4096B
        btf_id 126
20: array  name hello.rodata  flags 0x80
        key 4B  value 15B  max_entries 1  memlock 4096B
        btf_id 126  frozen
...
# bpftool map dump name hello.bss
[{
        "value": {
            ".bss": [{
                    "counter": 14750
                }
            ]
        }
    }
]
root@lima-learning-ebpf:/Users/xuweidong/project/learning-ebpf/chapter3# bpftool map dump name hello.rodata
[{
        "value": {
            ".rodata": [{
                    "hello.____fmt": "Hello World %d"
                }
            ]
        }
    }
]

其中bss段也就是存放了全局变量counter,rodata段是我们的字符串。需要注意的是只有编译时加了-g以及支持BTF,才能打印出这个效果。
tag是程序指令的sha校验和,当程序加载卸载,id可能会变,但tag不会变。
可以直接使用bpftool查看被转换的ebpf字节码:

#  bpftool prog dump xlated name hello
int hello(struct xdp_md * ctx):
; bpf_printk("Hello World %d", counter);
   0: (18) r6 = map[id:19][0]+0
   2: (61) r3 = *(u32 *)(r6 +0)
   3: (18) r1 = map[id:20][0]+0
   5: (b7) r2 = 15
   6: (85) call bpf_trace_printk#-73840
; counter++;
   7: (61) r1 = *(u32 *)(r6 +0)
   8: (07) r1 += 1
   9: (63) *(u32 *)(r6 +0) = r1
; return XDP_PASS;
  10: (b7) r0 = 2
  11: (95) exit

运行时编译机器码

jit即just in time,虽然上面看到的转换后的字节码已经是很低级了,但是还不是最终的机器码。ebpf使用即时编译器来将ebpf字节码转换成真正运行本地cpu上的机器码。
仍然使用bpftool查看真正的机器码:

#  bpftool prog dump jited name hello
int hello(struct xdp_md * ctx):
bpf_prog_d35b94b4c0c10efb_hello:
; bpf_printk("Hello World %d", counter);
  0:   stp     x29, x30, [sp, #-16]!
  4:   mov     x29, sp
  8:   stp     x19, x20, [sp, #-16]!
  c:   stp     x21, x22, [sp, #-16]!
 10:   stp     x25, x26, [sp, #-16]!
 14:   mov     x25, sp
 18:   mov     x26, #0
 1c:   sub     sp, sp, #0
 20:   mov     x19, #-281474976710656
 24:   movk    x19, #32768, lsl #32
 28:   movk    x19, #2184, lsl #16
 2c:   mov     x10, #0
 30:   ldr     w2, [x19, x10]
 34:   mov     x0, #-239581865705473
 38:   movk    x0, #16459, lsl #16
 3c:   movk    x0, #44304
 40:   mov     x1, #15
 44:   mov     x10, #-15440
 48:   movk    x10, #27055, lsl #16
 4c:   movk    x10, #51572, lsl #32
 50:   blr     x10
 54:   add     x7, x0, #0
; counter++;
 58:   mov     x10, #0
 5c:   ldr     w0, [x19, x10]
 60:   add     x0, x0, #1
 64:   mov     x10, #0
 68:   str     w0, [x19, x10]
; return XDP_PASS;
 6c:   mov     x7, #2
 70:   mov     sp, sp
 74:   ldp     x25, x26, [sp], #16
 78:   ldp     x21, x22, [sp], #16
 7c:   ldp     x19, x20, [sp], #16
 80:   ldp     x29, x30, [sp], #16
 84:   add     x0, x7, #0
 88:   ret

至此“Hello World”程序已经加载到内核中,但是此时它还没有与事件关联,因此没有任何东西会触发它运行。它需要附加到一个事件。

附加到事件

仍然使用bpftool,额外的,如果是xdp程序,也可以使用ip link来加载到网络接口上:

 bpftool net attach xdp name hello  dev eth0
root@lima-learning-ebpf:/Users/xuweidong/project/learning-ebpf/chapter3# bpftool net list
xdp:
eth0(2) driver id 123

tc:

flow_dissector:

netfilter:
# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdp qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:55:55:c9:9e:d6 brd ff:ff:ff:ff:ff:ff
    prog/xdp id 123 tag d35b94b4c0c10efb jited

查看输出,可以不使用/sys/kernel/debug/tracing/trace_pipe,而使用bpftool查看:

# bpftool prog  tracelog
bpftool-16915   [002] d.s.. 39786.828735: bpf_trace_printk: Hello World 13115
          <idle>-0       [002] d.s.. 39786.830403: bpf_trace_printk: Hello World 13116
          <idle>-0       [003] d.s.. 39797.947963: bpf_trace_printk: Hello World 13117
...

分离程序

事件关联去除:

# bpftool net detach xdp  dev eth0
root@lima-learning-ebpf:/Users/xuweidong/project/learning-ebpf/chapter3# bpftool net list
xdp:

tc:

flow_dissector:

netfilter:

卸载程序

从内核卸载ebpf程序很直接,直接rm就行了

# rm /sys/fs/bpf/hello

成功了同样没输出,可以使用bpftool prog show name hello查看,没输出代表卸载成功。

第4章 bpf()系统调用

这章探索了最原始的系统调用bpf(),也即前面的bcc,以及利用libbpf写的程序真正调用的系统接口。这章暂时先不看了。

第5章 CO-RE, BTF, and Libbpf

CO-RE概览

CO-RE即一次编译,任何地方运行compile once,run everywhere。CO-RE一般被读做和单词core同样发音,而不是读两个音节。
专有名词解释
BTF
一种用于表示数据结构布局和函数签名的格式,在CO-RE中用于确定在编译时和运行时的结构体的任何差异,也在bpftool中用来用更易读的格式打印结构体内容。linux 5.4开始支持
内核头文件
包含于内核源码,用来描述内核使用的数据结构,在不同版本的内核,头文件可能有变更。ebpf开发者可以选择包含单独的头文件,也可以使用bpftool来从运行的系统中生成一个叫vmlinux.h的头文件,这个头文件包含了所有的ebpf程序也许需要的内核的数据结构信息。
编译支持
clang编译器可以在加了-g的flag编译ebpf程序时包含CO-RE相关信息。GCC也添加了支持,但是要在GCC12才有。
数据结构重定位的库支持
当用户空间程序向内核加载ebpf程序时,CO-RE需要根据编译进obj的CO-RE重定位信息将字节码进行调整以适应结构体在编译时和目标机器运行时的实现的不同。目前libbpf,还有cilium的go库以及aya都实现了这个。
BPF skeleton
BPF skeleton可以在编译ebpf obj文件时自动生成,这里面包含了一些易用的函数,使用这些函数可以让用户态代码来调用去管理bpf程序的生命周期-比如加载到内核,关联到事件之类的。可以使用bpftool gen skeleton来生成skeleton。这些函数是高层抽象的,相比于libpbf等基础库,这些函数对开发者来说可以更方便。

BTF例子

BCC结构体:

struct user_msg_t {
 char message[12];
};

BTF,定义了这个结构体的布局:

[1] TYPEDEF 'u32' type_id=2
[2] TYPEDEF '__u32' type_id=3
[3] INT 'unsigned int' size=4 bits_offset=0 nr_bits=32 encoding=(none)
[4] STRUCT 'user_msg_t' size=12 vlen=1
 'message' type_id=6 bits_offset=0
[5] INT 'char' size=1 bits_offset=0 nr_bits=8 encoding=(none)
[6] ARRAY '(anon)' type_id=5 index_type_id=7 nr_elems=12
[7] INT '__ARRAY_SIZE_TYPE__' size=4 bits_offset=0 nr_bits=32 encoding=(none)

BCC函数

int hello(void *ctx)

BTF,typeid 32定义了函数原型信息:

[10] PTR '(anon)' type_id=0
...
[23] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
...
[31] FUNC_PROTO '(anon)' ret_type_id=23 vlen=1
 'ctx' type_id=10
[32] FUNC 'hello' type_id=31 linkage=static

另外也可以这样来获取btf信息:

# bpftool btf dump map name config                               4031 0 lima-guestagent Hey root!
[1] TYPEDEF 'u32' type_id=2                                                                                                             4033 0 lima-guestagent Hey root!
[4] STRUCT 'user_msg_t' size=12 vlen=1                                                                                                  4034 0 lima-guestagent Hey root!
        'message' type_id=6 bits_offset=0

对应的BPF HASH:

BPF_HASH(config, u32, struct user_msg_t);

libbpf

生成内核头文件

就像前面提到的vmlinux.h可以减少的你工作,可以使用bpftool来生成:

bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

如果没有/sys/kernel/btf/vmlinux这个文件,那可能需要开启CONFIG_DEBUG_INFO_BTF选项来重编内核。
如果你想要使用libbpf的bpf帮助函数,那么你就需要包含vmlinux.h或者linux/type.h,因为libbpf里使用了像u32,u64这种类型。

ebpf程序的节

使用libbpf的每个ebpf程序都需要使用SEC()来标记程序类型。
比如SEC("kprobe")会在编译的elf中产生一个kprobe的section,然后libbpf就知道应该将这个程序加载为BPF_PROG_TYPE_KPROBE。进一步的,通过程序类型,你可以指定这个程序关联的事件,libbpf将会使用这些信息自动的关联,比如使用SEC("ksyscall/execve"),将告诉加载器自动关联到kprobe的架构特定的execve上。
可用的节名和格式在libbpf手册可以查到。
以下是内核ebpf程序hello-buffer-config.bpf.c的部分节选,节的定义声明了ebpf程序应该挂接在哪,挂接的程序在其后定义:

SEC("ksyscall/execve")
int BPF_KPROBE_SYSCALL(hello, const char *pathname) //BPF_KPROBE_SYSCALL是一个宏,可以方便的访问syscall的参数,pathname就是即将被程序程序的路径名,而hello则是ebpf的程序名
{
 struct data_t data = {};
 struct user_msg_t *p;
 data.pid = bpf_get_current_pid_tgid() >> 32;
 data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
 bpf_get_current_comm(&data.command, sizeof(data.command));
 bpf_probe_read_user_str(&data.path, sizeof(data.path), pathname); //使用帮助函数,将pathname包含到perf 缓冲区输出的数据中去。
 p = bpf_map_lookup_elem(&my_config, &data.uid); 
 if (p != 0) {
 bpf_probe_read_kernel(&data.message, sizeof(data.message), p->message); 
 } else {
 bpf_probe_read_kernel(&data.message, sizeof(data.message), message);
 }
 bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU, 
 &data, sizeof(data)); //BPF_KPROBE_SYSCALL中定义了ctx
 return 0;
}

CO-RE 内存访问

ebpf验证器不允许你通过指针访问内存,libbpf提供了一些CO-RE包装函数,比如d= a->b->c->d;可以写成这样:

struct b_t *b;
struct c_t *c;
bpf_core_read(&b, 8, &a->b);
bpf_core_read(&c, 8, &b->c);
bpf_core_read(&d, 8, &c->d);

或更紧凑的:

d = BPF_CORE_READ(a, b, c, d);

在这之后,你就可以通过bpf_probe_read_kernel()从指针d来读取了。

编译CO-RE ebpf程序

编译参数
llvm-strip -g <object file> 保留-g,去除DWARF调试信息
-O2 clang产生ebpf字节码所需
-D __TARGET_ARCH_($ARCH) 当你使用了一些libbpf定义的宏,你必须在编译时指定目标架构,因为这些宏是架构相关的.在这里$ARCH是像arm64,amd64等架构名。
因此,一次编译,到处运行,也许应该叫做在每个架构上,一次编译,到处运行。
最终的编译脚本如下:

hello-buffer-config.bpf.o: %.o: %.c
 clang \
 -target bpf \
 -D __TARGET_ARCH_$(ARCH) \
 -I/usr/include/$(shell uname -m)-linux-gnu \
 -Wall \
 -O2 -g \
 -c $< -o $@
 llvm-strip -g $@

为何要生成CO-RE用户态代码

前面章节中,可以利用bpftool直接将ebpf程序加载到内核,甚至关联到事件,然而,在许多应用中,你可能并不想让用户自己来运行bpftool加载ebpf程序。相反,你希望将此功能构建到作为可执行文件提供的专用用户空间程序中。为此,你需要自己写一个用户态程序。

bpf skeletons

在这里的例子中,可以使用如下命令来从已经编译好的ebpf obj文件来自动生成skeletons:

bpftool gen skeleton hello-buffer-config.bpf.o > hello-buffer-config.skel.h

在这个生成的头文件中,你可以看到里面包含了ebpf程序和mas的结构体定义,还有一些以obj文件名字为开头的函数:hello_buffer_config_bpf__xxx。这些函数用来管理ebpf程序和maps的生命周期。
在这个头文件的最后,还有这个:

static inline const void *hello_buffer_config_bpf__elf_bytes(size_t *sz)
{
        static const char data[] __attribute__((__aligned__(8))) = "\
\x7f\x45\x4c\x46\x02\x01\x01\0\0\0\0\0\0\0\0\0\x01\0\xf7\0\x01\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\x98\x13\0\0\0\0\0\0\0\0\0\0\x40\0\0\0\0\0\x40\0\x0e\0\
\x01\0\xbf\x16\0\0\0\0\0\0\x18\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x71\x11\0\0\0\0\
...
\0\0\0\0\0\0\0\x70\x0e\0\0\0\0\0\0\x20\x01\0\0\0\0\0\0\x01\0\0\0\x06\0\0\0\x08\
\0\0\0\0\0\0\0\x18\0\0\0\0\0\0\0";

        *sz = sizeof(data) - 1;
        return (const void *)data;
}

这个就是obj文件的内容,当skeleton文件生成后,obj文件就不在需要了
这些函数在稍后编写的用户态程序中都会用到。

用户态程序

以下就是用户态程序hello-buffer-config.c:

... [other #includes]
#include "hello-buffer-config.h" 
#include "hello-buffer-config.skel.h"
... [some callback functions]
int main()
{
 struct hello_buffer_config_bpf *skel;
 struct perf_buffer *pb = NULL;
 int err;
 libbpf_set_print(libbpf_print_fn); //设置回调函数显示libbpf产生的日志信息
 skel = hello_buffer_config_bpf__open_and_load(); //skeleton内联函数,实现了所有maps和程序在elf里的定义以及内核加载
...
 err = hello_buffer_config_bpf__attach(skel); //设置自动挂接到合适的事件
...
 pb = perf_buffer__new(bpf_map__fd(skel->maps.output), 8, handle_event,
 lost_event, NULL, NULL); //创建结构体来处理perfbuffer的输出
 
...
 while (true) { //持续轮询perf buffer
 err = perf_buffer__poll(pb, 100);
...}
 perf_buffer__free(pb); //清理环境的代码
 hello_buffer_config_bpf__destroy(skel);
 return -err;
}

第6章 ebpf验证器

跳过,这章提到你写的程序很大可能通过不了验证器的验证,所以写程序也是个很大的挑战

第7章 ebpf程序和挂接类型

帮助函数的man手册,对应每种ebpf程序类型以及挂接类型,允许使用的帮助函数是不同的,比如在某些类型的ebpf程序中,某些帮助函数是没有意义的。可以使用bpftool feature命令来查看运行使用的帮助函数。

Kfuncs

在ebpf程序中允许调用一些内核内部的函数,这个机制被称为kfuncs。kfuncs允许内部的内核函数被注册到bpf子系统,这样,验证器可以允许ebpf程序来调用这些函数。一般认为程序类型分为两类:跟踪(或性能)程序类型和与网络相关的程序类型。下面将分别介绍这两种。

追踪程序

kprobe/ketprobe

kprobe几乎可以挂接到内核的任何地方,除了一些特殊的地方,比如kprobe本身,这是为了避免死循环。除了函数入口点和退出的地方,还可以设置偏移在特定指令。
系统调用kprobe的上下文参数是一个结构,表示用户空间传递给系统调用的值。“常规”(非系统调用)kprobe的上下文参数是一个结构,表示调用该函数的内核代码传递给该函数的参数,因此该结构取决于函数定义。

fentry/fexit

x86在5.5支持,arm在6.0支持。
fentry/fexit比kprobe/kretprobe更高效,并且fexit除了返回值还能访问函数入参,这是kretprobe办不到的。

SEC("kretprobe/do_unlinkat")
int BPF_KRETPROBE(do_unlinkat_exit, long ret)

SEC("fexit/do_unlinkat")
int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret)

tracepoint

跟踪点是在内核代码中标记的位置(我们将在本章后面讨论用户空间跟踪点)。它们绝不是eBPF所独有的,长期以来一直用于生成内核跟踪输出和SystemTap等工具。与使用kprobes附加到任意指令不同,跟踪点在内核版本之间是稳定的(尽管旧内核可能没有添加到新内核中的完整跟踪点集)。
使用cat /sys/kernel/tracing/available_events可以查看追踪点。
在这里,没有btf的帮助,需要自己使用命令查看格式:

#  cat /sys/kernel/tracing/events/syscalls/sys_enter_execve/format
name: sys_enter_execve
ID: 627
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:const char * filename;    offset:16;      size:8; signed:0;
        field:const char *const * argv; offset:24;      size:8; signed:0;
        field:const char *const * envp; offset:32;      size:8; signed:0;

print fmt: "filename: 0x%08lx, argv: 0x%08lx, envp: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->argv)), ((unsigned long)(REC->envp))

然后自己定义结构体并使用:

struct my_syscalls_enter_execve {
 unsigned short common_type;
 unsigned char common_flags;
 unsigned char common_preempt_count;
 int common_pid;
 long syscall_nr;
 long filename_ptr;
 long argv_ptr;
 long envp_ptr;
};
SEC("tp/syscalls/sys_enter_execve")
int tp_sys_enter_execve(struct my_syscalls_enter_execve *ctx) {

btf-enabled tracepoint

使用vmlinux.h中的结构体:

SEC("tp_btf/sched_process_exec")
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)

用户态挂接点

uprobe/uretprobe,user statically defined tracepoints (USDTs)
如果想挂接到openssl的函数SSL_write(),可以像下面这样定义:

SEC("uprobe/usr/lib/aarch64-linux-gnu/libssl.so.3/SSL_write")

LSM

跳过,安全模块相关

网络程序

网络消息处理过程中有很多ebpf程序类型,当他们经过网络协议栈的很多流程时
network
网络程序和跟踪程序不同,网络程序允许自定义网络行为:
1.使用返回值决定包处理行为,正常处理,丢弃,转发到不同目的地
2.允许ebpf程序修改网络包,socket配置参数等

sockets

tc:traffic control

xdp

flow dissector

lightweight tunnels

cgroups

control groups,

infrared controllers

bpf挂接类型

附件类型对程序在系统中的附加位置提供了更细粒度的控制。对于某些程序类型,它与可以附加到的钩子类型有一对一的关联,因此附加类型由程序类型隐式定义。例如,XDP程序被附加到网络栈中的XDP钩子上。对于一些程序类型,还必须指定附件类型。
附件类型涉及到决定哪些辅助函数有效,并且在某些情况下,它还限制对部分上下文信息的访问。
您还可以在内核函数bpf_prog_load_check_attach(在bpf/ sycall .c中定义)中查看哪些程序类型需要指定附件类型,以及哪些附件类型是有效的。

第8章 ebpf网络编程

跳过,看累了

第9章 ebpf安全

第10章 ebpf编程

第11章 ebpf的未来演变

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1365425.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CAN物理层协议介绍

目录 ​编辑 1. CAN协议简介 2. CAN物理层 3. 通讯节点 4. 差分信号 5. CAN协议中的差分信号 1. CAN协议简介 CAN是控制器局域网络(Controller Area Network)的简称,它是由研发和生产汽车电子产品著称的德国BOSCH公司开发的,并最终成为国际标准(ISO11519) &#xff0…

光明源@智慧公厕系统的功能介绍-详情可点击查看

什么是智慧公厕系统&#xff1f;智慧公厕系统是一种通过科技手段提升公共卫生设施管理和服务水平的解决方案。智慧公厕系统都有哪些功能呢&#xff1f;那么小编讲以光明源的角度来讲一下公厕系统都有哪些功能&#xff01; 光明源智慧公厕系统-实时监控和管理公厕 该系统使用各…

【leetcode】力扣算法之旋转图像【难度中等】

题目描述 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 用例 输入&#xff1a; matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&…

【Spring实战】26 使用Spring Security 保护 Spring Boot Admin

文章目录 1. 定义1.集成流程1&#xff09;添加 Spring Boot Admin 依赖2&#xff09;配置 Spring Boot Admin3&#xff09;启动 Spring Boot Admin 服务4&#xff09;访问 Spring Boot Admin 服务5&#xff09;添加 Spring Security 依赖6&#xff09;配置 Spring Security7&am…

【书生大模型00--开源体系介绍】

书生大模型开源体系介绍 0 通用人工智能1 InternLM性能及模型2 从模型到应用 大模型成为目前很热的关键词&#xff0c;最热门的研究方向&#xff0c;热门的应用&#xff1b;ChatGPT的横空出世所引爆&#xff0c;快速被人们上手应用到各领域&#xff1b; 0 通用人工智能 相信使…

02、Kafka ------ 配置 Kafka 集群

目录 配置 Kafka 集群配置步骤启动各Kafka节点 配置 Kafka 集群 启动命令&#xff1a; 1、启动 zookeeper 服务器端 小黑窗输入命令&#xff1a; zkServer 2、启动 zookeeper 的命令行客户端工具 &#xff08;这个只是用来看连接的节点信息&#xff0c;不启动也没关系&#…

Hadoop3.3.5云服务器安装教程-单机/伪分布式配置

系列文章目录 华为云服务器试用领取 领取的试用云服务器在哪 Hadoop3.3.5云服务器安装教程-单机/伪分布式配置 文章目录 系列文章目录创建hadoop用户更新apt安装SSH、配置SSH无密码登陆安装Java环境安装 Hadoop3.3.5Hadoop单机配置(非分布式)Hadoop伪分布式配置运行Hadoop伪分…

【AI视野·今日Sound 声学论文速览 第四十二期】Fri, 5 Jan 2024

AI视野今日CS.Sound 声学论文速览 Fri, 5 Jan 2024 Totally 10 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers PosCUDA: Position based Convolution for Unlearnable Audio Datasets Authors Vignesh Gokul, Shlomo Dubnov深度学习模型需要大量干净的…

Windows.OpenSSL生成ssl证书配置到nginx

一、下载OpenSSL程序安装 到E:\soft\OpenSSL-Win64 二、打开一个CMD控制台窗口&#xff0c;设置好openssl.cnf路径 E: cd E:\soft\OpenSSL-Win64\bin set OPENSSL_CONFE:\soft\OpenSSL-Win64\bin\openssl.cnf 三、在当前目录 E:\soft\OpenSSL-Win64\bin 里创建两个子目录 m…

UVa12419 Heap Manager

题目链接 UVa12419 - Heap Manager 题意 内存以内存单元为基本单位&#xff0c;每个内存单元用一个固定的整数作为标识&#xff0c;称为地址。地址从0开始连续排列&#xff0c;地址相邻的内存单元被认为是逻辑上连续的。我们把从地址i开始的s个连续的内存单元称为首地址为i长度…

三代半导体材料有何区别

什么是半导体材料 半导体材料是制作半导体器件和集成电路的电子材料&#xff0c;是半导体工业的基础。利用半导体材料制作的各种各样的半导体器件和集成电路&#xff0c;促进了现代信息社会的飞速发展。 绝缘体、半导体和导体的典型电导率范围 半导体材料的研究开始于19世纪初…

行走在深度学习的幻觉中:问题缘由与解决方案

如何解决大模型的「幻觉」问题&#xff1f; 我们在使用深度学习大模型如LLM&#xff08;Large Language Models&#xff09;时&#xff0c;可能会遇到一种被称为“幻觉”的现象。没错&#xff0c;它并不是人脑中的错觉&#xff0c;而是模型对特定模式的过度依赖&#xff0c;这…

ChatGPT学习笔记——大模型基础理论体系

1、ChatGPT的背景与意义 近期,ChatGPT表现出了非常惊艳的语言理解、生成、知识推理能力, 它可以极好的理解用户意图,真正做到多轮沟通,并且回答内容完整、重点清晰、有概括、有条理。 ChatGPT 是继数据库和搜索引擎之后的全新一代的 “知识表示和调用方式”如下表所示。 …

任务调度实现

一、定时任务概述 在项目中开发定时任务应该一种比较常见的需求&#xff0c;在 Java 中开发定时任务主要有三种解决方案&#xff1a;一是使用JDK 自带的 Timer&#xff0c;二是使用 Spring Task&#xff0c;三是使用第三方组件 Quartz Timer 是 JDK 自带的定时任务工具,其简单易…

Nougat:科学文档的OCR 使用记录

https://github.com/facebookresearch/nougat python环境需要在3.8以上 安装&#xff1a;pip install nougat-ocr 模型默认下载地址&#xff1a;/home/****/.cache/torch/hub/nougat-0.1.0-small 环境安装好之后默认使用cpu UserWarning: CUDA initialization: The NVIDIA dr…

数据恢复软件哪个好?10款好用的数据恢复软件推荐(免费和付费)

我们使用的个人电脑、智能手机和其他数字设备包含我们所有的个人和重要数据。我们不能丢失数据&#xff0c;幸运的是&#xff0c;这些设备都不可靠。它们可能随时损坏或损坏&#xff0c;在此过程中丢失我们的重要数据。 不幸的是&#xff0c;没有工具可以 100% 确保您永远不会丢…

2024 年 API 安全:预测和趋势

随着技术以前所未有的速度不断进步&#xff0c;API&#xff08;应用程序编程接口&#xff09;安全性的复杂性也随之增加。随着 API 在现代应用程序和服务中的激增&#xff0c;组织将需要更好地了解其 API 环境以及 API 给运营带来的风险。 到 2024 年&#xff0c;预计几个关键…

olap/spark-tungsten:codegen

15721这一章没什么好说的&#xff0c;不再贴课程内容了。codegen和simd在工业界一般只会选一种实现。比如phothon之前用codegen&#xff0c;然后改成了向量化引擎。一般gen的都是weld IR/LLVM IR/当前语言&#xff0c;gen成C的也要检查是不是有本地预编译版本&#xff0c;要不没…

电子学会C/C++编程等级考试2023年12月(三级)真题解析

C/C++编程(1~8级)全部真题・点这里 第1题:因子问题 任给两个正整数N、M,求一个最小的正整数a,使得a和(M-a)都是N的因子。 时间限制:10000 内存限制:65536 输入 包括两个整数N、M。N不超过1,000,000。 输出 输出一个整数a,表示结果。如果某个案例中满足条件的正整数不存…

2023 CSIG青年科学家会议丨多模态大模型时代下的文档图像处理

近日&#xff0c;由中国图象图形学学会青年工作委员会发起的“第十九届中国图象图形学学会青年科学家会议”在广州召开。 会议面向国际学术前沿与国家战略需求&#xff0c;聚焦最新前沿技术和热点领域&#xff0c;邀请各界专家与青年学者进行总计超200场的高水平学术深度交流&…