Added VM trace information for post-execution stuff.
This commit is contained in:
parent
11f4e8cb73
commit
79503e4f14
@ -106,7 +106,9 @@ pub trait Ext {
|
||||
/// Increments sstore refunds count by 1.
|
||||
fn inc_sstore_clears(&mut self);
|
||||
|
||||
// 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]) {}
|
||||
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false }
|
||||
|
||||
/// Trace the finalised execution of a single instruction.
|
||||
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
use common::*;
|
||||
use trace::VMTracer;
|
||||
use super::instructions as instructions;
|
||||
use super::instructions::Instruction;
|
||||
use super::instructions::{Instruction, get_info};
|
||||
use std::marker::Copy;
|
||||
use evm::{self, MessageCallResult, ContractCreateResult};
|
||||
|
||||
@ -71,7 +71,7 @@ trait Stack<T> {
|
||||
/// Get number of elements on Stack
|
||||
fn size(&self) -> usize;
|
||||
/// Returns all data on stack.
|
||||
fn peek_all(&mut self) -> &[T];
|
||||
fn peek_top(&mut self, no_of_elems: usize) -> &[T];
|
||||
}
|
||||
|
||||
struct VecStack<S> {
|
||||
@ -135,8 +135,9 @@ impl<S : fmt::Display> Stack<S> for VecStack<S> {
|
||||
self.stack.len()
|
||||
}
|
||||
|
||||
fn peek_all(&mut self) -> &[S] {
|
||||
&self.stack
|
||||
fn peek_top(&mut self, no_from_top: usize) -> &[S] {
|
||||
assert!(self.stack.len() >= no_from_top, "peek_top asked for more items than exist.");
|
||||
&self.stack[self.stack.len() - no_from_top .. self.stack.len()]
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,7 +307,7 @@ impl evm::Evm for Interpreter {
|
||||
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());
|
||||
let trace_executed = ext.trace_prepare_execute(reader.position, instruction, &gas_cost);
|
||||
|
||||
try!(self.verify_gas(¤t_gas, &gas_cost));
|
||||
mem.expand(mem_size);
|
||||
@ -322,10 +323,19 @@ impl evm::Evm for Interpreter {
|
||||
);
|
||||
});
|
||||
|
||||
let (mem_written, store_written) = match trace_executed {
|
||||
true => (Self::mem_written(instruction, &stack), Self::store_written(instruction, &stack)),
|
||||
false => (None, None),
|
||||
};
|
||||
|
||||
// Execute instruction
|
||||
let result = try!(self.exec_instruction(
|
||||
current_gas, ¶ms, ext, instruction, &mut reader, &mut mem, &mut stack
|
||||
));
|
||||
));
|
||||
|
||||
if trace_executed {
|
||||
ext.trace_executed(current_gas, stack.peek_top(get_info(instruction).ret), mem_written.map(|(o, s)| (o, &(mem[o..(o + s)]))), store_written);
|
||||
}
|
||||
|
||||
// Advance
|
||||
match result {
|
||||
@ -496,6 +506,31 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
fn mem_written(
|
||||
instruction: Instruction,
|
||||
stack: &Stack<U256>
|
||||
) -> Option<(usize, usize)> {
|
||||
match instruction {
|
||||
instructions::MSTORE | instructions::MLOAD => Some((stack.peek(0).low_u64() as usize, 32)),
|
||||
instructions::MSTORE8 => Some((stack.peek(0).low_u64() as usize, 1)),
|
||||
instructions::CALLDATACOPY | instructions::CODECOPY => Some((stack.peek(0).low_u64() as usize, stack.peek(2).low_u64() as usize)),
|
||||
instructions::EXTCODECOPY => Some((stack.peek(1).low_u64() as usize, stack.peek(3).low_u64() as usize)),
|
||||
instructions::CALL | instructions::CALLCODE => Some((stack.peek(5).low_u64() as usize, stack.peek(6).low_u64() as usize)),
|
||||
instructions::DELEGATECALL => Some((stack.peek(4).low_u64() as usize, stack.peek(5).low_u64() as usize)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn store_written(
|
||||
instruction: Instruction,
|
||||
stack: &Stack<U256>
|
||||
) -> Option<(U256, U256)> {
|
||||
match instruction {
|
||||
instructions::SSTORE => Some((stack.peek(0).clone(), stack.peek(1).clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &U256) -> Result<(U256, usize), evm::Error> {
|
||||
let gas_for_mem = |mem_size: U256| {
|
||||
let s = mem_size >> 5;
|
||||
|
@ -289,8 +289,12 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT
|
||||
self.substate.sstore_clears_count = self.substate.sstore_clears_count + U256::one();
|
||||
}
|
||||
|
||||
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);
|
||||
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool {
|
||||
self.vm_tracer.trace_prepare_execute(pc, instruction, gas_cost)
|
||||
}
|
||||
|
||||
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) {
|
||||
self.vm_tracer.trace_executed(gas_used, stack_push, mem_diff, store_diff)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
use util::{Bytes, Address, U256};
|
||||
use action_params::ActionParams;
|
||||
use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation};
|
||||
use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff};
|
||||
use trace::{Tracer, VMTracer};
|
||||
|
||||
/// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls.
|
||||
@ -113,13 +113,24 @@ pub struct ExecutiveVMTracer {
|
||||
}
|
||||
|
||||
impl VMTracer for ExecutiveVMTracer {
|
||||
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256, stack: &[U256]) {
|
||||
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool {
|
||||
self.data.operations.push(VMOperation {
|
||||
pc: pc,
|
||||
instruction: instruction,
|
||||
gas_cost: gas_cost.clone(),
|
||||
stack: stack.iter().cloned().collect(),
|
||||
})
|
||||
executed: None,
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) {
|
||||
let ex = VMExecutedOperation {
|
||||
gas_used: gas_used,
|
||||
stack_push: stack_push.iter().cloned().collect(),
|
||||
mem_diff: mem_diff.map(|(s, r)| MemoryDiff{ offset: s, data: r.iter().cloned().collect() }),
|
||||
store_diff: store_diff.map(|(l, v)| StorageDiff{ location: l, value: v }),
|
||||
};
|
||||
self.data.operations.last_mut().expect("trace_executed is always called after a trace_prepare_execute").executed = Some(ex);
|
||||
}
|
||||
|
||||
fn prepare_subtrace(&self, code: &Bytes) -> Self {
|
||||
|
@ -91,7 +91,11 @@ 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(&mut self, pc: usize, instruction: u8, gas_cost: &U256, stack: &[U256]);
|
||||
/// @returns true if `trace_executed` should be called.
|
||||
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false }
|
||||
|
||||
/// Trace the finalised execution of a single instruction.
|
||||
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {}
|
||||
|
||||
/// Spawn subtracer which will be used to trace deeper levels of execution.
|
||||
fn prepare_subtrace(&self, code: &Bytes) -> Self where Self: Sized;
|
||||
|
@ -69,7 +69,10 @@ 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]) {}
|
||||
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false }
|
||||
|
||||
/// Trace the finalised execution of a single instruction.
|
||||
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {}
|
||||
|
||||
/// Spawn subtracer which will be used to trace deeper levels of execution.
|
||||
fn prepare_subtrace(&self, _code: &Bytes) -> Self { NoopVMTracer }
|
||||
|
@ -349,16 +349,94 @@ impl Trace {
|
||||
}
|
||||
}
|
||||
|
||||
/*pub struct VMExecutedOperation {
|
||||
#[derive(Debug, Clone, PartialEq, Binary)]
|
||||
/// A diff of some chunk of memory.
|
||||
pub struct MemoryDiff {
|
||||
/// Offset into memory the change begins.
|
||||
pub offset: usize,
|
||||
/// The changed data.
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
impl Encodable for MemoryDiff {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
s.begin_list(2);
|
||||
s.append(&self.offset);
|
||||
s.append(&self.data);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for MemoryDiff {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
let d = decoder.as_rlp();
|
||||
Ok(MemoryDiff {
|
||||
offset: try!(d.val_at(0)),
|
||||
data: try!(d.val_at(1)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Binary)]
|
||||
/// A diff of some storage value.
|
||||
pub struct StorageDiff {
|
||||
/// Which key in storage is changed.
|
||||
pub location: U256,
|
||||
/// What the value has been changed to.
|
||||
pub value: U256,
|
||||
}
|
||||
|
||||
impl Encodable for StorageDiff {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
s.begin_list(2);
|
||||
s.append(&self.location);
|
||||
s.append(&self.value);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for StorageDiff {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
let d = decoder.as_rlp();
|
||||
Ok(StorageDiff {
|
||||
location: try!(d.val_at(0)),
|
||||
value: try!(d.val_at(1)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Binary)]
|
||||
/// A record of an executed VM operation.
|
||||
pub struct VMExecutedOperation {
|
||||
/// The total gas used.
|
||||
pub gas_used: U256,
|
||||
/// Altered storage value.
|
||||
pub storage_diff: Option<(U256, U256)>,
|
||||
/// If altered, the new memory image.
|
||||
pub new_memory: Option<Bytes>,
|
||||
}*/
|
||||
/// Information concerning the execution of the operation.
|
||||
// pub executed: Option<VMExecutedOperation>,
|
||||
/// The stack item placed, if any.
|
||||
pub stack_push: Vec<U256>,
|
||||
/// If altered, the memory delta, given as (offset, new bytes).
|
||||
pub mem_diff: Option<MemoryDiff>,
|
||||
/// The altered storage value.
|
||||
pub store_diff: Option<StorageDiff>,
|
||||
}
|
||||
|
||||
impl Encodable for VMExecutedOperation {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
s.begin_list(4);
|
||||
s.append(&self.gas_used);
|
||||
s.append(&self.stack_push);
|
||||
s.append(&self.mem_diff);
|
||||
s.append(&self.store_diff);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for VMExecutedOperation {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
let d = decoder.as_rlp();
|
||||
Ok(VMExecutedOperation {
|
||||
gas_used: try!(d.val_at(0)),
|
||||
stack_push: try!(d.val_at(1)),
|
||||
mem_diff: try!(d.val_at(2)),
|
||||
store_diff: try!(d.val_at(3)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Binary)]
|
||||
/// A record of the execution of a single VM operation.
|
||||
@ -369,8 +447,8 @@ pub struct VMOperation {
|
||||
pub instruction: u8,
|
||||
/// The gas cost for this instruction.
|
||||
pub gas_cost: U256,
|
||||
/// The stack.
|
||||
pub stack: Vec<U256>,
|
||||
/// Information concerning the execution of the operation.
|
||||
pub executed: Option<VMExecutedOperation>,
|
||||
}
|
||||
|
||||
impl Encodable for VMOperation {
|
||||
@ -379,7 +457,7 @@ impl Encodable for VMOperation {
|
||||
s.append(&self.pc);
|
||||
s.append(&self.instruction);
|
||||
s.append(&self.gas_cost);
|
||||
s.append(&self.stack);
|
||||
s.append(&self.executed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,7 +468,7 @@ impl Decodable for VMOperation {
|
||||
pc: try!(d.val_at(0)),
|
||||
instruction: try!(d.val_at(1)),
|
||||
gas_cost: try!(d.val_at(2)),
|
||||
stack: try!(d.val_at(3)),
|
||||
executed: try!(d.val_at(3)),
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
|
@ -80,9 +80,30 @@ fn vm_trace_to_object(t: &VMTrace) -> Value {
|
||||
.map(|(i, op)| {
|
||||
let mut m = map![
|
||||
"pc".to_owned() => to_value(&op.pc).unwrap(),
|
||||
"cost".to_owned() => to_value(&op.gas_cost).unwrap(),
|
||||
"stack".to_owned() => to_value(&op.stack).unwrap()
|
||||
"cost".to_owned() => match op.gas_cost <= U256::from(!0u64) {
|
||||
true => to_value(&op.gas_cost.low_u64()),
|
||||
false => to_value(&op.gas_cost),
|
||||
}.unwrap()
|
||||
];
|
||||
if let Some(ref ex) = op.executed {
|
||||
let mut em = map![
|
||||
"used".to_owned() => to_value(&ex.gas_used.low_u64()).unwrap(),
|
||||
"push".to_owned() => to_value(&ex.stack_push).unwrap()
|
||||
];
|
||||
if let Some(ref md) = ex.mem_diff {
|
||||
em.insert("mem".to_owned(), Value::Object(map![
|
||||
"off".to_owned() => to_value(&md.offset).unwrap(),
|
||||
"data".to_owned() => to_value(&md.data).unwrap()
|
||||
]));
|
||||
}
|
||||
if let Some(ref sd) = ex.store_diff {
|
||||
em.insert("store".to_owned(), Value::Object(map![
|
||||
"key".to_owned() => to_value(&sd.location).unwrap(),
|
||||
"val".to_owned() => to_value(&sd.value).unwrap()
|
||||
]));
|
||||
}
|
||||
m.insert("executed".to_owned(), Value::Object(em));
|
||||
}
|
||||
if next_sub.is_some() && next_sub.unwrap().parent_step == i {
|
||||
m.insert("sub".to_owned(), vm_trace_to_object(next_sub.unwrap()));
|
||||
next_sub = subs.next();
|
||||
@ -191,7 +212,6 @@ impl<C, M> Ethcore for EthcoreClient<C, M> where C: BlockChainClient + 'static,
|
||||
.and_then(|(request,)| {
|
||||
let signed = try!(self.sign_call(request));
|
||||
let r = take_weak!(self.client).call(&signed, true);
|
||||
trace!(target: "jsonrpc", "returned {:?}", r);
|
||||
if let Ok(executed) = r {
|
||||
if let Some(vm_trace) = executed.vm_trace {
|
||||
return Ok(vm_trace_to_object(&vm_trace));
|
||||
|
@ -338,6 +338,18 @@ impl<T> Encodable for Vec<T> where T: Encodable {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Encodable for Option<T> where T: Encodable {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
match *self {
|
||||
None => { s.begin_list(0); },
|
||||
Some(ref x) => {
|
||||
s.begin_list(1);
|
||||
s.append_internal(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RlpEncodable for T where T: Encodable {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
Encodable::rlp_append(self, s)
|
||||
|
@ -395,7 +395,7 @@ impl<'a> Decoder for BasicDecoder<'a> {
|
||||
}
|
||||
|
||||
impl<T> Decodable for T where T: FromBytes {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
decoder.read_value(| bytes | {
|
||||
Ok(try!(T::from_bytes(bytes)))
|
||||
})
|
||||
@ -403,13 +403,19 @@ impl<T> Decodable for T where T: FromBytes {
|
||||
}
|
||||
|
||||
impl<T> Decodable for Vec<T> where T: Decodable {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
decoder.as_rlp().iter().map(|d| T::decode(&BasicDecoder::new(d))).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Decodable for Option<T> where T: Decodable {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
decoder.as_rlp().iter().map(|d| T::decode(&BasicDecoder::new(d))).collect::<Result<Vec<_>, DecoderError>>().map(|mut a| a.pop())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Vec<u8> {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
decoder.read_value(| bytes | {
|
||||
let mut res = vec![];
|
||||
res.extend_from_slice(bytes);
|
||||
@ -418,22 +424,10 @@ impl Decodable for Vec<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Decodable for Option<T> where T: Decodable {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
decoder.read_value(| bytes | {
|
||||
let res = match bytes.len() {
|
||||
0 => None,
|
||||
_ => Some(try!(T::decode(decoder)))
|
||||
};
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_array_decodable {
|
||||
($index_type:ty, $len:expr ) => (
|
||||
impl<T> Decodable for [T; $len] where T: Decodable {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
let decoders = decoder.as_rlp();
|
||||
|
||||
let mut result: [T; $len] = unsafe { ::std::mem::uninitialized() };
|
||||
@ -466,7 +460,7 @@ impl_array_decodable_recursive!(
|
||||
);
|
||||
|
||||
impl<T> RlpDecodable for T where T: Decodable {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
Decodable::decode(decoder)
|
||||
}
|
||||
}
|
||||
@ -489,7 +483,7 @@ impl FromBytes for DecodableU8 {
|
||||
}
|
||||
|
||||
impl RlpDecodable for u8 {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
let u: DecodableU8 = try!(Decodable::decode(decoder));
|
||||
Ok(u.0)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user