首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >rbpf虚拟机-即时编译器(JIT)

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

作者头像
盹猫
发布2025-07-22 18:50:54
发布2025-07-22 18:50:54
9600
代码可运行
举报
运行总次数:0
代码可运行

一、概述

该篇文章是记录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 编译的代码执行完成后可以恢复它们。 保存寄存器

代码语言:javascript
代码运行次数:0
运行
复制
    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 存储中间值

代码语言:javascript
代码运行次数:0
运行
复制
self.emit_mov(mem, RDX, R10);

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

3.3 内存缓冲区

根据use_mbuff和update_data_ptr的值来判断:

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

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

代码语言:javascript
代码运行次数:0
运行
复制
  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 初始化栈

代码语言:javascript
代码运行次数:0
运行
复制
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 指令翻译

代码语言:javascript
代码运行次数:0
运行
复制
        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)

代码语言:javascript
代码运行次数:0
运行
复制
		//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 恢复寄存器和返回调用

代码语言:javascript
代码运行次数:0
运行
复制
    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过程也不同。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-07-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
  • 二、主要功能
    • 2.1 寄存器映射
    • 2.2 指令发射
    • 2.3 跳转处理
    • 2.4 辅助函数调用
  • 三、关键代码
    • 3.1 寄存器上下文
    • 3.2 存储中间值
    • 3.3 内存缓冲区
    • 3.4 初始化栈
    • 3.5 指令翻译
    • 3.6 收尾部分(Epilogue)
    • 3.7 恢复寄存器和返回调用
  • 四、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档