Rearchitected VM tracing to reflect existing tracing.

Should more or less work now.
This commit is contained in:
Gav Wood 2016-05-28 23:57:16 +02:00
parent d4a06b27ed
commit 86fdcabd0e
10 changed files with 173 additions and 89 deletions

View File

@ -40,9 +40,6 @@ pub enum MessageCallResult {
Failed
}
/// The trace function callback for VM tracing (*not* transaction tracing - that's different).
pub type VMTraceFunctionBox = Box<FnMut(usize, u8, U256, U256) + Send>;
/// Externalities interface for EVMs
pub trait Ext {
/// Returns a value for given key.
@ -109,6 +106,7 @@ pub trait Ext {
/// Increments sstore refunds count by 1.
fn inc_sstore_clears(&mut self);
/// Provide a tracer for VM tracing if the VM implementation supports it.
fn vm_tracer(&mut self) -> Option<&mut VMTraceFunctionBox>;
// TODO work out a way of not having this here but go via .
/// Prepare to trace an operation. Passthrough for the VM trace.
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256, _stack: &[U256]) {}
}

View File

@ -17,6 +17,7 @@
///! Rust VM implementation
use common::*;
use trace::VMTracer;
use super::instructions as instructions;
use super::instructions::Instruction;
use std::marker::Copy;
@ -69,6 +70,8 @@ trait Stack<T> {
fn push(&mut self, elem: T);
/// Get number of elements on Stack
fn size(&self) -> usize;
/// Returns all data on stack.
fn peek_all(&mut self) -> &[T];
}
struct VecStack<S> {
@ -131,6 +134,10 @@ impl<S : fmt::Display> Stack<S> for VecStack<S> {
fn size(&self) -> usize {
self.stack.len()
}
fn peek_all(&mut self) -> &[S] {
&self.stack
}
}
trait Memory {
@ -297,6 +304,10 @@ impl evm::Evm for Interpreter {
// Calculate gas cost
let (gas_cost, mem_size) = try!(self.get_gas_cost_mem(ext, instruction, &mut mem, &stack));
// TODO: make compile-time removable if too much of a performance hit.
ext.trace_prepare_execute(reader.position, instruction, &gas_cost, stack.peek_all());
try!(self.verify_gas(&current_gas, &gas_cost));
mem.expand(mem_size);
current_gas = current_gas - gas_cost; //TODO: use operator -=
@ -311,12 +322,6 @@ impl evm::Evm for Interpreter {
);
});
// Call trace
// TODO: allow to be disabled at build time for max speed
if let Some(ref mut trace_instruction) = ext.vm_tracer() {
(*trace_instruction.deref_mut())(reader.position, instruction, gas_cost, current_gas);
}
// Execute instruction
let result = try!(self.exec_instruction(
current_gas, &params, ext, instruction, &mut reader, &mut mem, &mut stack

View File

@ -16,6 +16,7 @@
//! Just in time compiler execution environment.
use common::*;
use trace::VMTracer;
use evmjit;
use evm;

View File

@ -30,6 +30,6 @@ mod jit;
mod tests;
pub use self::evm::{Evm, Error, Result};
pub use self::ext::{Ext, ContractCreateResult, MessageCallResult, VMTraceFunctionBox};
pub use self::ext::{Ext, ContractCreateResult, MessageCallResult};
pub use self::factory::{Factory, VMType};
pub use self::schedule::Schedule;

View File

@ -21,7 +21,7 @@ use engine::*;
use evm::{self, Ext, Factory};
use externalities::*;
use substate::*;
use trace::{Trace, Tracer, NoopTracer, ExecutiveTracer};
use trace::{Trace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer};
use crossbeam;
pub use types::executed::{Executed, ExecutionResult};
@ -82,21 +82,40 @@ impl<'a> Executive<'a> {
}
/// Creates `Externalities` from `Executive`.
pub fn as_externalities<'_, T>(&'_ mut self, origin_info: OriginInfo, substate: &'_ mut Substate, output: OutputPolicy<'_, '_>, tracer: &'_ mut T) -> Externalities<'_, T> where T: Tracer {
Externalities::new(self.state, self.info, self.engine, self.vm_factory, self.depth, origin_info, substate, output, tracer)
pub fn as_externalities<'_, T, V>(
&'_ mut self,
origin_info: OriginInfo,
substate: &'_ mut Substate,
output: OutputPolicy<'_, '_>,
tracer: &'_ mut T,
vm_tracer: &'_ mut V
) -> Externalities<'_, T, V> where T: Tracer, V: VMTracer {
Externalities::new(self.state, self.info, self.engine, self.vm_factory, self.depth, origin_info, substate, output, tracer, vm_tracer)
}
/// This function should be used to execute transaction.
pub fn transact(&'a mut self, t: &SignedTransaction, options: TransactOptions) -> Result<Executed, ExecutionError> {
let check = options.check_nonce;
match options.tracing {
true => self.transact_with_tracer(t, check, ExecutiveTracer::default()),
false => self.transact_with_tracer(t, check, NoopTracer),
true => match options.vm_tracing {
true => self.transact_with_tracer(t, check, ExecutiveTracer::default(), ExecutiveVMTracer::default()),
false => self.transact_with_tracer(t, check, ExecutiveTracer::default(), NoopVMTracer),
},
false => match options.vm_tracing {
true => self.transact_with_tracer(t, check, NoopTracer, ExecutiveVMTracer::default()),
false => self.transact_with_tracer(t, check, NoopTracer, NoopVMTracer),
},
}
}
/// Execute transaction/call with tracing enabled
pub fn transact_with_tracer<T>(&'a mut self, t: &SignedTransaction, check_nonce: bool, mut tracer: T) -> Result<Executed, ExecutionError> where T: Tracer {
pub fn transact_with_tracer<T, V>(
&'a mut self,
t: &SignedTransaction,
check_nonce: bool,
mut tracer: T,
mut vm_tracer: V
) -> Result<Executed, ExecutionError> where T: Tracer, V: VMTracer {
let sender = try!(t.sender().map_err(|e| {
let message = format!("Transaction malformed: {:?}", e);
ExecutionError::TransactionMalformed(message)
@ -156,7 +175,7 @@ impl<'a> Executive<'a> {
code: Some(t.data.clone()),
data: None,
};
(self.create(params, &mut substate, &mut tracer), vec![])
(self.create(params, &mut substate, &mut tracer, &mut vm_tracer), vec![])
},
Action::Call(ref address) => {
let params = ActionParams {
@ -172,20 +191,26 @@ impl<'a> Executive<'a> {
};
// TODO: move output upstream
let mut out = vec![];
(self.call(params, &mut substate, BytesRef::Flexible(&mut out), &mut tracer), out)
(self.call(params, &mut substate, BytesRef::Flexible(&mut out), &mut tracer, &mut vm_tracer), out)
}
};
// finalize here!
Ok(try!(self.finalize(t, substate, gas_left, output, tracer.traces().pop())))
Ok(try!(self.finalize(t, substate, gas_left, output, tracer.traces().pop(), vm_tracer.drain())))
}
fn exec_vm<T>(&mut self, params: ActionParams, unconfirmed_substate: &mut Substate, output_policy: OutputPolicy, tracer: &mut T)
-> evm::Result where T: Tracer {
fn exec_vm<T, V>(
&mut self,
params: ActionParams,
unconfirmed_substate: &mut Substate,
output_policy: OutputPolicy,
tracer: &mut T,
vm_tracer: &mut V
) -> evm::Result where T: Tracer, V: VMTracer {
// Ordinary execution - keep VM in same thread
if (self.depth + 1) % MAX_VM_DEPTH_FOR_THREAD != 0 {
let vm_factory = self.vm_factory;
let mut ext = self.as_externalities(OriginInfo::from(&params), unconfirmed_substate, output_policy, tracer);
let mut ext = self.as_externalities(OriginInfo::from(&params), unconfirmed_substate, output_policy, tracer, vm_tracer);
trace!(target: "executive", "ext.schedule.have_delegate_call: {}", ext.schedule().have_delegate_call);
return vm_factory.create().exec(params, &mut ext);
}
@ -195,7 +220,7 @@ impl<'a> Executive<'a> {
// https://github.com/aturon/crossbeam/issues/16
crossbeam::scope(|scope| {
let vm_factory = self.vm_factory;
let mut ext = self.as_externalities(OriginInfo::from(&params), unconfirmed_substate, output_policy, tracer);
let mut ext = self.as_externalities(OriginInfo::from(&params), unconfirmed_substate, output_policy, tracer, vm_tracer);
scope.spawn(move || {
vm_factory.create().exec(params, &mut ext)
@ -207,8 +232,14 @@ impl<'a> Executive<'a> {
/// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides).
/// Modifies the substate and the output.
/// Returns either gas_left or `evm::Error`.
pub fn call<T>(&mut self, params: ActionParams, substate: &mut Substate, mut output: BytesRef, tracer: &mut T)
-> evm::Result where T: Tracer {
pub fn call<T, V>(
&mut self,
params: ActionParams,
substate: &mut Substate,
mut output: BytesRef,
tracer: &mut T,
vm_tracer: &mut V
) -> evm::Result where T: Tracer, V: VMTracer {
// backup used in case of running out of gas
self.state.snapshot();
@ -266,16 +297,22 @@ impl<'a> Executive<'a> {
let trace_info = tracer.prepare_trace_call(&params);
let mut trace_output = tracer.prepare_trace_output();
let mut subtracer = tracer.subtracer();
let gas = params.gas;
if params.code.is_some() {
// part of substate that may be reverted
let mut unconfirmed_substate = Substate::new();
// TODO: make ActionParams pass by ref then avoid copy altogether.
let mut subvmtracer = vm_tracer.prepare_subtrace(params.code.as_ref().expect("scope is protected by params.code.is_some condition"));
let res = {
self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut()), &mut subtracer)
self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut()), &mut subtracer, &mut subvmtracer)
};
vm_tracer.done_subtrace(subvmtracer);
trace!(target: "executive", "res={:?}", res);
let traces = subtracer.traces();
@ -309,8 +346,13 @@ impl<'a> Executive<'a> {
/// Creates contract with given contract params.
/// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides).
/// Modifies the substate.
pub fn create<T>(&mut self, params: ActionParams, substate: &mut Substate, tracer: &mut T) -> evm::Result where T:
Tracer {
pub fn create<T, V>(
&mut self,
params: ActionParams,
substate: &mut Substate,
tracer: &mut T,
vm_tracer: &mut V
) -> evm::Result where T: Tracer, V: VMTracer {
// backup used in case of running out of gas
self.state.snapshot();
@ -332,10 +374,14 @@ impl<'a> Executive<'a> {
let gas = params.gas;
let created = params.address.clone();
let mut subvmtracer = vm_tracer.prepare_subtrace(&params.code.as_ref().expect("two ways into create (Externalities::create and Executive::transact_with_tracer); both place `Some(...)` `code` in `params`; qed"));
let res = {
self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::InitContract(trace_output.as_mut()), &mut subtracer)
self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::InitContract(trace_output.as_mut()), &mut subtracer, &mut subvmtracer)
};
vm_tracer.done_subtrace(subvmtracer);
match res {
Ok(gas_left) => tracer.trace_create(
trace_info,
@ -353,7 +399,15 @@ impl<'a> Executive<'a> {
}
/// Finalizes the transaction (does refunds and suicides).
fn finalize(&mut self, t: &SignedTransaction, substate: Substate, result: evm::Result, output: Bytes, trace: Option<Trace>) -> ExecutionResult {
fn finalize(
&mut self,
t: &SignedTransaction,
substate: Substate,
result: evm::Result,
output: Bytes,
trace: Option<Trace>,
vm_trace: Option<VMTrace>
) -> ExecutionResult {
let schedule = self.engine.schedule(self.info);
// refunds from SSTORE nonzero -> zero
@ -396,7 +450,7 @@ impl<'a> Executive<'a> {
contracts_created: vec![],
output: output,
trace: trace,
vm_trace: None,
vm_trace: vm_trace,
})
},
_ => {
@ -409,7 +463,7 @@ impl<'a> Executive<'a> {
contracts_created: substate.contracts_created,
output: output,
trace: trace,
vm_trace: None,
vm_trace: vm_trace,
})
},
}

View File

@ -19,9 +19,9 @@ use common::*;
use state::*;
use engine::*;
use executive::*;
use evm::{self, Schedule, Ext, ContractCreateResult, MessageCallResult, Factory, VMTraceFunctionBox};
use evm::{self, Schedule, Ext, ContractCreateResult, MessageCallResult, Factory};
use substate::*;
use trace::Tracer;
use trace::{Tracer, VMTracer};
/// Policy for handling output data on `RETURN` opcode.
pub enum OutputPolicy<'a, 'b> {
@ -55,7 +55,7 @@ impl OriginInfo {
}
/// Implementation of evm Externalities.
pub struct Externalities<'a, T> where T: 'a + Tracer {
pub struct Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer {
state: &'a mut State,
env_info: &'a EnvInfo,
engine: &'a Engine,
@ -66,10 +66,10 @@ pub struct Externalities<'a, T> where T: 'a + Tracer {
schedule: Schedule,
output: OutputPolicy<'a, 'a>,
tracer: &'a mut T,
vm_tracer: Option<VMTraceFunctionBox>,
vm_tracer: &'a mut V,
}
impl<'a, T> Externalities<'a, T> where T: 'a + Tracer {
impl<'a, T, V> Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer {
#[cfg_attr(feature="dev", allow(too_many_arguments))]
/// Basic `Externalities` constructor.
pub fn new(state: &'a mut State,
@ -81,6 +81,7 @@ impl<'a, T> Externalities<'a, T> where T: 'a + Tracer {
substate: &'a mut Substate,
output: OutputPolicy<'a, 'a>,
tracer: &'a mut T,
vm_tracer: &'a mut V,
) -> Self {
Externalities {
state: state,
@ -93,40 +94,12 @@ impl<'a, T> Externalities<'a, T> where T: 'a + Tracer {
schedule: engine.schedule(env_info),
output: output,
tracer: tracer,
vm_tracer: None,
}
}
#[cfg_attr(feature="dev", allow(too_many_arguments))]
/// Basic `Externalities` constructor.
pub fn with_vm_tracer(state: &'a mut State,
env_info: &'a EnvInfo,
engine: &'a Engine,
vm_factory: &'a Factory,
depth: usize,
origin_info: OriginInfo,
substate: &'a mut Substate,
output: OutputPolicy<'a, 'a>,
tracer: &'a mut T,
vm_tracer: VMTraceFunctionBox,
) -> Self {
Externalities {
state: state,
env_info: env_info,
engine: engine,
vm_factory: vm_factory,
depth: depth,
origin_info: origin_info,
substate: substate,
schedule: engine.schedule(env_info),
output: output,
tracer: tracer,
vm_tracer: Some(vm_tracer),
vm_tracer: vm_tracer,
}
}
}
impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer {
impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer {
fn storage_at(&self, key: &H256) -> H256 {
self.state.storage_at(&self.origin_info.address, key)
}
@ -181,7 +154,7 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer {
let mut ex = Executive::from_parent(self.state, self.env_info, self.engine, self.vm_factory, self.depth);
// TODO: handle internal error separately
match ex.create(params, self.substate, self.tracer) {
match ex.create(params, self.substate, self.tracer, self.vm_tracer) {
Ok(gas_left) => {
self.substate.contracts_created.push(address.clone());
ContractCreateResult::Created(address, gas_left)
@ -219,7 +192,7 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer {
let mut ex = Executive::from_parent(self.state, self.env_info, self.engine, self.vm_factory, self.depth);
match ex.call(params, self.substate, BytesRef::Fixed(output), self.tracer) {
match ex.call(params, self.substate, BytesRef::Fixed(output), self.tracer, self.vm_tracer) {
Ok(gas_left) => MessageCallResult::Success(gas_left),
_ => MessageCallResult::Failed
}
@ -316,7 +289,9 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer {
self.substate.sstore_clears_count = self.substate.sstore_clears_count + U256::one();
}
fn vm_tracer(&mut self) -> Option<&mut VMTraceFunctionBox> { self.vm_tracer.as_mut() }
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256, stack: &[U256]) {
self.vm_tracer.trace_prepare_execute(pc, instruction, gas_cost, stack);
}
}
#[cfg(test)]

View File

@ -18,13 +18,13 @@
use util::{Bytes, Address, U256};
use action_params::ActionParams;
use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult};
use trace::Tracer;
use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation};
use trace::{Tracer, VMTracer};
/// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls.
#[derive(Default)]
pub struct ExecutiveTracer {
traces: Vec<Trace>
traces: Vec<Trace>,
}
impl Tracer for ExecutiveTracer {
@ -105,3 +105,35 @@ impl Tracer for ExecutiveTracer {
self.traces
}
}
/// Simple VM tracer. Traces all operations. Ignores delegatecalls.
#[derive(Default)]
pub struct ExecutiveVMTracer {
data: VMTrace,
}
impl VMTracer for ExecutiveVMTracer {
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256, stack: &[U256]) {
self.data.operations.push(VMOperation {
pc: pc,
instruction: instruction,
gas_cost: gas_cost.clone(),
stack: stack.iter().cloned().collect(),
})
}
fn prepare_subtrace(&self, code: &Bytes) -> Self {
ExecutiveVMTracer { data: VMTrace {
parent_step: self.data.operations.len(),
code: code.clone(),
operations: vec![],
subs: vec![],
}}
}
fn done_subtrace(&mut self, sub: Self) {
self.data.subs.push(sub.data);
}
fn drain(self) -> Option<VMTrace> { Some(self.data) }
}

View File

@ -32,8 +32,8 @@ pub use self::config::{Config, Switch};
pub use self::db::TraceDB;
pub use self::error::Error;
pub use types::trace_types::trace::{Trace, VMTrace};
pub use self::noop_tracer::NoopTracer;
pub use self::executive_tracer::ExecutiveTracer;
pub use self::noop_tracer::{NoopTracer, NoopVMTracer};
pub use self::executive_tracer::{ExecutiveTracer, ExecutiveVMTracer};
pub use types::trace_types::filter::{Filter, AddressesFilter};
pub use self::import::ImportRequest;
pub use self::localized::LocalizedTrace;
@ -91,13 +91,16 @@ pub trait Tracer: Send {
/// Used by executive to build VM traces.
pub trait VMTracer: Send {
/// Trace the preparation to execute a single instruction.
fn trace_prepare_execute(pc: usize, instruction: u8, gas_cost: &U256, stack: &Vec<U256>);
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256, stack: &[U256]);
/// Spawn subtracer which will be used to trace deeper levels of execution.
fn subtracer(&self) -> Self where Self: Sized;
fn prepare_subtrace(&self, code: &Bytes) -> Self where Self: Sized;
/// Consumes self and returns all VM traces.
fn traces(self) -> Vec<VMTrace>;
/// Spawn subtracer which will be used to trace deeper levels of execution.
fn done_subtrace(&mut self, sub: Self) where Self: Sized;
/// Consumes self and returns the VM trace.
fn drain(self) -> Option<VMTrace>;
}
/// `DbExtras` provides an interface to query extra data which is not stored in tracesdb,

View File

@ -18,8 +18,8 @@
use util::{Bytes, Address, U256};
use action_params::ActionParams;
use trace::Tracer;
use trace::trace::{Trace, Call, Create};
use trace::{Tracer, VMTracer};
use trace::trace::{Trace, Call, Create, VMTrace};
/// Nonoperative tracer. Does not trace anything.
pub struct NoopTracer;
@ -63,3 +63,20 @@ impl Tracer for NoopTracer {
vec![]
}
}
/// Nonoperative VM tracer. Does not trace anything.
pub struct NoopVMTracer;
impl VMTracer for NoopVMTracer {
/// Trace the preparation to execute a single instruction.
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256, _stack: &[U256]) {}
/// Spawn subtracer which will be used to trace deeper levels of execution.
fn prepare_subtrace(&self, _code: &Bytes) -> Self { NoopVMTracer }
/// Spawn subtracer which will be used to trace deeper levels of execution.
fn done_subtrace(&mut self, _sub: Self) {}
/// Consumes self and returns all VM traces.
fn drain(self) -> Option<VMTrace> { None }
}

View File

@ -397,12 +397,11 @@ impl Decodable for VMOperation {
}
}
#[derive(Debug, Clone, PartialEq, Binary)]
#[derive(Debug, Clone, PartialEq, Binary, Default)]
/// A record of a full VM trace for a CALL/CREATE.
pub struct VMTrace {
/// The number of EVM execution environments active when this action happened; 0 if it's
/// the outer action of the transaction.
pub depth: usize,
/// The step (i.e. index into operations) at which this trace corresponds.
pub parent_step: usize,
/// The code to be executed.
pub code: Bytes,
/// The operations executed.
@ -415,7 +414,7 @@ pub struct VMTrace {
impl Encodable for VMTrace {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(4);
s.append(&self.depth);
s.append(&self.parent_step);
s.append(&self.code);
s.append(&self.operations);
s.append(&self.subs);
@ -426,7 +425,7 @@ impl Decodable for VMTrace {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let d = decoder.as_rlp();
let res = VMTrace {
depth: try!(d.val_at(0)),
parent_step: try!(d.val_at(0)),
code: try!(d.val_at(1)),
operations: try!(d.val_at(2)),
subs: try!(d.val_at(3)),