eBPF理解

Posted 为了维护世界和平_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了eBPF理解相关的知识,希望对你有一定的参考价值。

目录

深入理解BPF指令

x86格式的输出如下

BPF 指令的加载和执行过程

跟踪系统调用


eBPF运行时在内核中有五个模块组成

  1. eBPF辅助函数:用于eBPF程序与内核模块交互的函数
  2. eBPF验证器:确保eBPF程序的安全
  3. 11个64位寄存器,一个程序计数器和一个512字节的栈组成的存储模块
  4. 编译器:将eBPF字节码编译成本地机器指令
  5. 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

如第0行的0xb7表示为64位寄存器复制

  • 括号后面的部分,就是BPF指令的伪代码

  1. 借助R10寄存器从栈中把字符串“Hello world!”读出来
  2. 向R2寄存器写入字符串长度14,即Hello world!”的长度
  3. 调用BPF辅助函数bpf_trace_printk输出字符串
  4. 向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

BPF Internals | USENIX

以上是关于eBPF理解的主要内容,如果未能解决你的问题,请参考以下文章

eBPF理解

eBPF理解

eBPF理解

eBPF理解

eBPF理解

基于 eBPF 的 Kubernetes 可观测实践