From 3701873be68979b3575e7ffb68fa54d2d4bda4dd Mon Sep 17 00:00:00 2001 From: Poytr1 Date: Thu, 14 Sep 2023 23:36:59 +0800 Subject: [PATCH 1/3] add inputs to call trace --- third_party/move/move-vm/runtime/src/interpreter.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs index 908596b769c7b..2e182b6bbaaf5 100644 --- a/third_party/move/move-vm/runtime/src/interpreter.rs +++ b/third_party/move/move-vm/runtime/src/interpreter.rs @@ -218,6 +218,18 @@ impl Interpreter { current_frame.pc += 1; // advance past the Call instruction in the caller continue; } + self.call_traces.push(CallTrace { + pc: 0, + module_id: "".to_string(), + func_name: func.name().to_string(), + inputs: self.operand_stack.last_n(func.arg_count()).into_iter(), + outputs: vec![], + type_args: vec![], + }).map_err(|_e| { + let err = PartialVMError::new(StatusCode::ABORTED); + let err = set_err_info!(current_frame, err); + self.maybe_core_dump(err, ¤t_frame) + })?; let frame = self .make_call_frame(loader, func, vec![]) .map_err(|e| self.set_location(e)) From 17a7dfe1539168b433cea24b49a3bc8a0338553a Mon Sep 17 00:00:00 2001 From: Poytr1 Date: Sat, 16 Sep 2023 11:12:44 +0800 Subject: [PATCH 2/3] update --- third_party/move/move-vm/runtime/src/interpreter.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs index 2e182b6bbaaf5..fbdfd095fd54e 100644 --- a/third_party/move/move-vm/runtime/src/interpreter.rs +++ b/third_party/move/move-vm/runtime/src/interpreter.rs @@ -218,11 +218,15 @@ impl Interpreter { current_frame.pc += 1; // advance past the Call instruction in the caller continue; } + let mut inputs = vec![]; + for val in self.operand_stack.last_n(func.arg_count()).unwrap() { + inputs.push((*val).copy_value().unwrap()); + } self.call_traces.push(CallTrace { pc: 0, module_id: "".to_string(), func_name: func.name().to_string(), - inputs: self.operand_stack.last_n(func.arg_count()).into_iter(), + inputs, outputs: vec![], type_args: vec![], }).map_err(|_e| { From 61b1c40aa0dfa5a4c11b33992deac94a82d612d8 Mon Sep 17 00:00:00 2001 From: Poytr1 Date: Sat, 16 Sep 2023 20:35:13 +0800 Subject: [PATCH 3/3] update --- .../src/tests/multi_func_tests.rs | 37 +-- .../move/move-vm/runtime/src/interpreter.rs | 254 +++++++++++++++--- .../move/move-vm/runtime/src/runtime.rs | 71 +++++ .../move/move-vm/runtime/src/session.rs | 22 ++ .../move/move-vm/types/src/call_trace.rs | 48 ++++ third_party/move/move-vm/types/src/lib.rs | 1 + 6 files changed, 374 insertions(+), 59 deletions(-) create mode 100644 third_party/move/move-vm/types/src/call_trace.rs diff --git a/third_party/move/move-vm/integration-tests/src/tests/multi_func_tests.rs b/third_party/move/move-vm/integration-tests/src/tests/multi_func_tests.rs index 9a0180294782e..e4227196a77fc 100644 --- a/third_party/move/move-vm/integration-tests/src/tests/multi_func_tests.rs +++ b/third_party/move/move-vm/integration-tests/src/tests/multi_func_tests.rs @@ -12,6 +12,7 @@ use move_core_types::{ }; use move_vm_runtime::{move_vm::MoveVM, session::SerializedReturnValues}; use move_vm_test_utils::InMemoryStorage; +use move_vm_types::call_trace::CallTraces; use move_vm_types::gas::UnmeteredGasMeter; const TEST_ADDR: AccountAddress = AccountAddress::new([42; AccountAddress::LENGTH]); @@ -19,11 +20,11 @@ const TEST_ADDR: AccountAddress = AccountAddress::new([42; AccountAddress::LENGT fn run( ty_args: Vec, args: Vec, -) -> VMResult>> { +) -> VMResult { let code = r#" module {{ADDR}}::M { public fun foo(v1: u64): u64 { - bar(v1) + bar(v1) + 1 } public fun bar(v1: u64): u64 { @@ -52,21 +53,29 @@ fn run( .map(|val| val.simple_serialize().unwrap()) .collect(); - let SerializedReturnValues { - return_values, - mutable_reference_outputs: _, - } = sess.execute_function_bypass_visibility( + // let SerializedReturnValues { + // return_values, + // mutable_reference_outputs: _, + // } = sess.execute_function_bypass_visibility( + // &module_id, + // &fun_name, + // ty_args, + // args, + // &mut UnmeteredGasMeter, + // )?; + + sess.call_trace( &module_id, &fun_name, ty_args, args, &mut UnmeteredGasMeter, - )?; + ) - Ok(return_values - .into_iter() - .map(|(bytes, _layout)| bytes) - .collect()) + // Ok(return_values + // .into_iter() + // .map(|(bytes, _layout)| bytes) + // .collect()) } fn expect_success( @@ -75,11 +84,7 @@ fn expect_success( expected_layouts: &[MoveTypeLayout], ) { let return_vals = run(ty_args, args).unwrap(); - assert!(return_vals.len() == expected_layouts.len()); - - for (blob, layout) in return_vals.iter().zip(expected_layouts.iter()) { - MoveValue::simple_deserialize(blob, layout).unwrap(); - } + assert_eq!(return_vals.len(), 1); } #[test] diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs index fbdfd095fd54e..bc5fd856f4a9b 100644 --- a/third_party/move/move-vm/runtime/src/interpreter.rs +++ b/third_party/move/move-vm/runtime/src/interpreter.rs @@ -2,13 +2,7 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use crate::{ - data_cache::TransactionDataCache, - loader::{Function, Loader, Resolver}, - native_extensions::NativeContextExtensions, - native_functions::NativeContext, - trace, -}; +use crate::{data_cache::TransactionDataCache, interpreter, loader::{Function, Loader, Resolver}, native_extensions::NativeContextExtensions, native_functions::NativeContext, trace}; use fail::fail_point; use move_binary_format::{ errors::*, @@ -29,6 +23,7 @@ use move_vm_types::{ Vector, VectorRef, }, views::TypeView, + call_trace::{CallTraces, CallTrace}, }; use std::{cmp::min, collections::VecDeque, fmt::Write, sync::Arc}; @@ -68,8 +63,6 @@ pub(crate) struct Interpreter { call_stack: CallStack, /// Whether to perform a paranoid type safety checks at runtime. paranoid_type_checks: bool, - - call_traces: CallTraces, } struct TypeWithLoader<'a, 'b> { @@ -99,13 +92,31 @@ impl Interpreter { operand_stack: Stack::new(), call_stack: CallStack::new(), paranoid_type_checks: loader.vm_config().paranoid_type_checks, - call_traces: CallTraces::new(), } .execute_main( loader, data_store, gas_meter, extensions, function, ty_args, args, ) } + pub(crate) fn call_trace( + function: Arc, + ty_args: Vec, + args: Vec, + data_store: &mut TransactionDataCache, + gas_meter: &mut impl GasMeter, + extensions: &mut NativeContextExtensions, + loader: &Loader, + ) -> VMResult { + let interpreter = Interpreter { + operand_stack: Stack::new(), + call_stack: CallStack::new(), + paranoid_type_checks: loader.vm_config().paranoid_type_checks, + }; + interpreter.call_trace_internal( + loader, data_store, gas_meter, extensions, function, ty_args, args, + ) + } + /// Main loop for the execution of a function. /// /// This function sets up a `Frame` and calls `execute_code_unit` to execute code of the @@ -140,13 +151,174 @@ impl Interpreter { let mut current_frame = self .make_new_frame(loader, function, ty_args, locals) .map_err(|err| self.set_location(err))?; - self.call_traces.push(CallTrace { + loop { + let resolver = current_frame.resolver(loader); + let exit_code = + current_frame //self + .execute_code(&resolver, &mut self, data_store, gas_meter) + .map_err(|err| self.maybe_core_dump(err, ¤t_frame))?; + match exit_code { + ExitCode::Return => { + let non_ref_vals = current_frame + .locals + .drop_all_values() + .map(|(_idx, val)| val) + .collect::>(); + + // TODO: Check if the error location is set correctly. + gas_meter + .charge_drop_frame(non_ref_vals.iter()) + .map_err(|e| self.set_location(e))?; + + if let Some(frame) = self.call_stack.pop() { + // Note: the caller will find the callee's return values at the top of the shared operand stack + current_frame = frame; + current_frame.pc += 1; // advance past the Call instruction in the caller + } else { + // end of execution. `self` should no longer be used afterward + return Ok(self.operand_stack.value); + } + }, + ExitCode::Call(fh_idx) => { + let func = resolver.function_from_handle(fh_idx); + + if self.paranoid_type_checks { + self.check_friend_or_private_call(¤t_frame.function, &func)?; + } + + // 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))?, + (func.local_count() as u64).into(), + ) + .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(loader, func, vec![]) + .map_err(|e| self.set_location(e)) + .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) + })?; + // Note: the caller will find the the callee's return values at the top of the shared operand stack + 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); + + if self.paranoid_type_checks { + self.check_friend_or_private_call(¤t_frame.function, &func)?; + } + + // 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))?, + (func.local_count() as u64).into(), + ) + .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(loader, func, ty_args) + .map_err(|e| self.set_location(e)) + .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; + }, + } + } + } + + fn call_trace_internal( + mut self, + loader: &Loader, + data_store: &mut TransactionDataCache, + gas_meter: &mut impl GasMeter, + extensions: &mut NativeContextExtensions, + function: Arc, + ty_args: Vec, + args: Vec, + ) -> VMResult { + let mut locals = Locals::new(function.local_count()); + let mut args_1 = vec![]; + let mut call_traces = CallTraces::new(); + for (i, value) in args.into_iter().enumerate() { + locals + .store_loc( + i, + value.copy_value().unwrap(), + loader + .vm_config() + .enable_invariant_violation_check_in_swap_loc, + ) + .map_err(|e| self.set_location(e))?; + args_1.push(value); + } + + let mut current_frame = self + .make_new_frame(loader, function, ty_args, locals) + .map_err(|err| self.set_location(err))?; + call_traces.push(CallTrace { pc: current_frame.pc, module_id: "".to_string(), func_name: current_frame.function.name().to_string(), inputs: args_1, outputs: vec![], type_args: current_frame.ty_args.clone(), + sub_traces: vec![], }).map_err(|_e| { let err = PartialVMError::new(StatusCode::ABORTED); let err = set_err_info!(current_frame, err); @@ -171,13 +343,21 @@ impl Interpreter { .charge_drop_frame(non_ref_vals.iter()) .map_err(|e| self.set_location(e))?; + let mut outputs = vec![]; + for val in self.operand_stack.last_n(current_frame.function.return_type_count()).unwrap() { + outputs.push((*val).copy_value().unwrap()); + } + call_traces.set_outputs(outputs); + if let Some(frame) = self.call_stack.pop() { // Note: the caller will find the callee's return values at the top of the shared operand stack current_frame = frame; current_frame.pc += 1; // advance past the Call instruction in the caller + let top_call = call_traces.pop().unwrap(); + call_traces.push_call_trace(top_call); } else { // end of execution. `self` should no longer be used afterward - return Ok(self.operand_stack.value); + return Ok(call_traces); } }, ExitCode::Call(fh_idx) => { @@ -222,13 +402,14 @@ impl Interpreter { for val in self.operand_stack.last_n(func.arg_count()).unwrap() { inputs.push((*val).copy_value().unwrap()); } - self.call_traces.push(CallTrace { + call_traces.push(CallTrace { pc: 0, module_id: "".to_string(), func_name: func.name().to_string(), inputs, outputs: vec![], type_args: vec![], + sub_traces: vec![], }).map_err(|_e| { let err = PartialVMError::new(StatusCode::ABORTED); let err = set_err_info!(current_frame, err); @@ -284,6 +465,23 @@ impl Interpreter { current_frame.pc += 1; // advance past the Call instruction in the caller continue; } + let mut inputs = vec![]; + for val in self.operand_stack.last_n(func.arg_count()).unwrap() { + inputs.push((*val).copy_value().unwrap()); + } + call_traces.push(CallTrace { + pc: 0, + module_id: "".to_string(), + func_name: func.name().to_string(), + inputs, + outputs: vec![], + type_args: vec![], + sub_traces: vec![], + }).map_err(|_e| { + let err = PartialVMError::new(StatusCode::ABORTED); + let err = set_err_info!(current_frame, err); + self.maybe_core_dump(err, ¤t_frame) + })?; let frame = self .make_call_frame(loader, func, ty_args) .map_err(|e| self.set_location(e)) @@ -1033,27 +1231,6 @@ impl CallStack { } } -struct CallTraces(Vec); - -impl CallTraces { - fn new() -> Self { - CallTraces(vec![]) - } - - fn push(&mut self, trace: CallTrace) -> Result<(), CallTrace> { - if self.0.len() < CALL_STACK_SIZE_LIMIT { - self.0.push(trace); - Ok(()) - } else { - Err(trace) - } - } - - fn pop(&mut self) -> Option { - self.0.pop() - } -} - fn check_depth_of_type(resolver: &Resolver, ty: &Type) -> PartialVMResult<()> { // Start at 1 since we always call this right before we add a new node to the value's depth. let max_depth = match resolver.loader().vm_config().max_value_nest_depth { @@ -1150,15 +1327,6 @@ struct Frame { local_tys: Vec, } -struct CallTrace { - pc: u16, - module_id: String, - func_name: String, - inputs: Vec, - outputs: Vec, - type_args: Vec, -} - /// An `ExitCode` from `execute_code_unit`. #[derive(Debug)] enum ExitCode { diff --git a/third_party/move/move-vm/runtime/src/runtime.rs b/third_party/move/move-vm/runtime/src/runtime.rs index a91f94c1726db..0c8f31fc8b8c9 100644 --- a/third_party/move/move-vm/runtime/src/runtime.rs +++ b/third_party/move/move-vm/runtime/src/runtime.rs @@ -30,6 +30,7 @@ use move_vm_types::{ gas::GasMeter, loaded_data::runtime_types::Type, values::{Locals, Reference, VMValueCast, Value}, + call_trace::CallTraces, }; use std::{borrow::Borrow, collections::BTreeSet, sync::Arc}; @@ -497,6 +498,76 @@ impl VMRuntime { ) } + pub(crate) fn call_trace( + &self, + module: &ModuleId, + function_name: &IdentStr, + ty_args: Vec, + serialized_args: Vec>, + data_store: &mut TransactionDataCache, + gas_meter: &mut impl GasMeter, + extensions: &mut NativeContextExtensions, + bypass_declared_entry_check: bool, + ) -> VMResult { + // load the function + let (module, function, function_instantiation) = + self.loader + .load_function(module, function_name, &ty_args, data_store)?; + + // load the function + let LoadedFunctionInstantiation { + type_arguments, + parameters, + return_, + } = function_instantiation; + + use move_binary_format::{binary_views::BinaryIndexedView, file_format::SignatureIndex}; + fn check_is_entry( + _resolver: &BinaryIndexedView, + is_entry: bool, + _parameters_idx: SignatureIndex, + _return_idx: Option, + ) -> PartialVMResult<()> { + if is_entry { + Ok(()) + } else { + Err(PartialVMError::new( + StatusCode::EXECUTE_ENTRY_FUNCTION_CALLED_ON_NON_ENTRY_FUNCTION, + )) + } + } + let additional_signature_checks = if bypass_declared_entry_check { + move_bytecode_verifier::no_additional_script_signature_checks + } else { + check_is_entry + }; + + script_signature::verify_module_function_signature_by_name( + module.module(), + IdentStr::new(function.as_ref().name()).expect(""), + additional_signature_checks, + )?; + + let arg_types = parameters + .into_iter() + .map(|ty| ty.subst(&type_arguments)) + .collect::>>() + .map_err(|err| err.finish(Location::Undefined))?; + let (mut dummy_locals, deserialized_args) = self + .deserialize_args(arg_types, serialized_args) + .map_err(|e| e.finish(Location::Undefined))?; + + Interpreter::call_trace( + function, + type_arguments, + deserialized_args, + data_store, + gas_meter, + extensions, + &self.loader, + ) + } + pub(crate) fn loader(&self) -> &Loader { &self.loader } diff --git a/third_party/move/move-vm/runtime/src/session.rs b/third_party/move/move-vm/runtime/src/session.rs index 8fbd61eb03780..05c566edd0f43 100644 --- a/third_party/move/move-vm/runtime/src/session.rs +++ b/third_party/move/move-vm/runtime/src/session.rs @@ -23,6 +23,7 @@ use move_vm_types::{ gas::GasMeter, loaded_data::runtime_types::{CachedStructIndex, StructType, Type}, values::{GlobalValue, Value}, + call_trace::CallTraces, }; use std::{borrow::Borrow, sync::Arc}; @@ -112,6 +113,27 @@ impl<'r, 'l> Session<'r, 'l> { ) } + pub fn call_trace( + &mut self, + module: &ModuleId, + function_name: &IdentStr, + ty_args: Vec, + args: Vec>, + gas_meter: &mut impl GasMeter + ) -> VMResult { + let bypass_declared_entry_check = true; + self.move_vm.runtime.call_trace( + module, + function_name, + ty_args, + args, + &mut self.data_cache, + gas_meter, + &mut self.native_extensions, + bypass_declared_entry_check, + ) + } + pub fn execute_instantiated_function( &mut self, func: LoadedFunction, diff --git a/third_party/move/move-vm/types/src/call_trace.rs b/third_party/move/move-vm/types/src/call_trace.rs new file mode 100644 index 0000000000000..4abdd2f913ad6 --- /dev/null +++ b/third_party/move/move-vm/types/src/call_trace.rs @@ -0,0 +1,48 @@ +use crate::loaded_data::runtime_types::Type; +use crate::values::Value; + +const CALL_STACK_SIZE_LIMIT: usize = 1024; +pub struct CallTrace { + pub pc: u16, + pub module_id: String, + pub func_name: String, + pub inputs: Vec, + pub outputs: Vec, + pub type_args: Vec, + pub sub_traces: Vec, +} + +pub struct CallTraces(Vec); + +impl CallTraces { + pub fn new() -> Self { + CallTraces(vec![]) + } + + pub fn push(&mut self, trace: CallTrace) -> Result<(), CallTrace> { + if self.0.len() < CALL_STACK_SIZE_LIMIT { + self.0.push(trace); + Ok(()) + } else { + Err(trace) + } + } + + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + pub fn set_outputs(&mut self, outputs: Vec) { + let length = self.0.len(); + self.0[length - 1].outputs = outputs + } + + pub fn push_call_trace(&mut self, call_trace: CallTrace) { + let length = self.0.len(); + self.0[length - 1].sub_traces.push(call_trace); + } + + pub fn len(&self) -> usize { + self.0.len() + } +} diff --git a/third_party/move/move-vm/types/src/lib.rs b/third_party/move/move-vm/types/src/lib.rs index 4204e4a15b915..8c0f3028c86a2 100644 --- a/third_party/move/move-vm/types/src/lib.rs +++ b/third_party/move/move-vm/types/src/lib.rs @@ -30,3 +30,4 @@ pub mod views; #[cfg(test)] mod unit_tests; +pub mod call_trace;