文章目录
- 一、概述
- 背景知识
- 二、`call` 指令的主要方法
- 2.1 注册辅助函数
- 2.2 执行辅助函数
- 三、完整代码示例与详解
- 3.1 示例辅助函数
- 3.2 测试虚拟机的 `call` 指令
- 测试代码
- 代码解析
- 四、总结
Welcome to Code Block's blog本篇文章主要介绍了
[rbpf虚拟机-call指令]
❤博主广交技术好友,喜欢我的文章的可以关注一下❤
一、概述
本文重点介绍 RBPF(eBPF 的一种变体)虚拟机中 call
指令的作用与使用方式。
学习 RBPF 虚拟机的目的在于理解 Solana 合约的执行方式,因为 Solana 所使用的 RBPF 是在该虚拟机的基础上进行了功能扩展。
背景知识
在虚拟机的机器指令执行过程中,某些复杂功能(如获取当前时间、生成随机数等)无法通过基础计算功能实现,这时就需要调用自定义的辅助函数来拓展虚拟机的功能。
call
指令的格式如下:
call <key>
二、call
指令的主要方法
2.1 注册辅助函数
要使用辅助函数,首先需要对其进行注册。注册的过程是向 helpers
中添加辅助函数的实现,代码如下:
pub fn register_helper(&mut self, key: u32, function: Helper) -> Result<(), Error> {self.helpers.insert(key, function); // 将辅助函数以 `key-value` 的形式存入 `helpers`Ok(())
}
说明:
- 方法会在实际使用前进行注册。
- 辅助函数的名称(或标识)会通过
key
被存储,并在执行execute_program
方法时通过key
被调用。
2.2 执行辅助函数
在虚拟机的 execute_program
方法中,当遇到 call
指令时,会调用如下处理逻辑:
ebpf::CALL => {if let Some(function) = helpers.get(&(insn.imm as u32)) { // 根据指令中的 `key` (`insn.imm`) 查找对应的辅助函数reg[0] = function(reg[1], reg[2], reg[3], reg[4], reg[5]); // 调用辅助函数,并将寄存器 r1 - r5 的值作为参数传入,// 将返回值存入寄存器 r0} else {Err(Error::new(ErrorKind::Other,format!("Error: unknown helper function (id: {:#x})", insn.imm as u32),))?;}
}
说明:
- 如果找到与
key
匹配的辅助函数,则会传入r1
至r5
五个寄存器的值作为参数进行调用。 - 调用结果会存储在
r0
寄存器中。 - 如果未找到对应的辅助函数,则会返回错误提示。
三、完整代码示例与详解
3.1 示例辅助函数
以下是一个辅助函数 memfrob
的实现,功能是将指针指向的内存中每位与 0b101010
做异或运算:
#[allow(unused_variables)]
pub fn memfrob(ptr: u64, len: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {for i in 0..len {unsafe {let p = (ptr + i) as *mut u8; // 将指针偏移至当前操作地址*p ^= 0b101010; // 按位异或操作}}0 // 返回固定值(实际业务逻辑可能不同)
}
3.2 测试虚拟机的 call
指令
以下是一段测试代码,验证带有 call
指令的程序在 RBPF 虚拟机中的执行效果:
测试代码
#[test]
fn test_vm_call_memfrob() {// 汇编程序中有 call 指令,编号为 1let prog = assemble("mov r6, r1add r1, 2mov r2, 4call 1 // 调用编号为 1 的辅助函数ldxdw r0, [r6]be64 r0exit",).unwrap();// 定义测试时使用的内存let mem = &mut [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];// 创建虚拟机并加载程序let mut vm = rbpf::EbpfVmRaw::new(Some(&prog)).unwrap();// 注册辅助函数,辅助函数 key 为 1,指向 memfrobvm.register_helper(1, helpers::memfrob).unwrap();// 执行虚拟机程序,并验证返回结果assert_eq!(vm.execute_program(mem).unwrap(), 0x102292e2f2c0708);
}
代码解析
-
汇编程序解析:
•mov r6, r1
:将r1
的值移动到r6
。
•add r1, 2
:将寄存器r1
的值加 2。
•mov r2, 4
:将值 4 写入寄存器r2
。
•call 1
:调用编号为 1 的辅助函数(即memfrob
)。
•ldxdw r0, [r6]
:从r6
指向的内存地址加载 64 位数据到r0
。
•be64 r0
:执行特定操作(分支或跳转),依赖于架构实现。
•exit
:退出程序。 -
关键步骤:
•vm.register_helper(1, helpers::memfrob)
:将memfrob
函数注册到虚拟机,编号为1
,以供汇编程序中的call 1
调用。
•vm.execute_program(mem)
:加载并执行虚拟机程序。
•assert_eq!(...)
:验证虚拟机程序的执行结果是否符合预期。
四、总结
-
call
指令的作用:
•call
指令用于在程序中调用注册过的辅助函数,拓展虚拟机的功能。
• 这使得虚拟机能够处理复杂逻辑,如内存操作、时间获取、随机数生成等。 -
注册与调用过程:
• 辅助函数需提前通过register_helper
方法注册,并与唯一的key
(通常为u32
)绑定。
• 在汇编程序中,通过call <key>
指令调用辅助函数,虚拟机会根据key
查找对应的函数并执行。 -
代码结构:
•register_helper
:用于注册辅助函数。
•execute_program
:虚拟机程序执行的核心,包含call
指令的分发逻辑。
• 示例程序展示了虚拟机如何使用注册的辅助函数,以及如何验证执行结果。
代码来源:rbpf虚拟机
鸣谢: qmonnet 提供的开源代码.
当然,我也会将带有中文注释和自己理解的一些代码上传的我的github页面,感兴趣的朋友可以进行clone查看.
我的GitHub:forked
感谢您的点赞、关注、收藏!