linux 6.9.7 指令宏
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
/* eBPF instruction mini library */
#ifndef __BPF_INSN_H
#define __BPF_INSN_H
struct bpf_insn;
/* ALU ops on registers, bpf_add|sub|...: dst_reg += src_reg */
// BPF_ALU64_REG:dst_reg += src_reg
// BPF_ALU32_REG:dst_reg += src_reg
#define BPF_ALU64_REG(OP, DST, SRC) \
((struct bpf_insn) { \
.code = BPF_ALU64 | BPF_OP(OP) | BPF_X, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = 0, \
.imm = 0 })
#define BPF_ALU32_REG(OP, DST, SRC) \
((struct bpf_insn) { \
.code = BPF_ALU | BPF_OP(OP) | BPF_X, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = 0, \
.imm = 0 })
/* ALU ops on immediates, bpf_add|sub|...: dst_reg += imm32 */
// BPF_ALU64_IMM:dst_reg += imm32
// BPF_ALU32_IMM:dst_reg += imm32
#define BPF_ALU64_IMM(OP, DST, IMM) \
((struct bpf_insn) { \
.code = BPF_ALU64 | BPF_OP(OP) | BPF_K, \
.dst_reg = DST, \
.src_reg = 0, \
.off = 0, \
.imm = IMM })
#define BPF_ALU32_IMM(OP, DST, IMM) \
((struct bpf_insn) { \
.code = BPF_ALU | BPF_OP(OP) | BPF_K, \
.dst_reg = DST, \
.src_reg = 0, \
.off = 0, \
.imm = IMM })
/* Short form of mov, dst_reg = src_reg */
// BPF_MOV64_REG:dst_reg = src_reg
// BPF_MOV32_REG:dst_reg = src_reg
#define BPF_MOV64_REG(DST, SRC) \
((struct bpf_insn) { \
.code = BPF_ALU64 | BPF_MOV | BPF_X, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = 0, \
.imm = 0 })
#define BPF_MOV32_REG(DST, SRC) \
((struct bpf_insn) { \
.code = BPF_ALU | BPF_MOV | BPF_X, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = 0, \
.imm = 0 })
/* Short form of mov, dst_reg = imm32 */
// BPF_MOV64_IMM:dst_reg = imm32
// BPF_MOV32_IMM:dst_reg = imm32
#define BPF_MOV64_IMM(DST, IMM) \
((struct bpf_insn) { \
.code = BPF_ALU64 | BPF_MOV | BPF_K, \
.dst_reg = DST, \
.src_reg = 0, \
.off = 0, \
.imm = IMM })
#define BPF_MOV32_IMM(DST, IMM) \
((struct bpf_insn) { \
.code = BPF_ALU | BPF_MOV | BPF_K, \
.dst_reg = DST, \
.src_reg = 0, \
.off = 0, \
.imm = IMM })
/* BPF_LD_IMM64 macro encodes single 'load 64-bit immediate' insn */
// BPF_LD_IMM64 和 BPF_LD_IMM64_RAW 都是用于将立即数加载到寄存器中的指令
// 差异:
// BPF_LD_IMM64:
// 这是一个加载立即数的指令,用于将一个 64 位的立即数直接加载到指定的寄存器中
// 这种类型的加载通常用于设置寄存器的初始值/将特定的常量值传递给后续的计算
// BPF_LD_IMM64_RAW:
// 这个指令也是用于加载立即数,但它是“原始”形式,意味着它可能允许加载的立即数包含一些特殊值或模式
// 这些在常规的 BPF_LD_IMM64 指令中可能不被允许
// BPF_LD_IMM64_RAW 通常用于加载那些不能直接用常量表达的值,或者需要绕过某些编译器优化的情况
//
// 在实际使用中,BPF_LD_IMM64 是更常见的指令,因为它适用于大多数需要加载立即数的场景
// BPF_LD_IMM64_RAW 可能在特定的上下文或特殊情况下使用,例如,当需要加载的立即数受到某些限制或需要特定的处理时
// 注意
// eBPF 指令集和可用的指令可能会根据不同的内核版本和 eBPF 特性而有所不同
// 因此,在使用这些指令时,应参考当前环境的文档和内核版本
#define BPF_LD_IMM64(DST, IMM) \
BPF_LD_IMM64_RAW(DST, 0, IMM)
#define BPF_LD_IMM64_RAW(DST, SRC, IMM) \
((struct bpf_insn) { \
.code = BPF_LD | BPF_DW | BPF_IMM, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = 0, \
.imm = (__u32) (IMM) }), \
((struct bpf_insn) { \
.code = 0, /* zero is reserved opcode */ \
.dst_reg = 0, \
.src_reg = 0, \
.off = 0, \
.imm = ((__u64) (IMM)) >> 32 })
#ifndef BPF_PSEUDO_MAP_FD
# define BPF_PSEUDO_MAP_FD 1
#endif
/* pseudo BPF_LD_IMM64 insn used to refer to process-local map_fd */
// MAP_FD 是映射的文件描述符,这是一个整数值,表示了要加载的映射
// 使用 BPF_LD_IMM64_RAW,将立即数加载到指定的寄存器 DST
// BPF_PSEUDO_MAP_FD:
// 这是一个伪指令,用于指示内核这是一个特殊的立即数加载,它不是直接加载一个立即数,而是加载一个映射的文件描述符
// 在 eBPF 程序中简化加载映射文件描述符的过程
// 在 eBPF 程序中,可能需要引用一个已经创建的映射
// 通过使用这个宏,可以轻松地将映射的文件描述符加载到一个寄存器中,然后使用该寄存器与其他 eBPF 指令一起操作映射
//
// 例如,在 eBPF 程序中,如果想加载映射文件描述符到寄存器 BPF_REG_1,可以这样使用这个宏:
// int map_fd = /* 假设这是从某个地方获取的映射文件描述符 */;
// BPF_LD_MAP_FD(BPF_REG_1, map_fd);
// 这行代码将映射的文件描述符加载到 BPF_REG_1 寄存器中
// 之后可以使用这个寄存器来进行映射查找或其他操作
#define BPF_LD_MAP_FD(DST, MAP_FD) \
BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD)
/* Direct packet access, R0 = *(uint *) (skb->data + imm32) */
// 直接包访问
// BPF_LD_ABS:
// R0 = *(uint *) (skb->data + imm32)
//
// eBPF 指令,加载一个立即数到 eBPF 寄存器
// SIZE 表示加载数据的大小,可以是 BPF_W(表示 32 位)或 BPF_DW(表示 64 位)
// 可能的值有 BPF_B(8位字节,byte)、BPF_H(16位半字,half-word)、BPF_W(32位字,word)、BPF_DW(64位双字,double-word)
//
// IMM 是一个参数,表示要加载的立即数
//
// bpf_insn 是 eBPF 程序中表示单个指令的结构体
// 设置指令属性:
// .code = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS
// .code 是设置指令的操作码
// BPF_LD 表示这是一个加载操作
// BPF_SIZE(SIZE) 根据 SIZE 参数设置加载数据的大小
// BPF_ABS 表示使用绝对偏移量加载
//
// 设置目标寄存器:
// .dst_reg = 0,
// .dst_reg 是目标寄存器,这里设置为 0,表示使用寄存器 BPF_REG_0(在 eBPF 中,BPF_REG_0 通常用于存储加载的数据)
// 设置源寄存器:
// .src_reg = 0,
// .src_reg 是源寄存器,这里设置为 0,对于 BPF_LD 指令,源寄存器通常用于间接加载,但在这个宏中未使用。
// 设置偏移量:
// .off = 0,
// .off 是指令的偏移量,这里设置为 0,表示从输入数据的开始位置加载数据
// 设置立即数:
// .imm = IMM
// .imm 是指令的立即数字段,这里使用参数 IMM 填充
//
// 简化创建一个加载立即数的 eBPF 指令的过程
// 从输入数据的固定偏移量加载数据到寄存器,使用该宏可以方便构造指令
//
// 【示例】
// 加载位于输入数据偏移量 42 处的 32 位数据到寄存器 BPF_REG_0:
// BPF_LD_ABS(BPF_W, 42);
// 将创建一个 eBPF 指令,从输入数据的偏移量 42 处加载一个 32 位的数据到 BPF_REG_0 寄存器
//
// 在 BPF_PROG_TYPE_SOCKET_FILTER 类型的 eBPF 程序中,这种类型的加载操作通常用于访问网络数据包的特定部分
// 比如从数据包缓冲区中,加载数据到寄存器
// BPF_PROG_TYPE_SOCKET_FILTER 是 eBPF 程序的一种类型,用于在套接字上运行,可以过滤/修改经过套接字的网络数据包
// 这种类型的程序通常用于实现自定义的网络策略、监控或数据包处理逻辑
//
// 在 BPF_PROG_TYPE_SOCKET_FILTER 中使用 BPF_LD_ABS 指令的一般步骤如下:
// (1) 确定要访问的数据包部分:
// 首先,需要知道想要访问的数据包头部/字段的偏移量。
// 例如,如果访问 IP 头部的协议字段,需要知道该字段在数据包中的确切位置。
// (2) 编写 BPF_LD_ABS 指令:
// 使用 BPF_LD_ABS 指令将所需数据加载到寄存器。
// 需要指定加载的大小(如 BPF_W 表示 32 位,BPF_B 表示 8 位等),以及数据在数据包中的偏移量
// (3) 处理加载的数据:
// 加载数据后,使用其他 eBPF 指令来处理这些数据,例如根据协议类型进行条件分支或计数。
//
// 【示例】
// 如何在 BPF_PROG_TYPE_SOCKET_FILTER 程序中使用 BPF_LD_ABS 来加载 IP 头部的协议字段:
//
// struct bpf_insn prog[] = {
// // 加载 IP 头部的协议字段(偏移量 ETH_HLEN + offsetof(struct iphdr, protocol))
// BPF_LD_ABS(BPF_W, ETH_HLEN + offsetof(struct iphdr, protocol)),
// // 假设只关心 TCP 流量
// BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, IPPROTO_TCP, 0),
// // 如果协议是 TCP,允许数据包通过
// BPF_RET(BPF_K, 0),
// // 如果不是 TCP,丢弃数据包
// BPF_RET(BPF_K, -1),
// };
// 首先,使用 BPF_LD_ABS 指令加载 IP 头部的协议字段到 BPF_REG_0
// 然后,使用 BPF_JMP_IMM 指令检查协议字段是否等于 IPPROTO_TCP
// 如果是,程序返回 0 允许数据包通过;如果不是,程序返回 -1 丢弃数据包
#define BPF_LD_ABS(SIZE, IMM) \
((struct bpf_insn) { \
.code = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS, \
.dst_reg = 0, \
.src_reg = 0, \
.off = 0, \
.imm = IMM })
/* Memory load, dst_reg = *(uint *) (src_reg + off16) */
// 把 内存MEM SRC+OFF 中的 SIZE 大小的数据加载到寄存器 DST
// dst_reg = *(uint *) (src_reg + off16)
//
// BPF_LDX 表示这是一个加载指令,并且加载目标是寄存器
// BPF_SIZE(SIZE) 表示加载数据的大小(字节、半字、字或双字)
// BPF_MEM 表示数据来源是内存
// dst_reg 目标寄存器,宏参数 DST 指定
// src_reg 源寄存器,宏参数 SRC 指定,包含内存基址
// off 内存地址偏移量,宏参数 OFF 指定
// imm 立即数部分,在这种类型的指令中这个字段不使用,所以设置为 0
//
// 如果要从内存地址加载一个字节到寄存器 r1,可以这么写
// struct bpf_insn instr = BPF_LDX_MEM(BPF_B, BPF_REG_1, BPF_REG_0, 10)
// 在内存地址 r0 + 10 处加载 1B 的数据量,然后将该字节存储到寄存器 r1 中
#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) \
((struct bpf_insn) { \
.code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = OFF, \
.imm = 0 })
/* Memory store, *(uint *) (dst_reg + off16) = src_reg */
// *(uint *) (dst_reg + off16) = src_reg
// BPF_STX 表示这是一个存储指令,并且存储源是寄存器
// 将存储源是 SRC 寄存器的 SIZE 大小的内容,存储到内存 DST+OFF 处
//
// BPF_SIZE(SIZE) 表示存储数据的大小(字节、半字、字或双字)
// BPF_MEM 表示数据存储于内存
// dst_reg 目的寄存器,宏参数 DST 指定,它包含内存基址
// src_reg 源寄存器, 宏参数 SRC 指定,它包含要存储的值
// off 内存地址偏移量,宏参数 OFF 指定
//
// 如果要将寄存器 r2 中的一个字节存储到内存地址(r1 + 10)
// struct bpf_insn instr = BPF_STX_MEM(BPF_B, BPF_REG_1, BPF_REG_2, 10);
// 上述指令的含义是将寄存器 r2 中的一个字节值存储到内存地址 r1 + 10 处
#define BPF_STX_MEM(SIZE, DST, SRC, OFF) \
((struct bpf_insn) { \
.code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = OFF, \
.imm = 0 })
/*
* Atomic operations:
*
* BPF_ADD *(uint *) (dst_reg + off16) += src_reg
* BPF_AND *(uint *) (dst_reg + off16) &= src_reg
* BPF_OR *(uint *) (dst_reg + off16) |= src_reg
* BPF_XOR *(uint *) (dst_reg + off16) ^= src_reg
* BPF_ADD | BPF_FETCH src_reg = atomic_fetch_add(dst_reg + off16, src_reg);
* BPF_AND | BPF_FETCH src_reg = atomic_fetch_and(dst_reg + off16, src_reg);
* BPF_OR | BPF_FETCH src_reg = atomic_fetch_or(dst_reg + off16, src_reg);
* BPF_XOR | BPF_FETCH src_reg = atomic_fetch_xor(dst_reg + off16, src_reg);
* BPF_XCHG src_reg = atomic_xchg(dst_reg + off16, src_reg)
* BPF_CMPXCHG r0 = atomic_cmpxchg(dst_reg + off16, r0, src_reg)
*/
// atomic_fetch_add 是一个用于原子操作的函数,主要用于在多线程编程中对共享变量进行无锁的加法操作,并且返回变量更新前的值
// 它用于避免在多线程环境中更新变量时可能出现的数据竞争问题
// BPF_ATOMIC_OP(BPF_DW, BPF_ADD, BPF_REG_0, BPF_REG_1, 0)
// *(uint *) (BPF_REG_0 + 0) += BPF_REG_1
#define BPF_ATOMIC_OP(SIZE, OP, DST, SRC, OFF) \
((struct bpf_insn) { \
.code = BPF_STX | BPF_SIZE(SIZE) | BPF_ATOMIC, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = OFF, \
.imm = OP })
/* Legacy alias */
#define BPF_STX_XADD(SIZE, DST, SRC, OFF) BPF_ATOMIC_OP(SIZE, BPF_ADD, DST, SRC, OFF)
/* Memory store, *(uint *) (dst_reg + off16) = imm32 */
// 将 SIZE 大小的立即数 IMM ,保存到 DST + OFF 上
#define BPF_ST_MEM(SIZE, DST, OFF, IMM) \
((struct bpf_insn) { \
.code = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, \
.dst_reg = DST, \
.src_reg = 0, \
.off = OFF, \
.imm = IMM })
/* Conditional jumps against registers, if (dst_reg 'op' src_reg) goto pc + off16 */
// 条件跳转,如果 (dst_reg 'op' src_reg),则跳转到 pc + off16
#define BPF_JMP_REG(OP, DST, SRC, OFF) \
((struct bpf_insn) { \
.code = BPF_JMP | BPF_OP(OP) | BPF_X, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = OFF, \
.imm = 0 })
/* Like BPF_JMP_REG, but with 32-bit wide operands for comparison. */
#define BPF_JMP32_REG(OP, DST, SRC, OFF) \
((struct bpf_insn) { \
.code = BPF_JMP32 | BPF_OP(OP) | BPF_X, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = OFF, \
.imm = 0 })
/* Conditional jumps against immediates, if (dst_reg 'op' imm32) goto pc + off16 */
// 条件跳转,如果 (dst_reg 'op' imm32),则跳转到 pc + off16
#define BPF_JMP_IMM(OP, DST, IMM, OFF) \
((struct bpf_insn) { \
.code = BPF_JMP | BPF_OP(OP) | BPF_K, \
.dst_reg = DST, \
.src_reg = 0, \
.off = OFF, \
.imm = IMM })
/* Like BPF_JMP_IMM, but with 32-bit wide operands for comparison. */
#define BPF_JMP32_IMM(OP, DST, IMM, OFF) \
((struct bpf_insn) { \
.code = BPF_JMP32 | BPF_OP(OP) | BPF_K, \
.dst_reg = DST, \
.src_reg = 0, \
.off = OFF, \
.imm = IMM })
/* Raw code statement block */
#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM) \
((struct bpf_insn) { \
.code = CODE, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = OFF, \
.imm = IMM })
/* Program exit */
#define BPF_EXIT_INSN() \
((struct bpf_insn) { \
.code = BPF_JMP | BPF_EXIT, \
.dst_reg = 0, \
.src_reg = 0, \
.off = 0, \
.imm = 0 })
#endif
// BPF_W 和 BPF_B 是在 eBPF 编程中使用的宏,它们定义了字的大小
// 在 eBPF 指令集中,字的大小对于某些加载和存储操作是重要的
//
// BPF_W:
// BPF_W 代表 "word",通常用于表示一个 32 位的字
// 在 eBPF 指令集中,这个宏用于指定操作应该以 32 位宽的字进行
// 例如,在使用 BPF_LD 系列指令加载数据时,如果指定 BPF_W,那么加载的数据将是 32 位宽
// BPF_B:
// BPF_B 代表 "byte",用于表示一个 8 位的字节
// 在 eBPF 指令集中,这个宏用于指定操作应该以 8 位宽的字节进行
// 例如,在使用 BPF_LD 系列指令加载数据时,如果指定 BPF_B,那么加载的数据将是 8 位宽
//
// 在 eBPF 程序中,这些宏通常与指令的操作码结合使用,以指定操作的数据宽度
// 例如,BPF_LDW 指令可以与 BPF_W 结合使用来加载一个 32 位的字,或者与 BPF_B 结合使用来加载一个 8 位的字节
//
// struct bpf_insn load_32_bit_word = {
// .code = BPF_LD | BPF_W | BPF_ABS, // 加载一个 32 位的字
// .dst_reg = BPF_REG_0, // 目标寄存器
// .src_reg = 0, // 源寄存器(对于 BPF_ABS,这通常是 0)
// .off = 0, // 偏移量
// .imm = offsetof(struct some_struct, some_field) // 立即数,表示偏移量
// };
//
// struct bpf_insn load_8_bit_byte = {
// .code = BPF_LD | BPF_B | BPF_ABS, // 加载一个 8 位的字节
// .dst_reg = BPF_REG_0,
// .src_reg = 0,
// .off = 0,
// .imm = offsetof(struct some_struct, some_byte_field)
// };
//
// load_32_bit_word 指令加载一个 32 位的字
// load_8_bit_byte 指令加载一个 8 位的字节
// 这些操作的数据宽度由 BPF_W 和 BPF_B 宏指定
eBPF指令集规范v1.0
本文档是eBPF指令集规范,版本 1.0
寄存器和使用规范
eBPF有10个通用寄存器、一个只读的栈帧寄存器,都是64-bit宽度
eBPF的寄存器使用规范为
- R0:保存函数返回值和 eBPF 程序退出值
- R1 - R5:用于函数调用参数
- R6 - R9:callee函数负责进入时保存这几个寄存器到栈中,函数退出前再恢复寄存器原有值
(callee saved registers that function calls will preserve) - R10: 只读的栈帧寄存器,用于访问栈
R0 - R5是临时寄存器,eBPF程序如果希望在函数调用后寄存器值不变,需要自己保存和恢复寄存器
(R0 - R5 are scratch registers and eBPF programs needs to spill/fill them if necessary across calls.)
Scratch register / temporary register:保存临时值或者中间值
Caller 和 Callee:A 函数中调用 B 函数,A 是 Caller,B 是 Callee
Caller saved registers 和 Callee saved registers
在函数调用时,某些寄存器的值需要被保存,这样函数调用做出的任何修改不会破坏调用函数已经存储在这些寄存器里的值
寄存器的保存分为两种主要的策略:Caller-saved(调用者保存)和 Callee-saved(被调用者保存)
Caller-saved Registers (volatile registers or call-clobbered registers)
这些寄存器在函数调用时,调用者(caller)需要自己负责保存它们的值。
如果需要在函数调用后继续使用这些寄存器中的值,调用者在调用函数之前,需要将它们的值保存到堆栈或其他内存区域中
并在函数调用之后恢复它们
特点:
调用者负责保存和恢复
高效性:许多情况下,这些寄存器可以直接用于临时存储,不需要额外的保存和恢复操作
Callee-saved Registers (non-volatile registers or call-preserved registers)
这些寄存器在函数调用时,由被调用者(callee)负责保存它们的值
在函数执行之前,被调用者需要将这些寄存器的当前值保存在栈上,并在函数返回之前恢复它们的值
特点:
被调用者负责保存和恢复。
持久性:这些寄存器的值在函数调用之间保持不变
示例
x86-64 ABI(应用二进制接口)
Caller-saved Registers
RAX (Accumulator): 返回值寄存器
RCX, RDX (Counter, Data): 函数参数寄存器
R8, R9, R10, R11: 函数参数寄存器和临时寄存器
这些寄存器的内容在函数调用时可能被覆盖,因此调用者在调用函数之前需要保存它们的值以便在函数返回后继续使用
Callee-saved Registers
RBX: 通用寄存器
RBP (Base Pointer): 栈基指针
R12, R13, R14, R15: 通用寄存器
RSP (Stack Pointer): 栈指针
这些寄存器的值在函数调用前需要被保存,并在函数返回前恢复,以确保调用者在函数调用前后的寄存器状态一致
void callee_function() {
// some operations
}
void caller_function() {
int i = 5;
// potentially need to save caller-saved registers here
callee_function(); // May use caller-saved registers
// potentially need to restore caller-saved registers here
}
编译器在处理 caller_function 时,根据使用的寄存器,可能会插入代码来保存和恢复 caller-saved 寄存器的值
总结
Caller-saved Registers:调用者保存,volatile,函数调用可能覆盖其值,需要调用者在调用前保存并在调用后恢复
Callee-saved Registers:被调用者保存,non-volatile,函数调用需要保存现有值,并在返回前恢复,以确保调用者的环境不受干扰
指令编码
eBPF 有两种编码模式:
基础编码,64bit 固定长度编码
宽指令编码,在基础编码后增加了一个 64bit 的立即数 (imm64) ,这样指令为 128bit
基础编码格式,每一列约定为 field
绝大多数指令不会使用所有的field,不使用的field被置0
宽指令编码目前只有 64-bit 立即数指令使用
指令类型(class)
基础编码中的 field 的 opcode,一共 8 bit,其中最低位 3bit 用来保存指令类型:
算术和跳转指令
(BPF_ALU、BPF_ALU64、BPF_JMP、BPF_JMP32) 称为算术和跳转指令
8bit 的 opcode 被分为 3 个部分:
第4个bit(source)含义:
4个 bit 的 operation code 用来存储具体指令操作码
算术指令(BPF_ALU, BPF_ALU64)
BPF_ALU 操作数为 32bit,BPF_ALU64 操作数为 64bit
4个 bit 的 operation code 编码如下操作
译者注:
上表中 dst 一定是指目的寄存器,不支持内存地址
上表中 src 可能是源寄存器,也可能是 imm32,根据 source 位 (BPF_K/BPF_X) 来区分
eBPF 寄存器都是 64bit,根据操作数类型,可以使用全部 64bit,也可以只使用其中 32bit
BPF_ADD | BPF_X | BPF_ALU :
dst_reg = (u32) dst_reg + (u32) src_reg;
BPF_XOR | BPF_K | BPF_ALU64 :
dst_reg = dst_reg + src_reg
BPF_XOR | BPF_K | BPF_ALU :
dst_reg = (u32) dst_reg ^ (u32) imm32
BPF_XOR | BPF_K | BPF_ALU64 :
dst_reg = dst_reg ^ imm32
字节swap指令
字节 swap 指令,属于 BPF_ALU 分类,操作码为 BPF_END
字节 swap 指令操作数只有 dst_reg,不操作 src_reg 和 imm32
基础编码格式中的 imm32,此时编码了 swap 操作的位宽。支持:16、32、64bit
BPF_ALU | BPF_TO_LE | BPF_END,并且imm32 = 16:
dst_reg = htole16(dst_reg)
BPF_ALU | BPF_TO_LE | BPF_END,并且imm32 = 64:
dst_reg = htole64(dst_reg)
跳转指令(BPF_JMP32, BPF_JMP)
操作数为寄存器,BPF_JMP32 使用 32bit,BPF_JMP 使用 64bit,其它行为都一样
eBPF 程序在调用 BPF_EXIT 前,需要把返回值保存在 R0 寄存器中
PC:程序计数器
off:基础编码格式中的 off16
src、dst:都是指的寄存器的值
Load 和 Store指令
BPF_LD、BPF_LDX、BPF_ST、BPF_STX指令类型中,8bit 的 opcode 含义为:
标准 load 和 store 指令
BPF_MEM 代表了标准 load 和 store 指令,这些指令用于寄存器和内存之间传递数据。
BPF_MEM | <size> | BPF_STX:
*(size *) (dst_reg + off16) = src_reg
BPF_MEM | <size> | BPF_ST:
*(size *) (dst_reg + off16) = imm32
BPF_MEM | <size> | BPF_LDX:
dst_reg = *(size *) (src_reg + off16)
size 可选值:BPF_B, BPF_H, BPF_W, or BPF_DW
原子操作
原子操作,指的的是对内存的操作,不会被其他 eBPF 程序中途扰乱
eBPF 所有的原子操作由 BPF_ATOMIC 指定,例如:
BPF_ATOMIC | BPF_W | BPF_STX :32-bit原子操作
BPF_ATOMIC | BPF_DW | BPF_STX :64-bit原子操作
8-bit 和 16-bit 原子操作不支持
基本编码格式的 imm32 用来编码真正的原子操作, 以下是简单原子指令:
BPF_ATOMIC | BPF_W | BPF_STX, imm32 = BPF_ADD
*(u32 *)(dst_reg + off16) += src_reg
BPF_ATOMIC | BPF_DW | BPF_STX, imm32 = BPF_ADD
*(u64 *)(dst_reg + off16) += src_reg
除了以上比较简单的原子操作,还有 2 个复杂原子指令:
对于简单原子指令是可选的,如果设置了,src_reg 将保存 (dst_reg + off16) 指向的内存中被修改前的原始值
对于复杂原子指令是必选的
64bit立即数指令
mode 为 BPF_IMM 的指令,用于 eBPF 宽指令,有额外的一个 imm64 值
含义为:dst_reg = imm64
经典BPF数据包访问指令
eBPF 之前为了兼容经典 BPF,引入了一些访问数据包的指令,现在已经废弃不再使用
学习链接
https://heapdump.cn/article/5420563