eBPF 指令宏

news2024/10/5 18:34:38

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.coloradmin.cn/o/1900716.html

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

相关文章

轻松创建对象——简单工厂模式(Java实现)

1. 引言 大家好&#xff0c;又见面了&#xff01;在上一篇文章中&#xff0c;我们通过Python示例介绍了简单工厂模式&#xff0c;今天&#xff0c;我们继续深入这个话题&#xff0c;用Java来实现简单工厂模式。 2. 什么是简单工厂模式 简单工厂模式&#xff08;Simple Facto…

CorelDRAW2024设计师的神器,一试就爱上!

&#x1f3a8; CorelDRAW 2024&#xff1a;设计界的瑞士军刀&#xff0c;让创意不再受限&#xff01;&#x1f31f; 嗨&#xff0c;各位朋友们&#xff01;&#x1f44b;&#x1f3fb; 今天我要跟大家分享一个神奇的设计神器——CorelDRAW 2024。作为设计师的你&#xff0c;是否…

谷粒商城----通过缓存和分布式锁获取数据。

高并发下缓存失效的问题 高并发下缓存失效的问题--缓存穿透 指查询一个一定不存在的数据&#xff0c;由于缓存是不命中&#xff0c;将去查询数据库&#xff0c;但是数据库也无此记录&#xff0c;我们没有将这次查询的不写入缓存&#xff0c;这将导致这个不存在的数据每次请求…

【论文阅读】-- Interactive Horizon Graphs:改进多个时间序列的紧凑可视化

Interactive Horizon Graphs: Improving the Compact Visualization of Multiple Time Series 摘要1 引言2 相关工作2.1 多个时间序列的可视化2.2 缩减折线图 &#xff08;RLC&#xff09;2.3 地平线图 &#xff08;HG&#xff09;2.4 大尺度和小尺度变异数据集2.5 多个时间序列…

IPSS模块怎么安装到VOS服务器的,到底有没有效果,是不是能大幅度提升VOS3000安全性呢

由于VOS的普及性&#xff0c;不得不承认VOS确实是非常优秀的软交换&#xff0c;但是很多客户在使用过程中都会遇到各种安全问题&#xff0c;比如话费被盗用了&#xff0c;历史话单一堆的非法呼叫话单&#xff0c;严重的影响到了话务安全&#xff0c;并不是那点话费的事了&#…

浏览器怎么抓包?Wireshark详细教程奉上!

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

校园气象站:科学教育与环境感知

在现代化的校园里&#xff0c;一座座高耸的教学楼、郁郁葱葱的绿植、充满活力的学生群体共同构成了一幅生机勃勃的画卷。然而&#xff0c;在这幅画卷中&#xff0c;有一个不可或缺的元素——校园气象站&#xff0c;它不仅是学生们学习气象知识的窗口&#xff0c;更是连接科学与…

【技术支持】vscode代码格式化空格数量问题

问题 使用AltShiftF代码格式化时&#xff0c;发现有些文件格式化后缩进为2格个空格&#xff0c;有些文件正常4个空格 刨析 发现vue创建的文件使用的是两个空格&#xff0c;而且换行符表示方式也不一样 LF 是 Unix 和 Unix-like 系统&#xff08;如 Linux 和 macOS&#xff0…

边缘概率密度、条件概率密度、边缘分布函数、联合分布函数关系

目录 二维随机变量及其分布离散型随机变量连续型随机变量边缘分布边缘概率密度举例边缘概率密度 条件概率密度边缘概率密度与条件概率密度的区别边缘概率密度条件概率密度举个具体例子 参考资料 二维随机变量及其分布 离散型随机变量 把所有的概率&#xff0c;都理解成不同质量…

最新CorelDRAW2024设计师的必备神器!

Hey&#xff0c;各位创意小能手和设计爱好者们&#xff0c;今天要跟大家安利一个超级给力的设计软件——CorelDRAW 2024&#xff01;如果你还在用那些老旧的设计工具&#xff0c;那你就OUT啦&#xff01;&#x1f389;&#x1f3a8; CorelDRAW全系列汉化版下载网盘分享链接&am…

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第55课-芝麻开门(语音 识别 控制3D纪念馆开门 和 关门)

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第55课-芝麻开门&#xff08;语音识别控制3D纪念馆开门和关门&#xff09; 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtn…

Vue 3 中集成 ECharts(附一些案例)

Vue 3 中集成 ECharts 的完全指南 引言 在现代Web开发中&#xff0c;Vue 3以其卓越的性能和灵活的Composition API赢得了广泛的关注。而ECharts&#xff0c;作为开源的一个使用JavaScript实现的强大可视化库&#xff0c;以其丰富的图表类型和高度可定制性成为了数据可视化的首…

基于Qwen2/Lllama3等大模型,部署团队私有化RAG知识库系统的详细教程(Docker+AnythingLLM)

自 ChatGPT 发布以来&#xff0c;大型语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff0c;大模型&#xff09;得到了飞速发展&#xff0c;它在处理复杂任务、增强自然语言理解和生成类人文本等方面的能力让人惊叹&#xff0c;几乎各行各业均可从中获益。 然…

Vatee万腾平台:智慧生活的无限可能

在科技日新月异的今天&#xff0c;我们的生活正被各种智能技术悄然改变。从智能家居到智慧城市&#xff0c;从个人健康管理到企业数字化转型&#xff0c;科技的力量正以前所未有的速度渗透到我们生活的每一个角落。而在这场智能革命的浪潮中&#xff0c;Vatee万腾平台以其卓越的…

ctfshow-web入门-文件包含(web87)巧用 php://filter 流绕过死亡函数的三种方法

目录 方法1&#xff1a;php://filter 流的 base64-decode 方法 方法2&#xff1a;通过 rot13 编码实现绕过 方法3&#xff1a;通过 strip_tags 函数去除 XML 标签 除了替换&#xff0c;新增 file_put_contents 函数&#xff0c;将会往 $file 里写入 <?php die(大佬别秀了…

Drools开源业务规则引擎(三)- 事件模型(Event Model)

文章目录 Drools开源业务规则引擎&#xff08;三&#xff09;- 事件模型&#xff08;Event Model&#xff09;1.org.kie.api.event2.RuleRuntimeEventManager3.RuleRuntimeEventListener接口说明示例规则文件规则执行日志输出 4.AgentaEventListener接口说明示例监听器实现类My…

leetcode力扣_双指针问题

141. 环形链表 思路&#xff1a;判断链表中是否有环是经典的算法问题之一。常见的解决方案有多种&#xff0c;其中最经典、有效的一种方法是使用 快慢指针&#xff08;Floyd’s Cycle-Finding Algorithm&#xff09;。 初始化两个指针&#xff1a;一个快指针&#xff08;fast&…

文件加密软件谁好用丨2024文件加密软件TOP10推荐

个人和企业都有隐私保护的需求&#xff0c;文件加密可以确保敏感信息不被未授权的人查看。在数据传输或存储过程中&#xff0c;加密可以防止数据被截获或非法访问。企业需要保护其商业机密&#xff0c;如专利、商业策略和客户信息等&#xff0c;防止竞争对手获取。加密可以保护…

腐蚀服务器如何设置管理员

可以设置服主与管理员 控制台中设置&#xff08;需游戏账号在线&#xff09; 服主 添加&#xff1a;在控制台中输入ownerid空格SteamID 删除&#xff1a;在控制台中输入removeowner空格SteamID 管理员 添加&#xff1a;在控制台中输入moderatorid空格SteamID 删除&#…

分布式事务get global lock fail Xid 获取全局锁失败

问题如下&#xff1a; 解决方法&#xff1a;在发生报错的方法上添加本地事务