• 基于虚拟机源码分析move合约(零):Move虚拟机执行原理


    最近在审计Aptos公链,我主要负责mempool和execute的审计,里面用到了Move虚拟机,所以对Move虚拟机的原理进行了一定的探索。本系列文章尝试通过对照move虚拟机执行阶段的源码,来分析move合约实际的工作原理,如果不了解move语言,可以先阅读官方文档:move book

    合约一

    1. module test_05::test_move{
    2. public fun test_local_variable(){
    3. let i = 0;
    4. }
    5. }

    这是一个最简单的合约,其中只有一个方法test_local_variable,这个方法只会分配一个本地变量i,下面我们通过下面的命令执行反编译:

    move disassemble --name test_move

    得到的move汇编指令如下:

    1. // Move bytecode v5
    2. module f2.test_move {
    3. public test_local_variable() {
    4. L0: i: u64
    5. B0:
    6. 0: LdU64(0)
    7. 1: Pop
    8. 2: Ret
    9. }
    10. }

    Move虚拟机最终是针对这些汇编指令进行执行。Move虚拟机使用interpreter.rs进行指令的执行,执行的时候主要依赖两个个数据结构:

    1. /// `Interpreter` instances can execute Move functions.
    2. ///
    3. /// An `Interpreter` instance is a stand alone execution context for a function.
    4. /// It mimics execution on a single thread, with an call stack and an operand stack.
    5. pub(crate) struct Interpreter {
    6. /// Operand stack, where Move `Value`s are stored for stack operations.
    7. operand_stack: Stack,
    8. /// The stack of active functions.
    9. call_stack: CallStack,
    10. }

    operand_stack是一个栈,用来暂存执行指令时的本地数据,call_stack是个列表,用来存放Frame:

    struct CallStack(Vec);

    这里的Frame是栈帧,执行一次Function会创建一个栈帧,所以当一个Function中调用另一个Function的时候,需要把当前Function的上下文保存起来,call_stack就是用来保存当前Function的。

    下面是执行入口代码:

    1. /// Main loop for the execution of a function.
    2. ///
    3. /// This function sets up a `Frame` and calls `execute_code_unit` to execute code of the
    4. /// function represented by the frame. Control comes back to this function on return or
    5. /// on call. When that happens the frame is changes to a new one (call) or to the one
    6. /// at the top of the stack (return). If the call stack is empty execution is completed.
    7. // REVIEW: create account will be removed in favor of a native function (no opcode) and
    8. // we can simplify this code quite a bit.
    9. fn execute_main(
    10. &mut self,
    11. loader: &Loader,
    12. data_store: &mut impl DataStore,
    13. gas_meter: &mut impl GasMeter,
    14. extensions: &mut NativeContextExtensions,
    15. function: Arc,
    16. ty_args: Vec,
    17. args: Vec,
    18. ) -> VMResult<Vec> {
    19. let mut locals = Locals::new(function.local_count());
    20. for (i, value) in args.into_iter().enumerate() {
    21. locals
    22. .store_loc(i, value)
    23. .map_err(|e| self.set_location(e))?;
    24. }
    25. let mut current_frame = Frame::new(function, ty_args, locals);
    26. loop {
    27. let resolver = current_frame.resolver(loader);
    28. let exit_code = current_frame //self
    29. .execute_code(&resolver, self, data_store, gas_meter)
    30. .map_err(|err| self.maybe_core_dump(err, ¤t_frame))?;
    31. match exit_code {
    32. ExitCode::Return => {
    33. if let Some(frame) = self.call_stack.pop() {
    34. current_frame = frame;
    35. current_frame.pc += 1; // advance past the Call instruction in the caller
    36. } else {
    37. return Ok(mem::take(&mut self.operand_stack.0));
    38. }
    39. }
    40. ExitCode::Call(fh_idx) => {
    41. let func = resolver.function_from_handle(fh_idx);
    42. // Charge gas
    43. let module_id = func
    44. .module_id()
    45. .ok_or_else(|| {
    46. PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
    47. .with_message("Failed to get native function module id".to_string())
    48. })
    49. .map_err(|e| set_err_info!(current_frame, e))?;
    50. gas_meter
    51. .charge_call(
    52. module_id,
    53. func.name(),
    54. self.operand_stack
    55. .last_n(func.arg_count())
    56. .map_err(|e| set_err_info!(current_frame, e))?,
    57. )
    58. .map_err(|e| set_err_info!(current_frame, e))?;
    59. if func.is_native() {
    60. self.call_native(
    61. &resolver,
    62. data_store,
    63. gas_meter,
    64. extensions,
    65. func,
    66. vec![],
    67. )?;
    68. current_frame.pc += 1; // advance past the Call instruction in the caller
    69. continue;
    70. }
    71. let frame = self
    72. .make_call_frame(func, vec![])
    73. .map_err(|err| self.maybe_core_dump(err, ¤t_frame))?;
    74. self.call_stack.push(current_frame).map_err(|frame| {
    75. let err = PartialVMError::new(StatusCode::CALL_STACK_OVERFLOW);
    76. let err = set_err_info!(frame, err);
    77. self.maybe_core_dump(err, &frame)
    78. })?;
    79. current_frame = frame;
    80. }
    81. ExitCode::CallGeneric(idx) => {
    82. // TODO(Gas): We should charge gas as we do type substitution...
    83. let ty_args = resolver
    84. .instantiate_generic_function(idx, current_frame.ty_args())
    85. .map_err(|e| set_err_info!(current_frame, e))?;
    86. let func = resolver.function_from_instantiation(idx);
    87. // Charge gas
    88. let module_id = func
    89. .module_id()
    90. .ok_or_else(|| {
    91. PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
    92. .with_message("Failed to get native function module id".to_string())
    93. })
    94. .map_err(|e| set_err_info!(current_frame, e))?;
    95. gas_meter
    96. .charge_call_generic(
    97. module_id,
    98. func.name(),
    99. ty_args.iter().map(|ty| TypeWithLoader { ty, loader }),
    100. self.operand_stack
    101. .last_n(func.arg_count())
    102. .map_err(|e| set_err_info!(current_frame, e))?,
    103. )
    104. .map_err(|e| set_err_info!(current_frame, e))?;
    105. if func.is_native() {
    106. self.call_native(
    107. &resolver, data_store, gas_meter, extensions, func, ty_args,
    108. )?;
    109. current_frame.pc += 1; // advance past the Call instruction in the caller
    110. continue;
    111. }
    112. let frame = self
    113. .make_call_frame(func, ty_args)
    114. .map_err(|err| self.maybe_core_dump(err, ¤t_frame))?;
    115. self.call_stack.push(current_frame).map_err(|frame| {
    116. let err = PartialVMError::new(StatusCode::CALL_STACK_OVERFLOW);
    117. let err = set_err_info!(frame, e
  • 相关阅读:
    windows安装yarn 详细教程
    十九、一起学习Lua 垃圾回收
    Parallels 将扩展桌面平台产品,以进一步改善在 Mac 上运行 Windows 的用户体验和工作效率
    Qt篇——QTableWidget选中多行右键删除
    字符串内穿插{}使用
    react18 安装 react-activation 后,依赖报错,解决办法
    qemu的详细资料大全(入门必看!!!)
    Java-访问控制
    ModbusTCP 转 Profinet 主站网关在博图配置案例
    申报高企的条件你真的满足了吗?
  • 原文地址:https://blog.csdn.net/biakia0610/article/details/127091796