最近在审计Aptos公链,我主要负责mempool和execute的审计,里面用到了Move虚拟机,所以对Move虚拟机的原理进行了一定的探索。本系列文章尝试通过对照move虚拟机执行阶段的源码,来分析move合约实际的工作原理,如果不了解move语言,可以先阅读官方文档:move book
合约一
- module test_05::test_move{
- public fun test_local_variable(){
- let i = 0;
- }
- }
这是一个最简单的合约,其中只有一个方法test_local_variable,这个方法只会分配一个本地变量i,下面我们通过下面的命令执行反编译:
move disassemble --name test_move
得到的move汇编指令如下:
- // Move bytecode v5
- module f2.test_move {
-
-
- public test_local_variable() {
- L0: i: u64
- B0:
- 0: LdU64(0)
- 1: Pop
- 2: Ret
- }
- }
Move虚拟机最终是针对这些汇编指令进行执行。Move虚拟机使用interpreter.rs进行指令的执行,执行的时候主要依赖两个个数据结构:
- /// `Interpreter` instances can execute Move functions.
- ///
- /// An `Interpreter` instance is a stand alone execution context for a function.
- /// It mimics execution on a single thread, with an call stack and an operand stack.
- pub(crate) struct Interpreter {
- /// Operand stack, where Move `Value`s are stored for stack operations.
- operand_stack: Stack,
- /// The stack of active functions.
- call_stack: CallStack,
- }
operand_stack是一个栈,用来暂存执行指令时的本地数据,call_stack是个列表,用来存放Frame:
struct CallStack(Vec);
这里的Frame是栈帧,执行一次Function会创建一个栈帧,所以当一个Function中调用另一个Function的时候,需要把当前Function的上下文保存起来,call_stack就是用来保存当前Function的。
下面是执行入口代码:
- /// Main loop for the execution of a function.
- ///
- /// This function sets up a `Frame` and calls `execute_code_unit` to execute code of the
- /// function represented by the frame. Control comes back to this function on return or
- /// on call. When that happens the frame is changes to a new one (call) or to the one
- /// at the top of the stack (return). If the call stack is empty execution is completed.
- // REVIEW: create account will be removed in favor of a native function (no opcode) and
- // we can simplify this code quite a bit.
- fn execute_main(
- &mut self,
- loader: &Loader,
- data_store: &mut impl DataStore,
- gas_meter: &mut impl GasMeter,
- extensions: &mut NativeContextExtensions,
- function: Arc
, - ty_args: Vec
, - args: Vec
, - ) -> VMResult<Vec
> { - let mut locals = Locals::new(function.local_count());
- for (i, value) in args.into_iter().enumerate() {
- locals
- .store_loc(i, value)
- .map_err(|e| self.set_location(e))?;
- }
-
- let mut current_frame = Frame::new(function, ty_args, locals);
- loop {
- let resolver = current_frame.resolver(loader);
- let exit_code = current_frame //self
- .execute_code(&resolver, self, data_store, gas_meter)
- .map_err(|err| self.maybe_core_dump(err, ¤t_frame))?;
- match exit_code {
- ExitCode::Return => {
- if let Some(frame) = self.call_stack.pop() {
- current_frame = frame;
- current_frame.pc += 1; // advance past the Call instruction in the caller
- } else {
- return Ok(mem::take(&mut self.operand_stack.0));
- }
- }
- ExitCode::Call(fh_idx) => {
- let func = resolver.function_from_handle(fh_idx);
-
- // Charge gas
- let module_id = func
- .module_id()
- .ok_or_else(|| {
- PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
- .with_message("Failed to get native function module id".to_string())
- })
- .map_err(|e| set_err_info!(current_frame, e))?;
- gas_meter
- .charge_call(
- module_id,
- func.name(),
- self.operand_stack
- .last_n(func.arg_count())
- .map_err(|e| set_err_info!(current_frame, e))?,
- )
- .map_err(|e| set_err_info!(current_frame, e))?;
-
- if func.is_native() {
- self.call_native(
- &resolver,
- data_store,
- gas_meter,
- extensions,
- func,
- vec![],
- )?;
- current_frame.pc += 1; // advance past the Call instruction in the caller
- continue;
- }
- let frame = self
- .make_call_frame(func, vec![])
- .map_err(|err| self.maybe_core_dump(err, ¤t_frame))?;
- self.call_stack.push(current_frame).map_err(|frame| {
- let err = PartialVMError::new(StatusCode::CALL_STACK_OVERFLOW);
- let err = set_err_info!(frame, err);
- self.maybe_core_dump(err, &frame)
- })?;
- current_frame = frame;
- }
- ExitCode::CallGeneric(idx) => {
- // TODO(Gas): We should charge gas as we do type substitution...
- let ty_args = resolver
- .instantiate_generic_function(idx, current_frame.ty_args())
- .map_err(|e| set_err_info!(current_frame, e))?;
- let func = resolver.function_from_instantiation(idx);
-
- // Charge gas
- let module_id = func
- .module_id()
- .ok_or_else(|| {
- PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
- .with_message("Failed to get native function module id".to_string())
- })
- .map_err(|e| set_err_info!(current_frame, e))?;
- gas_meter
- .charge_call_generic(
- module_id,
- func.name(),
- ty_args.iter().map(|ty| TypeWithLoader { ty, loader }),
- self.operand_stack
- .last_n(func.arg_count())
- .map_err(|e| set_err_info!(current_frame, e))?,
- )
- .map_err(|e| set_err_info!(current_frame, e))?;
-
- if func.is_native() {
- self.call_native(
- &resolver, data_store, gas_meter, extensions, func, ty_args,
- )?;
- current_frame.pc += 1; // advance past the Call instruction in the caller
- continue;
- }
- let frame = self
- .make_call_frame(func, ty_args)
- .map_err(|err| self.maybe_core_dump(err, ¤t_frame))?;
- self.call_stack.push(current_frame).map_err(|frame| {
- let err = PartialVMError::new(StatusCode::CALL_STACK_OVERFLOW);
- let err = set_err_info!(frame, e