rbpf虚拟机-即时编译器(JIT)

本文涉及的产品
云原生网关 MSE Higress,422元/月
MSE Nacos/ZooKeeper 企业版试用,1600元额度,限量50份
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 该篇文章是记录rbpf虚拟机即时编译器(JIT)方法过程。本文记录的是基于 x86-64 架构的 eBPF(Extended Berkeley Packet Filter)即时编译器(JIT)。(学习该虚拟机的目的是为了搞懂solana合约的执行方式,solana使用的rbpf是在该虚拟机上进行扩展。)

✨重磅!盹猫的个人小站正式上线啦~诚邀各位技术大佬前来探秘!✨
—— 专为开发者打造的宝藏基地,等你来探索!
这里有:


🔥 硬核技术干货:编程技巧、开发经验、踩坑指南,带你解锁技术新姿势!
🎉 趣味开发日常:代码背后的脑洞故事、工具测评,让技术圈不再枯燥~
💎 独家资源分享:开源项目、学习资料包,助你打怪升级快人一步!


🚀 立即访问 → 盹猫猫的个人小站 ← 点击探索
🌟 说不定这里就有你寻找已久的技术秘籍哦~

@[toc]

Welcome to Code Block's blog

本篇文章主要介绍了
[rbpf虚拟机-即时编译器(JIT)]
❤博主广交技术好友,喜欢我的文章的可以关注一下❤

一、概述

该篇文章是记录rbpf虚拟机即时编译器(JIT)方法过程。

本文记录的是基于 x86-64 架构的 eBPF(Extended Berkeley Packet Filter)即时编译器(JIT)。

(学习该虚拟机的目的是为了搞懂solana合约的执行方式,solana使用的rbpf是在该虚拟机上进行扩展。)


二、主要功能

2.1 寄存器映射

eBPF 有 11 个通用寄存器,x86-64 有更多的寄存器。

  • RAX 映射到 eBPF 的返回值寄存器。
  • RDI、RSI、RDX 等寄存器用于传递参数。

    2.2 指令发射

    指令发射是 JIT 编译的核心部分(emit*),在本编译器中主要由下述指令完成:

  • emit_alu32emit_alu64 生成算术逻辑单元(ALU)指令。

  • emit_mov 生成数据传输指令。
  • emit_jccemit_jmp 生成条件跳转和无条件跳转指令。

    2.3 跳转处理

    eBPF 中的跳转指令(如 JEQ、JGT 等)动态计算目标地址。

    2.4 辅助函数调用

    eBPF 支持调用辅助函数(如哈希表查找、随机数生成等)。

三、关键代码

3.1 寄存器上下文

先保存寄存器的当前值,以便在 JIT 编译的代码执行完成后可以恢复它们。
保存寄存器

    self.emit_push(mem, RBP);
    self.emit_push(mem, RBX);
    self.emit_push(mem, R13);
    self.emit_push(mem, R14);
    self.emit_push(mem, R15);

3.2 存储中间值

self.emit_mov(mem, RDX, R10);

RDX用于传递内存指针,这里将RDX赋值给R10,即使用R10存储临时数据。

3.3 内存缓冲区

根据use_mbuff和update_data_ptr的值来判断:

  • use_mbuff:是否启用缓冲区
  • update_data_ptr:是否需要更新数据指针

为JIT编译的函数序言准备寄存器和内存指针状态。

  match (use_mbuff, update_data_ptr) {
     
      (false, _) => {
     
          //不使用任何mbuff,内存指针移至寄存器1
          if map_register(1) != RDX {
     
              self.emit_mov(mem, RDX, map_register(1));
          }
      }
      (true, false) => {
     
          // 使用已经指向mem和mem_end的mbuff:将其移动到寄存器1。
          // We use a mbuff already pointing to mem and mem_end: move it to register 1.
          if map_register(1) != RDI {
     
              self.emit_mov(mem, RDI, map_register(1));
          }
      }
      //使用 mbuff,并且需要更新数据指针(mem 和 mem_end 的偏移值)
      (true, true) => {
     
          self.emit_alu64(mem, 0x01, RDI, R8); // add mbuff to mem_offset in R8
          self.emit_store(mem, OperandSize::S64, RDX, R8, 0); // set mem at mbuff + mem_offset
                                                              // Store mem_end at mbuff + mem_end_offset. Trash R9.
          self.emit_load(mem, OperandSize::S64, RDX, R8, 0); // load mem into R8
          self.emit_alu64(mem, 0x01, RCX, R8); // add mem_len to mem (= mem_end)
          self.emit_alu64(mem, 0x01, RDI, R9); // add mbuff to mem_end_offset
          self.emit_store(mem, OperandSize::S64, R8, R9, 0); // store mem_end

          // Move rdi into register 1
          if map_register(1) != RDI {
     
              self.emit_mov(mem, RDI, map_register(1));
          }
      }
  }

对应功能
当不使用任何mbuff,内存指针移至寄存器1
当使用已经指向mem和mem_end的mbuff:将其移动到寄存器1。
当使用 mbuff,并且需要更新数据指针(mem 和 mem_end 的偏移值)

3.4 初始化栈

self.emit_mov(mem, RSP, map_register(10));
self.emit_alu64_imm32(mem, 0x81, 5, RSP, ebpf::STACK_SIZE as i32);

将RSP(栈指针)赋值给寄存器R10,分配栈空间(emit_alu64_imm32 调整 RSP)。

3.5 指令翻译

        while insn_ptr * ebpf::INSN_SIZE < prog.len() {
     
            let insn = ebpf::get_insn(prog, insn_ptr);

            self.pc_locs[insn_ptr] = mem.offset;

            let dst = map_register(insn.dst);
            let src = map_register(insn.src);
            let target_pc = insn_ptr as isize + insn.off as isize + 1;

            match insn.opc {
     
                // BPF_LD class
                // R10 is a constant pointer to mem.
                ebpf::LD_ABS_B => self.emit_load(mem, OperandSize::S8, R10, RAX, insn.imm),
                ....}
    }

这里类似与汇编器和反汇编器,对不同指令进行翻译。

3.6 收尾部分(Epilogue)

        //1.
        self.set_anchor(mem, TARGET_PC_EXIT);
        //2.
        // Move register 0 into rax
        if map_register(0) != RAX {
     
            self.emit_mov(mem, map_register(0), RAX);
        }
        //3.
        // Deallocate stack space
        self.emit_alu64_imm32(mem, 0x81, 0, RSP, ebpf::STACK_SIZE as i32);
  • self.set_anchor(mem, TARGET_PC_EXIT); 即设置突出标记,对应汇编码总的EXIT
  • 确保返回值符合 x86-64 调用约定。
  • 释放栈空间。

    3.7 恢复寄存器和返回调用

     self.emit_pop(mem, R15);
     self.emit_pop(mem, R14);
     self.emit_pop(mem, R13);
     self.emit_pop(mem, RBX);
     self.emit_pop(mem, RBP);
     //返回
     self.emit1(mem, 0xc3); // ret
    

什么是x86-64 调用约定(System V ABI )?

  • 现代64位Linux/Unix系统的标准约定。

    • 前6个整型参数通过寄存器传递(RDI, RSI, RDX, RCX, R8, R9),其余参数通过栈传递。

    • 返回值通过RAX传递,调用者负责保存部分寄存器。

四、总结

通过对即时编译器的执行进行分步骤记录,知道该JIT是按照x86-64调用标准进行编写,其寄存器和堆栈分配以及返回值都符合对应标准。对执行过程有了一些了解。

不同的(如arm架构)的JIT与X86其标准不同,所以其JIT过程也不同。

代码来源:rbpf虚拟机
鸣谢: qmonnet 提供的开源代码.

当然,我也会将带有中文注释和自己理解的一些代码上传的我的github页面,感兴趣的朋友可以进行clone查看.

我的GitHub:forked

目录
相关文章
|
4月前
|
安全
电脑32位系统能改64位系统吗
本文详解32位系统能否升级为64位。答案是:可以,但需满足CPU支持64位架构、内存至少4GB等条件。升级唯一方法是重装系统,需备份数据、制作启动U盘、设置BIOS并安装驱动。升级前务必确认硬件兼容性,避免系统运行不稳定。
|
7月前
|
人工智能 Linux API
119K star!无需GPU轻松本地部署多款大模型,DeepSeek支持!这个开源神器绝了
"只需一行命令就能在本地运行Llama 3、DeepSeek-R1等前沿大模型,支持Windows/Mac/Linux全平台,这个开源项目让AI开发从未如此简单!"
424 0
|
4月前
|
算法 关系型数据库 Java
Springboot集成PostGIS完成路径规划
因为公司里需要做关于林区防火方面的项目,需要完成着火后山区路径的导航,但.....某德的功能似乎只能到达山区的边上,后边的路就需要自己完成导航了。搞了一个周终于有所效果了,也遇见了很多的坑,在此记录一下,希望以后不要踩坑。需要上述的环境才能进行路径导航,环境的搭建可以参阅
155 5
|
4月前
|
传感器 定位技术 数据格式
常用通信协议及数据格式
常用通信协议和格式总结
434 2
|
4月前
|
存储 NoSQL 区块链
开源:LMDB 操作工具:lmcmd
本文介绍了 LMDB(一种高效的键值存储数据库)和基于 Python 开发的命令行工具 `lmcmd`。由于 LMDB 使用二进制文件存储,管理和调试不便,因此开发了 `lmcmd`,提供了类似 Redis 的命令行操作界面,支持数据库操作、数据导入导出和查找等功能。文章涵盖了 `lmcmd` 的安装、连接数据库和常用命令(如 `set`、`get`、`export` 等)示例。最后强调了开源工具的价值,鼓励用户反馈和改进。
152 1
|
4月前
|
jenkins Java 持续交付
使用Jenkins完成springboot项目快速更新
本文介绍了使用Jenkins和WinSW实现SpringBoot项目自动化部署的完整流程。首先讲解了Jenkins作为持续集成工具的作用,然后详细说明了环境准备步骤:包括JDK版本管理、WinSW服务配置(含XML文件修改)以及bat启动脚本编写。重点演示了Jenkins的项目配置方法,包括源码管理设置和构建步骤中的Windows批处理命令调用。通过这套方案,开发者只需推送代码到Git仓库,即可触发Jenkins自动完成项目构建、服务重启等全流程,显著提升部署效率。文章还提到IDEA的Jenkins插件可进
189 0
|
SQL 存储 数据库
excel导入sql数据库
将Excel数据导入SQL数据库是一个相对常见的任务,可以通过多种方法来实现。以下是一些常用的方法: ### 使用SQL Server Management Studio (SSMS) 1
|
8月前
|
机器学习/深度学习 传感器 数据采集
基于机器学习的数据分析:PLC采集的生产数据预测设备故障模型
本文介绍如何利用Python和Scikit-learn构建基于PLC数据的设备故障预测模型。通过实时采集温度、振动、电流等参数,进行数据预处理和特征提取,选择合适的机器学习模型(如随机森林、XGBoost),并优化模型性能。文章还分享了边缘计算部署方案及常见问题排查,强调模型预测应结合定期维护,确保系统稳定运行。
853 0
|
数据可视化 数据挖掘 数据处理
淘宝天猫玩具销售数据可视化(下)
淘宝天猫玩具销售数据可视化(下)
691 2
|
9月前
|
小程序 测试技术 数据安全/隐私保护
微信公众号接口测试实战指南
微信公众号接口测试是确保系统稳定性和功能完整性的重要环节。本文详细介绍了测试全流程,包括准备、工具选择(如Postman、JMeter)、用例设计与执行,以及常见问题的解决方法。通过全面测试,可以提前发现潜在问题,优化用户体验,确保公众号上线后稳定运行。内容涵盖基础接口、高级接口、微信支付和数据统计接口的测试,强调了功能验证、性能优化、安全保护及用户体验的重要性。未来,随着微信生态的发展,接口测试将面临更多挑战和机遇,如小程序融合、AI应用和国际化拓展。