eBPF理解
Posted 为了维护世界和平_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了eBPF理解相关的知识,希望对你有一定的参考价值。
目录
eBPF运行时在内核中有五个模块组成
- eBPF辅助函数:用于eBPF程序与内核模块交互的函数
- eBPF验证器:确保eBPF程序的安全
- 11个64位寄存器,一个程序计数器和一个512字节的栈组成的存储模块
- 编译器:将eBPF字节码编译成本地机器指令
- BPF映射(map):提供大块存储,可以被应用空间程序访问。
深入理解BPF指令
以上一期的实验一为例,查询BPF在运行时的指令码
#bpftool prog list
38: cgroup_device tag 03b4eaae2f14641a gpl
loaded_at 2022-08-20T10:29:46+0800 uid 1000
xlated 296B jited 162B memlock 4096B map_ids 1
109: kprobe name hello tag 7e400057bc2e722a gpl
loaded_at 2022-08-20T11:52:24+0800 uid 0
xlated 200B jited 115B memlock 4096B map_ids 8
btf_id 108
109是eBPF程序编号,kprobe是程序的类型,hello是程序的名字
根据编号,导出eBPF程序的指令
root@root:~# sudo bpftool prog dump xlated id 254
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#-64016
; return 0;
11: (b7) r0 = 0
12: (95) exit
- 中括号的十六进制数值表示BPF指令码参考IOVisor BPF 文档
如第0行的0xb7表示为64位寄存器复制
- 括号后面的部分,就是BPF指令的伪代码
- 借助R10寄存器从栈中把字符串“Hello world!”读出来
- 向R2寄存器写入字符串长度14,即Hello world!”的长度
- 调用BPF辅助函数bpf_trace_printk输出字符串
- 向R0寄存器写入0,表示程序的返回值0
总结:先通过 R1 和 R2 寄存器设置了 bpf_trace_printk 的参数,然后调用 bpf_trace_printk 函数输出字符串,最后再通过 R0 寄存器返回。
x86格式的输出如下
# bpftool prog dump jited id 254
int hello_world(void * ctx):
bpf_prog_38dd440716c4900f_hello_world:
; int hello_world(void *ctx)
0: nopl 0x0(%rax,%rax,1)
5: xchg %ax,%ax
7: push %rbp
8: mov %rsp,%rbp
b: sub $0x10,%rsp
12: mov $0x21,%edi
; ( char _fmt[] = "Hello, World!"; bpf_trace_printk_(_fmt, sizeof(_fmt)); );
17: mov %di,-0x4(%rbp)
1b: mov $0x646c726f,%edi
20: mov %edi,-0x8(%rbp)
23: movabs $0x57202c6f6c6c6548,%rdi
2d: mov %rdi,-0x10(%rbp)
31: mov %rbp,%rdi
;
34: add $0xfffffffffffffff0,%rdi
; ( char _fmt[] = "Hello, World!"; bpf_trace_printk_(_fmt, sizeof(_fmt)); );
38: mov $0xe,%esi
3d: call 0xffffffffd8c7e834
; return 0;
42: xor %eax,%eax
44: leave
45: ret
BPF 指令的加载和执行过程
# -ebpf表示只跟踪bpf系统调用
sudo strace -v -f -ebpf ./hello.py
输出如下
bpf(BPF_PROG_LOAD,
prog_type=BPF_PROG_TYPE_SOCKET_FILTER,
insn_cnt=2,
insns=[
code=BPF_JMP|BPF_K|BPF_CALL, dst_reg=BPF_REG_0, src_reg=BPF_REG_0, off=0, imm=0xa0,
code=BPF_JMP|BPF_K|BPF_EXIT, dst_reg=BPF_REG_0, src_reg=BPF_REG_0, off=0, imm=0
],
license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_VERSION(0, 0, 0), prog_flags=0, prog_name="", prog_ifindex=0, expected_attach_type=BPF_CGROUP_INET_INGRESS, prog_btf_fd=0, func_info_rec_size=0, func_info=NULL, func_info_cnt=0, line_info_rec_size=0, line_info=NULL, line_info_cnt=0, attach_btf_id=0, attach_prog_fd=0
,
116) = 3
bpf(BPF_PROG_LOAD,
prog_type=BPF_PROG_TYPE_KPROBE,
insn_cnt=13,
insns=[code=BPF_ALU64|BPF_K|BPF_MOV, dst_reg=BPF_REG_1, src_reg=BPF_REG_0, off=0, imm=0x21,
code=BPF_STX|BPF_H|BPF_MEM, dst_reg=BPF_REG_10, src_reg=BPF_REG_1, off=-4, imm=0,
code=BPF_ALU64|BPF_K|BPF_MOV, dst_reg=BPF_REG_1, src_reg=BPF_REG_0, off=0, imm=0x646c726f,
code=BPF_STX|BPF_W|BPF_MEM, dst_reg=BPF_REG_10, src_reg=BPF_REG_1, off=-8, imm=0,
code=BPF_LD|BPF_DW|BPF_IMM, dst_reg=BPF_REG_1, src_reg=BPF_REG_0, off=0, imm=0x6c6c6548,
code=BPF_LD|BPF_W|BPF_IMM, dst_reg=BPF_REG_0, src_reg=BPF_REG_0, off=0, imm=0x57202c6f,
code=BPF_STX|BPF_DW|BPF_MEM, dst_reg=BPF_REG_10, src_reg=BPF_REG_1, off=-16, imm=0,
code=BPF_ALU64|BPF_X|BPF_MOV, dst_reg=BPF_REG_1, src_reg=BPF_REG_10, off=0, imm=0,
code=BPF_ALU64|BPF_K|BPF_ADD, dst_reg=BPF_REG_1, src_reg=BPF_REG_0, off=0, imm=0xfffffff0,
code=BPF_ALU64|BPF_K|BPF_MOV, dst_reg=BPF_REG_2, src_reg=BPF_REG_0, off=0, imm=0xe,
code=BPF_JMP|BPF_K|BPF_CALL, dst_reg=BPF_REG_0, src_reg=BPF_REG_0, off=0, imm=0x6,
code=BPF_ALU64|BPF_K|BPF_MOV, dst_reg=BPF_REG_0, src_reg=BPF_REG_0, off=0, imm=0,
code=BPF_JMP|BPF_K|BPF_EXIT, dst_reg=BPF_REG_0, src_reg=BPF_REG_0, off=0, imm=0],
license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_VERSION(5, 15, 39), prog_flags=0, prog_name="hello_world", prog_ifindex=0, expected_attach_type=BPF_CGROUP_INET_INGRESS, prog_btf_fd=3, func_info_rec_size=8, func_info=0x559ef2f15440, func_info_cnt=1, line_info_rec_size=16, line_info=0x559ef2f34400, line_info_cnt=5, attach_btf_id=0, attach_prog_fd=0, fd_array=NULL
,
144) = 4
函数bpf原型,上面的bpf输出与原型对应
int bpf(int cmd, union bpf_attr *attr, unsigned int size);
1、参数BPF_PROG_LOAD,表示加载BPF程序
2、bpf_attr表示BPF程序的属性
- prog_type 表示程序类型,如BPF_PROG_TYPE_KPEOBE
- insn_cnt 表示指令条数
- insns 包含具体的每条指令
- prog_name BPF程序的名字
3、size大小
把eBPF程序加载到内核之外,还需要对跟踪的事件进行绑定,kprobe和uprobe都是通过perf_event_open函数来完成的。
跟踪系统调用
- bpf系统调用,加载BPF程序
- 通过sys/bus/event_source/devices/kprobe/type 查询kprobe类型的事件
- 调用perf_event_open创建性能监控事件,6 表示查询到的事件 PERF_TYPE_XX
- 通过ioctl的PERF_EVENT_IOC_SET_BPF命令,将BPF程序绑定到性能事件
参考:
bpf-docs/eBPF.md at master · iovisor/bpf-docs · GitHub
以上是关于eBPF理解的主要内容,如果未能解决你的问题,请参考以下文章