eBPF 指令宏

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jmeter使用JSON Extractor提取多个变量

1.当正则不好使时&#xff0c;用json extractor 2.提取多个值时&#xff0c;默认值必填&#xff0c;否则读不到变量

代码随想录算法训练营第4天|LeetCode24,19,02,07,142

24.交换链表结点 题目链接&#xff1a;24. 两两交换链表中的节点 - 力扣&#xff08;LeetCode&#xff09; 文章链接&#xff1a;代码随想录 (programmercarl.com) 视频链接&#xff1a;代码随想录算法公开课 | 最强算法公开课 | 代码随想录 第一想法 正常模拟&#xff0c;先画…

47.HOOK引擎优化支持CALL与JMP位置做HOOK

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 上一个内容&#xff1a;46.修复HOOK对代码造成的破坏 以 46.修复HOOK对代码造成的破坏 它的代码为基础进行修改 优化的是让引擎支持从短跳JMP&#xff08;E9&…

JUC(java.util.concurrent)中的常见类

文章目录 Callable接口ReentrantLockReentrantLock 和 synchronized 的区别:如何选择使用哪个锁? 信号量SemaphoreCountDownLatch多线程环境使用ArrayList多线程使用 哈希表相关面试题 JUC放了和多线程有关的组件 Callable接口 和Runnable一样是描述一个任务,但是有返回值,表…

leetcode-每日一题

3101. 交替子数组计数https://leetcode.cn/problems/count-alternating-subarrays/ 给你一个 二进制数组 nums 。 如果一个 子数组 中 不存在 两个 相邻 元素的值 相同 的情况&#xff0c;我们称这样的子数组为 交替子数组 。 返回数组 nums 中交替子数组的数量。 示例 …

Linux字符设备驱动

一、字符设备驱动结构 1. cdev结构体 在Linux内核中&#xff0c;使用cdev结构体来描述一个字符设备 struct cdev {struct kobject kobj; //内嵌kobject对象struct module *owner; //所属的模块const struct file_operations *ops; //该设备的文件操作结构体struct list_head…

确认下单:购物车页面点击 去结算 按钮发起两个请求trade(显示购物车的商品信息和计算商品的总金额)findUserAddressList

文章目录 1、确认下单&#xff1a;购物车页面点击去结算1.1、在OrderController类中创建 trade 方法1.2、在CartController类中创建 checkedCartInfos1.3、CartServiceImpl 实现 checkedCartInfos的业务功能1.4、在service-cart-client模块下定义远程openFeign接口1.5、在SpzxO…

Java - 程序员面试笔记记录 实现 - Part3

4.1 线程与进程 线程是程序执行的最小单元&#xff0c;一个进程可以拥有多个线程&#xff0c;各个线程之间共享程序的内存空间以及一些进程级资源&#xff0c;但拥有自己的栈空间。 4.3 Java 多线程 方法一&#xff1a;继承 Thread 类&#xff0c;重写 run 方法&#xff1b;…

qt QGridLayout 简单实验1

1.概要 2.实验 2.1 实验1 简单实验跨行 2.1.1 代码 #ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);~W…

Golang语法规范和风格指南(一)——简单指南

1. 前引 一个语言的规范的学习是重要的&#xff0c;直接关系到你的代码是否易于维护和理解&#xff0c;同时学习好对应的语言规范可以在前期学习阶段有效规避该语言语法和未知编程风格的冲突。 这里是 Google 提供的规范&#xff0c;有助于大家在开始学习阶段对 Golang 进行一…

如何魔改vnstat-docker项目使其支持每1分钟采样?

文章目录 一、概述二、官网参考1. 官网地址2. 查看打包过程3.打包命令 三、修改过的文件四、部署运行1. 编排文件2. 运行效果 一、概述 接前文 网络流量监控神器vnStat初探 我们已经了解了vnStat的作用、使用和docker部署。 同时也了解到官方版本支持的采样统计间隔最小为5分…

【Unity】unity学习扫盲知识点

1、建议检查下SystemInfo的引用。这个是什么 Unity的SystemInfo类提供了一种获取关于当前硬件和操作系统的信息的方法。这包括设备类型&#xff0c;操作系统&#xff0c;处理器&#xff0c;内存&#xff0c;显卡&#xff0c;支持的Unity特性等。使用SystemInfo类非常简单。它的…

Linux操作系统的引导过程

系统初始化进程与文件、systemd概述、单元类型、切换运行级别、查看系统默认默认运行、永久切换、常见的系统服务&#xff08;centos&#xff09;-CSDN博客 centos 7系统升级内核&#xff08;ELRepo仓库&#xff09;、小版本升级、自编译内核-CSDN博客 ss命令详细使用讲解文…

tongweb+ths6011测试websocket(by lqw)

本次使用的tongweb版本7049m4&#xff0c;测试包ws_example.war&#xff08;在tongweb安装目录的samples/websocket下&#xff09;&#xff0c;ths版本6011 首先在tongweb控制台部署一下ws_example.war,部署后测试是否能访问&#xff1a; 然後ths上的httpserver.conf的參考配…

游戏服务器搭建选VPS还是专用服务器?

游戏服务器搭建选VPS&#xff0c;VPS能够提供控制、性能和稳定性。它不仅仅是让游戏保持活力。它有助于减少延迟问题&#xff0c;增强您的游戏体验。 想象一下&#xff1a;你正沉浸在一场游戏中。 胜利在望。突然&#xff0c;屏幕卡住——服务器延迟。 很崩溃&#xff0c;对…

PageCache页缓存

一.PageCache基本结构 1.PageCache任务 PageCache负责使用系统调用向系统申请页的内存,给CentralCache分配大块儿的内存,以及合并前后页空闲的内存,整体也是一个单例,需要加锁. PageCache桶的下标按照页号进行映射,每个桶里span的页数即为下标大小. 2.基本结构 当每个线程的…

文件、文本阅读与重定向、路径与理解指令——linux指令学习(一)

前言&#xff1a;本节内容标题虽然为指令&#xff0c;但是并不只是讲指令&#xff0c; 更多的是和指令相关的一些原理性的东西。 如果友友只想要查一查某个指令的用法&#xff0c; 很抱歉&#xff0c; 本节不是那种带有字典性质的文章。但是如果友友是想要来学习的&#xff0c;…

Python 空间和时间高效的二项式系数(Space and time efficient Binomial Coefficient)

这里函数采用两个参数n和k&#xff0c;并返回二项式系数 C(n, k) 的值。 例子&#xff1a; 输入&#xff1a; n 4 和 k 2 输出&#xff1a; 6 解释&#xff1a; 4 C 2 等于 4!/(2!*2!) 6 输入&#xff1a; n 5 和 k 2 输出&#xff1a; 10 解释&#xff1a; 5 C …

moonlight+sunshine+ParsecVDisplay ipad8-windows 局域网串流

1.sunshine PC 安装 2.设置任意账户密码登录 3.setting 里 network启用UPNP IPV4IPV6 save apply 4.ParsecVDisplay虚拟显示器安装 5.ipad appstore download moonlight 6.以ipad 8 为例 2160*1620屏幕分辨率 7.ParsecVDisplay里面 custom设置2160*1620 240hz&#xff0c;…

python conda查看源,修改源

查看源 conda config --show-sources 修改源 可以直接vim .condarc修改源&#xff0c;