Added VM trace information for post-execution stuff.

This commit is contained in:
Gav Wood 2016-05-30 17:19:15 +02:00
parent 11f4e8cb73
commit 79503e4f14
10 changed files with 212 additions and 49 deletions

View File

@ -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)>) {}
}

View File

@ -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(&current_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, &params, 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;

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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;

View File

@ -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 }

View File

@ -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)

View File

@ -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));

View File

@ -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)

View File

@ -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)
}