文章目录
- 一、概述
- 二、主要功能
- 三、关键函数解析
- 3.1 汇编器
- 3.1.1 `parse` -转换为Instruction列表
- 3.1.2 `assemble_internal`-转换为Insn
- 3.2 反汇编器
- 3.2.1 `to_insn_vec`-转换为机器指令
- 四、总结
Welcome to Code Block's blog本篇文章主要介绍了
[rbpf虚拟机-汇编和反汇编器]
❤博主广交技术好友,喜欢我的文章的可以关注一下❤
一、概述
该篇文章是rbpf汇编器和反汇编器代码块功能的整理。
(学习该虚拟机的目的是为了搞懂solana合约的执行方式,solana使用的rbpf是在该虚拟机上进行扩展。)
汇编器的作用是将机器指令转换为二进制操作码,反汇编器将二进制操作码数据转换为机器指令的形式进行显示。
二、主要功能
汇编和反汇编器主要实现以下功能:
- 机器指令转二进制
- 机器指令到初始指令结构体(Instruction)
- 初始指令类到指令结构体(Insn)
- 指令列表通过切片转换为二进制
- 二进制转机器指令
- 循环解析到机器指令
三、关键函数解析
3.1 汇编器
3.1.1 parse
-转换为Instruction列表
pub fn parse(input: &str) -> Result<Vec<Instruction>, String> {//1.跳过零个或多个空格//2.这里的with 不关心((), ["mov", "add", "sub"]) 里的()let mut with = spaces().with(many(instruction()).skip(eof()));#[cfg(feature = "std")]{//根据with解析器去解析指令match with.easy_parse(position::Stream::new(input)) {Ok((insts, _)) => Ok(insts),Err(err) => Err(err.to_string()),}}#[cfg(not(feature = "std"))]{match with.parse(position::Stream::new(input)) {Ok((insts, _)) => Ok(insts),Err(err) => Err(err.to_string()),}}
}
在parse方法中,会将机器指令如:"move r1, 0"格式转换为:
[Instruction { name: "mov", operands: [Register(1), Integer(0)] }]
主要使用的是combine
转换库,在这里是根据空格和换行分割后,使用instruction()
进行封装,instruction()
方法内容如下:
n instruction<I>() -> impl Parser<I, Output = Instruction>
whereI: Stream<Token = char>,I::Error: ParseError<I::Token, I::Range, I::Position>,
{//解析以特定分隔符分隔的多个元素。let operands = sep_by(operand(), char(',').skip(spaces()));(ident().skip(spaces()), operands, spaces()).map(|t| Instruction {name: t.0,operands: t.1,})
}
什么是combine?
combine 是一个基于解析器组合子(parser combinators)的库,它允许开发者通过组合简单的解析器来构建复杂的解析器。解析器组合子是一种函数式编程技术,通过将小的解析器组合起来,形成更大、更复杂的解析器。
combine文档
3.1.2 assemble_internal
-转换为Insn
fn assemble_internal(parsed: &[Instruction]) -> Result<Vec<Insn>, String> {let instruction_map = make_instruction_map();let mut result: Vec<Insn> = vec![];//循环parsed列表 (parsed来自parse方法返回值)for instruction in parsed {//获取命令名称let name = instruction.name.as_str();//匹配类型和opcodematch instruction_map.get(name) {Some(&(inst_type, opc)) => {match encode(inst_type, opc, &instruction.operands) {//匹配成功后返在结果内添加命令Ok(insn) => result.push(insn),//发生错误时,返回错误Err(msg) => return Err(format!("Failed to encode {name}: {msg}")),}// Special case for lddw.// 当类型是imm类型时,对命令取高32位进行拼接if let LoadImm = inst_type {if let Integer(imm) = instruction.operands[1] {result.push(insn(0, 0, 0, 0, imm >> 32).unwrap());}}}None => return Err(format!("Invalid instruction {name:?}")),}}Ok(result)
}
assemble_internal()
作用是将parse()
处理完成后的数据进行进一步的处理,处理成以下数据格式:
[Insn { opc: 183, dst: 1, src: 0, off: 0, imm: 0 }]
Insn数据为ebpf的指令格式.
31 24 23 16 15 8 7 0
+--------+--------+--------+--------+
| dst | src | offset | opcode |
+--------+--------+--------+--------+
| immediate / address |
+----------------------------------------+
然后通过切片extend_from_slice
的方式将其转换为二进制格式。完整的转换代码如下:
pub fn assemble(src: &str) -> Result<Vec<u8>, String> {let parsed = (parse(src))?;//Ok([Instruction { name: "mov", operands: [Register(0), Integer(0)] }, Instruction { name: "add", operands: [Register(1), Integer(2)] }])let insns = (assemble_internal(&parsed))?;let mut result: Vec<u8> = vec![];for insn in insns {result.extend_from_slice(&insn.to_array());}Ok(result)
}
3.2 反汇编器
3.2.1 to_insn_vec
-转换为机器指令
pub fn to_insn_vec(prog: &[u8]) -> Vec<HLInsn> {//验证程序是规定的8位if prog.len() % ebpf::INSN_SIZE != 0 {panic!("[Disassembler] Error: eBPF program length must be a multiple of {:?} octets",ebpf::INSN_SIZE);}//验证是否为空if prog.is_empty() {return vec![];}let mut res = vec![];let mut insn_ptr: usize = 0;//循环遍历指令while insn_ptr * ebpf::INSN_SIZE < prog.len() {let insn = ebpf::get_insn(prog, insn_ptr);let name;let desc;let mut imm = insn.imm as i64;match insn.opc {ebpf::LD_ABS_B => {name = "ldabsb";desc = ldabs_str(name, &insn);},......}res
}
在to_insn_vec
方法代码中使用循环的方式对每个二进制指令(8位
),根据每条指令的opcode码进行转换,转换为字符串格式的机器指令(desc
),其中包括对指令长度、是否为空进行检查。
四、总结
通过上述对源码进行解读,可以看到汇编器和反汇编器在虚拟机中起着重要的作用,允许开发者直接编写与 eBPF 架构兼容的指令。通过汇编器,这些指令被翻译成内核能够理解和执行的二进制形式。
代码来源:rbpf虚拟机
鸣谢: qmonnet 提供的开源代码.
当然,我也会将带有中文注释和自己理解的一些代码上传的我的github页面,感兴趣的朋友可以进行clone查看.
我的GitHub:forked
感谢您的点赞、关注、收藏!