【译】eBPF 概述:第 2 部分:机器和字节码

简介: 【译】eBPF 概述:第 2 部分:机器和字节码

1. 前言


我们在第 1 篇文章中 介绍了 eBPF 虚拟机,包括其有意的设计限制以及如何从用户空间进程中进行交互。如果你还没有读过这篇文章,建议你在继续之前读一下,因为没有适当的介绍,直接开始接触机器和字节码的细节是比较困难的。如果有疑问,请看第 1 部分 开头的流程图。


本系列的第 2 部分对第 1 部分中研究的 eBPF 虚拟机和程序进行了更深入的探讨。掌握这些低层次的知识并不是强制性的,但可以为本系列的其他部分打下非常有用的基础,我们将在这些机制的基础上研究更高层次的工具。


2. 虚拟机


eBPF 是一个 RISC 寄存器机,共有11 个 64 位寄存器,一个程序计数器和 512 字节的固定大小的栈。9 个寄存器是通用读写的,1 个是只读栈指针,程序计数器是隐式的,也就是说,我们只能跳转到它的某个偏移量。VM 寄存器总是 64 位宽(即使在 32 位 ARM 处理器内核中运行!),如果最重要的 32 位被清零,则支持 32 位子寄存器寻址 - 这在第 4 部分交叉编译和在嵌入式设备上运行 eBPF 程序时非常有用。

这些寄存器是:


r0: 存储返回值,包括函数调用和当前程序退出代码
r1-r5: 作为函数调用参数使用,在程序启动时,r1 包含 "上下文" 参数指针
r6-r9: 这些在内核函数调用之间被保留下来
r10: 每个 eBPF 程序 512 字节栈的只读指针


在加载时提供的 eBPF程序类型决定了哪些内核函数的子集可以被调用,以及在程序启动时通过 r1 提供的 "上下文" 参数。存储在 r0 中的程序退出值的含义也由程序类型决定。

每个函数调用在寄存器 r1-r5 中最多可以有 5 个参数;这适用于 ebpf 到 ebpf 的调用和内核函数调用。寄存器 r1-r5 只能存储数字或指向栈的指针(作为函数的参数),不能直接指向任意的内存。所有的内存访问必须在 eBPF 程序中使用之前首先将数据加载到 eBPF 栈。这一限制有助于 eBPF 验证器,它简化了内存模型,使其更容易进行内核检查。


BPF 可访问的内核 "帮助(helper)" 函数是由内核通过类似于定义 syscalls 的 API 定义的(不能通过模块扩展),定义使用 BPF_CALL_* 宏。bpf.h试图为所有 BPF 可访问的内核辅助函数提供参考。例如,bpf_trace_printk的定义使用了 BPF_CALL_5 和 5 对类型 / 参数名称。定义 参数数据类型 是非常重要的,因为在每次 eBPF 程序加载时,eBPF 验证器会确保寄存器的数据类型与被调用者的参数类型相符。


eBPF 指令也是固定大小的 64 位编码,目前大约有 100 条指令,被分组为8 类。该虚拟机支持从通用内存(map、栈、如数据包缓冲区等的 "上下文",)进行 1-8 字节的加载 / 存储,前 / 后(非)条件跳转、算术 / 逻辑操作和函数调用。操作码格式格式深入研究的文档,请参考 Cilium 项目指令集文档。IOVisor 项目也维护了一个有用的指令规格

在本系列第 1 部分研究的例子中,我们使用了部分有用的 内核宏,使用以下结构 创建了一个 eBPF 字节码指令数组(所有指令都是这样编码的):


struct bpf_insn {
  __u8  code;   /* opcode */
  __u8  dst_reg:4;  /* dest register */
  __u8  src_reg:4;  /* source register */
  __s16 off;    /* signed offset */
  __s32 imm;    /* signed immediate constant */
};
msb                                                        lsb
+------------------------+----------------+----+----+--------+
|immediate               |offset          |src |dst |opcode  |
+------------------------+----------------+----+----+--------+


让我们看看 BPF_JMP_IMM 指令,它编码了一个针对立即值的条件跳转。下面的宏注释对指令的逻辑应该是不言自明的。操作码编码了指令类别 BPF_JMP,操作(通过 BPF_OP 位域以确保核心性)和一个标志,表示它是对即期 / 常量值的操作,BPF_K。


#define BPF_OP(code)    ((code) & 0xf0)
#define BPF_K   0x00
/* Conditional jumps against immediates, if (dst_reg 'op' imm32) goto 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 })


如果我们去计算该指令的值,或者拆解一个包含 BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2)的 eBPF 字节码,我们会发现它是 0x020015。这个特定的字节码非常频繁地被用来测试存储在 r0 中的函数调用的返回值;如果 r0 == 0,它就会跳过接下来的 2 条指令。


3. 重新认识字节码


现在我们已经有了必要的知识来完全理解本系列第 1 部分中 eBPF 例子中使用的字节码,现在我们将一步一步地进行详解。记住,sock_example.c是一个简单的用户空间程序,使用 eBPF 来统计回环接口上收到多少个 TCP、UDP 和 ICMP 协议包。


在更高层次上,代码所做的是从接收到的数据包中读取协议号,然后把它推到 eBPF 栈中,作为 map_lookup_elem 调用的索引,从而得到各自协议的数据包计数。


map_lookup_elem 函数在 r0 接收一个索引(或键)指针,在 r1 接收一个 map 文件描述符。如果查找调用成功,r0 将包含一个指向存储在协议索引的 map 值的指针。然后我们原子式地增加 map 值并退出。


BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),


当一个 eBPF 程序启动时,r1 中的地址指向 context 上下文(当前情况下为数据包缓冲区)。r1 将在函数调用时用于参数,所以我们也将其存储在 r6 中作为备份。


BPF_LD_ABS(BPF_B, ETH_HLEN + offsetof(struct iphdr, protocol) /* R0 = ip->proto */),


这条指令从 context 上下文缓冲区的偏移量向 r0 加载一个字节(BPF_B),当前情况下是网络数据包缓冲区,所以我们从一个 iphdr 结构 中提供协议字节的偏移量,以加载到 r0。


BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), /* *(u32 *)(fp - 4) = r0 */


将包含先前读取的协议的字(BPF_W)加载到栈上(由 r10 指出,从偏移量 -4 字节开始)。


BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */


将栈地址指针移至 r2 并减去 4,所以现在 r2 指向协议值,作为下一个 map 键查找的参数。


BPF_LD_MAP_FD(BPF_REG_1, map_fd),


将本地进程中的文件描述符引用包含协议包计数的 map 加载到 r1。


BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),


执行 map 查找调用,将栈中由 r2 指向的协议值作为 key。结果存储在 r0 中:一个指向由 key 索引的值的指针地址。


BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),


还记得 0x020015 吗?这和第一节的字节码是一样的。如果 map 查找没有成功,r0 == 0,所以我们跳过下面两条指令。


BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */
BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0), /* xadd r0 += r1 */


递增 r0 所指向的地址的 map 值。


BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */
BPF_EXIT_INSN(),


将 eBPF 的 retcode 设置为 0 并退出。


尽管这个 sock_example 逻辑是非常简单(它只是在一个映射中增加一些数字),但在原始字节码中实现或理解它也是很难做到的。更加复杂的任务在像这样的汇编程序中完成会变得非常困难。展望未来,我们将准备使用更高级别的语言和工具来实现更强大的 eBPF 用例,而不费吹灰之力。


4. 总结


在这一部分中,我们仔细观察了 eBPF 虚拟机的寄存器和指令集,了解了 eBPF 可访问的内核函数是如何从字节码中调用的,以及它们是如何被核心内核通过类似 syscall 的特殊目的 API 定义的。我们也完全理解了第 1 部分例子中使用的字节码。还有一些未探索的领域,如创建多个 eBPF 程序函数或链式 eBPF 程序以绕过 Linux 发行版的 4096 条指令限制。也许我们会在以后的文章中探讨这些。


现在,主要的问题是编写原始字节码是很困难的,这非常像编写汇编代码,而且编写效果不高。在第 3 部分中,我们将开始研究使用高级语言编译成 eBPF 字节码,到此为止我们已经了解了虚拟机工作的底层基础知识。

目录
相关文章
Minecraft Forge部署以及部署时可能出现的问题以及解决方案
Minecraft Forge部署以及部署时可能出现的问题以及解决方案
2457 0
|
人工智能 调度 开发者
【项目复现上新】突破推理瓶颈!LightLLM轻量化部署新范式,打造高性能法律智能体
LightLLM突破大模型推理瓶颈,结合LlamaIndex实现高效RAG,打造轻量化、低延迟的法律智能体。支持实时检索与精准生成,单机即可部署,助力法律、医疗等专业场景落地,显著缓解知识滞后与幻觉问题,赋能AI应用高效复现与创新。
227 0
|
消息中间件 移动开发
PCIe RN (Readiness Notification)介绍
PCIe RN (Readiness Notification)介绍
1865 0
PCIe RN (Readiness Notification)介绍
|
3天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
1400 109
|
10天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1931 8
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
4天前
|
编解码 人工智能 安全
2核4G/4核8G/8核16G阿里云服务器如何选择实例?经济型e、通用算力型u2i与计算型c9i选哪个?
本文介绍了阿里云2核4G、4核8G、8核16G三档主流配置下经济型e、通用算力型u2i和计算型c9i三种实例的最新活动价格与适用场景。同配置下三者价差显著,以2核4G为例,经济型e低至599.93元/年,计算型c9i则高达1742.08元/年。文章详细解析了各实例的性能定位:经济型e适合轻负载入门场景,u2i兼顾稳定算力与性价比,c9i凭借第9代至强处理器与芯片级安全能力支撑高性能业务。同时提示用户可叠加满减优惠券享受折上折,建议根据业务负载与预算综合决策。
513 112
|
4天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
|
8天前
|
存储 人工智能 关系型数据库
阿里云AI产品与云产品最新组合套餐:Token Plan、AI coding及云服务器和建站等组合优惠价
阿里云推出全新“算力+模型+应用”一站式云与AI组合套餐活动,覆盖从个人开发者到中大型企业的全场景需求。核心亮点为分三档定价的Token Plan订阅服务,支持Qwen3.8-Max-Preview大模型调用,错峰时段最低可享0.2折优惠。活动同步推出AI Coding、智能体部署、云电脑托管、0代码建站等十余类场景化组合,搭配99元/年的普惠云服务器、88元/年的入门数据库等经典特惠产品,还为企业提供1V1定制化AI转型方案,大幅降低了不同用户群体拥抱AI的技术门槛与采购成本。
696 111
|
18天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2616 13
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航

热门文章

最新文章