diff --git a/libaleth-interpreter/VM.cpp b/libaleth-interpreter/VM.cpp index ca618702eed..bd225ddef9c 100644 --- a/libaleth-interpreter/VM.cpp +++ b/libaleth-interpreter/VM.cpp @@ -22,10 +22,6 @@ namespace { -void destroy(evmc_instance* _instance) -{ - (void)_instance; -} evmc_capabilities_flagset getCapabilities(evmc_instance* _instance) noexcept { @@ -38,99 +34,142 @@ void delete_output(const evmc_result* result) delete[] result->output_data; } -evmc_result execute(evmc_instance* _instance, evmc_context* _context, evmc_revision _rev, - const evmc_message* _msg, uint8_t const* _code, size_t _codeSize) noexcept +class InterpreterEvmcInstance : public evmc_instance { - (void)_instance; - std::unique_ptr vm{new dev::eth::VM}; +public: + static InterpreterEvmcInstance* create() { return new InterpreterEvmcInstance{}; } - evmc_result result = {}; - dev::eth::owning_bytes_ref output; +private: + InterpreterEvmcInstance() + : evmc_instance{ + EVMC_ABI_VERSION, "interpreter", aleth_get_buildinfo()->project_version, destroy, + execute, getCapabilities, setTracer, + nullptr, // set_option + } + {} - try - { - output = vm->exec(_context, _rev, _msg, _code, _codeSize); - result.status_code = EVMC_SUCCESS; - result.gas_left = vm->m_io_gas; - } - catch (dev::eth::RevertInstruction& ex) - { - result.status_code = EVMC_REVERT; - result.gas_left = vm->m_io_gas; - output = ex.output(); // This moves the output from the exception! - } - catch (dev::eth::BadInstruction const&) + static void destroy(evmc_instance* _instance) { - result.status_code = EVMC_UNDEFINED_INSTRUCTION; + delete static_cast(_instance); } - catch (dev::eth::OutOfStack const&) - { - result.status_code = EVMC_STACK_OVERFLOW; - } - catch (dev::eth::StackUnderflow const&) - { - result.status_code = EVMC_STACK_UNDERFLOW; - } - catch (dev::eth::BufferOverrun const&) - { - result.status_code = EVMC_INVALID_MEMORY_ACCESS; - } - catch (dev::eth::OutOfGas const&) - { - result.status_code = EVMC_OUT_OF_GAS; - } - catch (dev::eth::BadJumpDestination const&) - { - result.status_code = EVMC_BAD_JUMP_DESTINATION; - } - catch (dev::eth::DisallowedStateChange const&) - { - result.status_code = EVMC_STATIC_MODE_VIOLATION; - } - catch (dev::eth::VMException const&) - { - result.status_code = EVMC_FAILURE; - } - catch (...) + + static evmc_result execute(evmc_instance* _instance, evmc_context* _context, evmc_revision _rev, + evmc_message const* _msg, uint8_t const* _code, size_t _codeSize) noexcept { - result.status_code = EVMC_INTERNAL_ERROR; + std::unique_ptr vm{new dev::eth::VM}; + + evmc_result result = {}; + dev::eth::owning_bytes_ref output; + + auto evmc = static_cast(_instance); + try + { + output = vm->exec(_context, _rev, _msg, _code, _codeSize, evmc->m_traceCallback, + evmc->m_traceContext); + result.status_code = EVMC_SUCCESS; + result.gas_left = vm->m_io_gas; + } + catch (dev::eth::RevertInstruction& ex) + { + result.status_code = EVMC_REVERT; + result.gas_left = vm->m_io_gas; + output = ex.output(); // This moves the output from the exception! + } + catch (dev::eth::BadInstruction const&) + { + result.status_code = EVMC_UNDEFINED_INSTRUCTION; + } + catch (dev::eth::OutOfStack const&) + { + result.status_code = EVMC_STACK_OVERFLOW; + } + catch (dev::eth::StackUnderflow const&) + { + result.status_code = EVMC_STACK_UNDERFLOW; + } + catch (dev::eth::BufferOverrun const&) + { + result.status_code = EVMC_INVALID_MEMORY_ACCESS; + } + catch (dev::eth::OutOfGas const&) + { + result.status_code = EVMC_OUT_OF_GAS; + } + catch (dev::eth::BadJumpDestination const&) + { + result.status_code = EVMC_BAD_JUMP_DESTINATION; + } + catch (dev::eth::DisallowedStateChange const&) + { + result.status_code = EVMC_STATIC_MODE_VIOLATION; + } + catch (dev::eth::VMException const&) + { + result.status_code = EVMC_FAILURE; + } + catch (...) + { + result.status_code = EVMC_INTERNAL_ERROR; + } + + if (!output.empty()) + { + // Make a copy of the output. + auto outputData = new uint8_t[output.size()]; + std::memcpy(outputData, output.data(), output.size()); + result.output_data = outputData; + result.output_size = output.size(); + result.release = delete_output; + } + + return result; } - if (!output.empty()) + static void setTracer(evmc_instance* _instance, evmc_trace_callback _callback, + evmc_tracer_context* _context) noexcept { - // Make a copy of the output. - auto outputData = new uint8_t[output.size()]; - std::memcpy(outputData, output.data(), output.size()); - result.output_data = outputData; - result.output_size = output.size(); - result.release = delete_output; + auto evmc = static_cast(_instance); + + evmc->m_traceCallback = _callback; + evmc->m_traceContext = _context; } - return result; -} + evmc_trace_callback m_traceCallback = nullptr; + evmc_tracer_context* m_traceContext = nullptr; +}; + } // namespace extern "C" evmc_instance* evmc_create_interpreter() noexcept { - // TODO: Allow creating multiple instances with different configurations. - static evmc_instance s_instance{ - EVMC_ABI_VERSION, - "interpreter", - aleth_get_buildinfo()->project_version, - ::destroy, - ::execute, - getCapabilities, - nullptr, // set_tracer - nullptr, // set_option - }; - return &s_instance; + return InterpreterEvmcInstance::create(); } - namespace dev { namespace eth { +void VM::trace() noexcept +{ + if (m_traceCallback) + { + // Skip the code extension added by the optimizer. + if (m_tracePC >= m_codeSize) + return; + + auto const& metrics = c_metrics[static_cast(m_OP)]; + evmc_uint256be topStackItem; + evmc_uint256be const* pushedStackItem = nullptr; + if (m_traceStatus == EVMC_SUCCESS && metrics.num_stack_returned_items >= 1) + { + topStackItem = toEvmC(m_SPP[0]); + pushedStackItem = &topStackItem; + } + m_traceCallback(m_traceContext, m_tracePC, m_traceStatus, m_io_gas, m_stackEnd - m_SPP, + pushedStackItem, m_mem.size(), 0, 0, nullptr); + } +} + uint64_t VM::memNeed(u256 _offset, u256 _size) { return toInt63(_size ? u512(_offset) + _size : u512(0)); @@ -262,7 +301,8 @@ evmc_tx_context const& VM::getTxContext() // interpreter entry point owning_bytes_ref VM::exec(evmc_context* _context, evmc_revision _rev, const evmc_message* _msg, - uint8_t const* _code, size_t _codeSize) + uint8_t const* _code, size_t _codeSize, evmc_trace_callback _traceCallback, + evmc_tracer_context* _traceContext) { m_context = _context; m_rev = _rev; @@ -270,7 +310,9 @@ owning_bytes_ref VM::exec(evmc_context* _context, evmc_revision _rev, const evmc m_io_gas = uint64_t(_msg->gas); m_PC = 0; m_pCode = _code; - m_codeSize = _codeSize; + m_codeSize = _codeSize; ///< The size of the original code. + m_traceCallback = _traceCallback; + m_traceContext = _traceContext; // trampoline to minimize depth of call stack when calling out m_bounce = &VM::initEntry; @@ -341,7 +383,8 @@ void VM::interpretCases() uint64_t b = (uint64_t)m_SP[0]; uint64_t s = (uint64_t)m_SP[1]; m_output = owning_bytes_ref{std::move(m_mem), b, s}; - m_bounce = 0; + m_bounce = nullptr; + trace(); } BREAK @@ -388,6 +431,7 @@ void VM::interpretCases() updateIOGas(); m_context->host->selfdestruct(m_context, &m_message->destination, &destination); m_bounce = nullptr; + trace(); } BREAK @@ -395,7 +439,8 @@ void VM::interpretCases() { ON_OP(); updateIOGas(); - m_bounce = 0; + m_bounce = nullptr; + trace(); } BREAK @@ -1178,9 +1223,8 @@ void VM::interpretCases() { ON_OP(); updateIOGas(); - ++m_PC; - m_SPP[0] = m_code[m_PC]; - ++m_PC; + m_SPP[0] = m_code[m_PC + 1]; + m_PC += 2; } CONTINUE @@ -1224,8 +1268,11 @@ void VM::interpretCases() // Construct a number out of PUSH bytes. // This requires the code has been copied and extended by 32 zero // bytes to handle "out of code" push data here. - for (++m_PC; numBytes--; ++m_PC) - m_SPP[0] = (m_SPP[0] << 8) | m_code[m_PC]; + uint64_t codeOffset = m_PC + 1; + for (; numBytes--; ++codeOffset) + m_SPP[0] = (m_SPP[0] << 8) | m_code[codeOffset]; + + m_PC = codeOffset; } CONTINUE diff --git a/libaleth-interpreter/VM.h b/libaleth-interpreter/VM.h index 30f20bfe6fa..de52da2791e 100644 --- a/libaleth-interpreter/VM.h +++ b/libaleth-interpreter/VM.h @@ -66,7 +66,8 @@ class VM VM() = default; owning_bytes_ref exec(evmc_context* _context, evmc_revision _rev, const evmc_message* _msg, - uint8_t const* _code, size_t _codeSize); + uint8_t const* _code, size_t _codeSize, evmc_trace_callback _traceCallback, + evmc_tracer_context* _traceContext); uint64_t m_io_gas = 0; private: @@ -81,7 +82,6 @@ class VM void copyCode(int); typedef void (VM::*MemFnPtr)(); MemFnPtr m_bounce = nullptr; - uint64_t m_nSteps = 0; // return bytes owning_bytes_ref m_output; @@ -108,6 +108,8 @@ class VM // interpreter state Instruction m_OP; // current operation uint64_t m_PC = 0; // program counter + uint64_t m_tracePC = 0; // program counter for tracing + evmc_status_code m_traceStatus = EVMC_SUCCESS; // the status of current instruction u256* m_SP = m_stackEnd; // stack pointer u256* m_SPP = m_SP; // stack pointer prime (next SP) @@ -116,6 +118,10 @@ class VM uint64_t m_newMemSize = 0; uint64_t m_copyMemSize = 0; + evmc_trace_callback m_traceCallback = nullptr; + evmc_tracer_context* m_traceContext = nullptr; + void trace() noexcept; + // initialize interpreter void initEntry(); void optimize(); diff --git a/libaleth-interpreter/VMCalls.cpp b/libaleth-interpreter/VMCalls.cpp index 1ffb818e949..047065d472f 100644 --- a/libaleth-interpreter/VMCalls.cpp +++ b/libaleth-interpreter/VMCalls.cpp @@ -41,21 +41,29 @@ void VM::copyDataToMemory(bytesConstRef _data, u256*_sp) void VM::throwOutOfGas() { + m_traceStatus = EVMC_OUT_OF_GAS; + trace(); BOOST_THROW_EXCEPTION(OutOfGas()); } void VM::throwBadInstruction() { + m_traceStatus = EVMC_UNDEFINED_INSTRUCTION; + trace(); BOOST_THROW_EXCEPTION(BadInstruction()); } void VM::throwBadJumpDestination() { + m_traceStatus = EVMC_BAD_JUMP_DESTINATION; + trace(); BOOST_THROW_EXCEPTION(BadJumpDestination()); } void VM::throwDisallowedStateChange() { + m_traceStatus = EVMC_STATIC_MODE_VIOLATION; + trace(); BOOST_THROW_EXCEPTION(DisallowedStateChange()); } @@ -65,7 +73,10 @@ void VM::throwDisallowedStateChange() void VM::throwBadStack(int _removed, int _added) { bigint size = m_stackEnd - m_SPP; - if (size < _removed) + bool underflow = size < _removed; + m_traceStatus = underflow ? EVMC_STACK_UNDERFLOW : EVMC_STACK_OVERFLOW; + trace(); + if (underflow) BOOST_THROW_EXCEPTION(StackUnderflow() << RequirementError((bigint)_removed, size)); else BOOST_THROW_EXCEPTION(OutOfStack() << RequirementError((bigint)(_added - _removed), size)); @@ -75,11 +86,15 @@ void VM::throwRevertInstruction(owning_bytes_ref&& _output) { // We can't use BOOST_THROW_EXCEPTION here because it makes a copy of exception inside and // RevertInstruction has no copy constructor + m_traceStatus = EVMC_REVERT; + trace(); throw RevertInstruction(std::move(_output)); } void VM::throwBufferOverrun(bigint const& _endOfAccess) { + m_traceStatus = EVMC_INVALID_MEMORY_ACCESS; + trace(); BOOST_THROW_EXCEPTION(BufferOverrun() << RequirementError(_endOfAccess, bigint(m_returnData.size()))); } @@ -163,6 +178,7 @@ void VM::caseCreate() } else m_SPP[0] = 0; + trace(); ++m_PC; } @@ -194,6 +210,7 @@ void VM::caseCall() m_SPP[0] = 0; m_io_gas += msg.gas; } + trace(); ++m_PC; } diff --git a/libaleth-interpreter/VMConfig.h b/libaleth-interpreter/VMConfig.h index b94ac62316a..c4cef7ed420 100644 --- a/libaleth-interpreter/VMConfig.h +++ b/libaleth-interpreter/VMConfig.h @@ -124,18 +124,24 @@ namespace eth #if EVM_SWITCH_DISPATCH #define INIT_CASES -#define DO_CASES \ - for (;;) \ - { \ - fetchInstruction(); \ - switch (m_OP) \ +#define DO_CASES \ + for (;;) \ + { \ + fetchInstruction(); \ + auto startPC = m_PC; \ + switch (m_OP) \ { #define CASE(name) case Instruction::name: -#define NEXT \ - ++m_PC; \ +#define NEXT \ + trace(startPC); \ + ++m_PC; \ break; -#define CONTINUE continue; -#define BREAK return; +#define CONTINUE \ + trace(startPC); \ + continue; +#define BREAK \ + trace(startPC); \ + return; #define DEFAULT default: #define WHILE_CASES \ } \ @@ -414,15 +420,19 @@ namespace eth fetchInstruction(); \ goto* jumpTable[(int)m_OP]; #define CASE(name) \ - name: + name: \ + m_tracePC = m_PC; // Safe the current PC for tracing. #define NEXT \ + trace(); \ ++m_PC; \ fetchInstruction(); \ goto* jumpTable[(int)m_OP]; #define CONTINUE \ + trace(); \ fetchInstruction(); \ goto* jumpTable[(int)m_OP]; -#define BREAK return; +#define BREAK \ + return; #define DEFAULT #define WHILE_CASES diff --git a/libevm/EVMC.cpp b/libevm/EVMC.cpp index 8afd2984a12..7002541c1c3 100644 --- a/libevm/EVMC.cpp +++ b/libevm/EVMC.cpp @@ -3,6 +3,7 @@ #include "EVMC.h" +#include #include #include @@ -49,6 +50,34 @@ EVM::Result EVM::execute(ExtVMFace& _ext, int64_t gas) evmc_execute(m_instance, &_ext, mode, &msg, _ext.code.data(), _ext.code.size())}; } +static inline bool isCall(uint8_t _opcode) noexcept +{ + return _opcode == OP_CALL || _opcode == OP_CALLCODE || _opcode == OP_DELEGATECALL || + _opcode == OP_CREATE || _opcode == OP_CREATE2; +} + +EVMC::EVMC(evmc_instance* _instance) : EVM(_instance) +{ + static const auto tracer = [](evmc_tracer_context * _context, size_t _codeOffset, + evmc_status_code _statusCode, int64_t _gasLeft, size_t /*_stackNumItems*/, + evmc_uint256be const* _pushedStackItem, size_t /*_memorySize*/, + size_t /*changed_memory_offset*/, size_t /*changed_memory_size*/, + uint8_t const* /*changed_memory*/) noexcept + { + EVMC* evmc = reinterpret_cast(_context); + boost::optional pushedStackItem; + if (_pushedStackItem) + pushedStackItem = *_pushedStackItem; + + auto opcode = evmc->m_code[_codeOffset]; + auto callIndex = isCall(opcode) ? evmc->m_prevCall : -1; + evmc->m_calls[evmc->m_currentCall].trace.emplace_back(InstructionTrace{ + opcode, _codeOffset, _statusCode, _gasLeft, pushedStackItem, callIndex}); + }; + + _instance->set_tracer(_instance, tracer, reinterpret_cast(this)); +} + owning_bytes_ref EVMC::exec(u256& io_gas, ExtVMFace& _ext, const OnOpFunc& _onOp) { assert(_ext.envInfo().number() >= 0); @@ -63,9 +92,32 @@ owning_bytes_ref EVMC::exec(u256& io_gas, ExtVMFace& _ext, const OnOpFunc& _onOp assert(_ext.envInfo().gasLimit() <= int64max); assert(_ext.depth <= static_cast(std::numeric_limits::max())); + auto prevCode = m_code; + m_code = bytesConstRef{&_ext.code}; + auto gas = static_cast(io_gas); + + if (_ext.depth == 0) + m_prevCall = -1; // Reset prev call. + + m_calls.emplace_back( + CallTrace{static_cast(_ext.depth), _ext.isCreate ? EVMC_CREATE : EVMC_CALL, + EVMC_SUCCESS, gas, -1, _ext.caller, _ext.myAddress, {}}); + auto parentCall = m_currentCall; + m_currentCall = m_calls.size() - 1; + EVM::Result r = execute(_ext, gas); + m_calls[m_currentCall].status = r.status(); + m_calls[m_currentCall].gasLeft = r.gasLeft(); + m_prevCall = m_currentCall; + m_currentCall = parentCall; + + m_code = prevCode; + + if (_ext.depth == 0) + dumpTrace(); + switch (r.status()) { case EVMC_SUCCESS: @@ -130,5 +182,70 @@ evmc_revision EVM::toRevision(EVMSchedule const& _schedule) return EVMC_HOMESTEAD; return EVMC_FRONTIER; } + +static char const* to_string(evmc_call_kind _kind) +{ + switch (_kind) + { + case EVMC_CALL: + return "CALL"; + case EVMC_CALLCODE: + return "CALLCODE"; + case EVMC_DELEGATECALL: + return "DELEGATECALL"; + case EVMC_CREATE: + return "CREATE"; + case EVMC_CREATE2: + return "CREATE2"; + } + return ""; +} + +static void dumpCallTrace( + const std::vector& calls, size_t index, char const* const* names) +{ + auto& c = calls[index]; + + std::string indent; + for (int i = 0; i <= c.depth; ++i) + indent.push_back(' '); + + std::cout << ">>>>" << indent << "> " << to_string(c.kind) << " " << c.sender << " > " + << c.destination << " gas: " << std::dec << c.gas << "\n"; + for (auto& trace : c.trace) + { + auto* name = names[trace.opcode]; + std::cout << std::hex << std::right << std::setfill('0') << std::setw(4) << trace.codeOffset + << indent << " "; + if (name) + std::cout << std::left << std::setfill(' ') << std::setw(7) << name; + else + std::cout << "<" << std::hex << std::right << std::setfill('0') << std::setw(2) << int(trace.opcode) << "> "; + if (trace.pushedStackItem) + std::cout << " (" << fromEvmC(*trace.pushedStackItem) << ")"; + if (trace.statusCode != EVMC_SUCCESS) + std::cout << " <" << trace.statusCode << ">"; + std::cout << "\n"; + + if (trace.callIndex >= 0) + dumpCallTrace(calls, trace.callIndex, names); + } + std::cout << "<<<<" << indent << "< <" << c.status << "> " + << "gas left: " << std::dec << c.gasLeft << "\n"; +} + +void EVMC::dumpTrace() +{ + auto names = evmc_get_instruction_names_table(EVMC_LATEST_REVISION); + + for (size_t i = 0; i < m_calls.size(); ++i) + { + if (m_calls[i].depth == 0) + dumpCallTrace(m_calls, i, names); + } + + m_calls.clear(); +} + } // namespace eth } // namespace dev diff --git a/libevm/EVMC.h b/libevm/EVMC.h index 8d317f4616b..72b78990466 100644 --- a/libevm/EVMC.h +++ b/libevm/EVMC.h @@ -81,14 +81,50 @@ class EVM evmc_instance* m_instance = nullptr; }; +struct CallTrace; + +struct InstructionTrace +{ + uint8_t opcode; + size_t codeOffset; + evmc_status_code statusCode; + int64_t gasLeft; + boost::optional pushedStackItem; + int callIndex; +}; + +struct CallTrace +{ + int depth; + evmc_call_kind kind; + evmc_status_code status; + int64_t gas; + int64_t gasLeft; + Address sender; + Address destination; + + std::vector trace; +}; + /// The wrapper implementing the VMFace interface with a EVMC VM as a backend. class EVMC : public EVM, public VMFace { public: - explicit EVMC(evmc_instance* _instance) : EVM(_instance) {} + explicit EVMC(evmc_instance* _instance); owning_bytes_ref exec(u256& io_gas, ExtVMFace& _ext, OnOpFunc const& _onOp) final; + +private: + bytesConstRef m_code; + + Logger m_vmTraceLogger{createLogger(VerbosityTrace, "vmtrace")}; + + std::vector m_calls; + int m_currentCall = -1; + int m_prevCall = -1; + + void dumpTrace(); }; } } diff --git a/libevm/VMFactory.cpp b/libevm/VMFactory.cpp index 72c27f00c3a..e3d07d88e31 100644 --- a/libevm/VMFactory.cpp +++ b/libevm/VMFactory.cpp @@ -187,7 +187,10 @@ VMPtr VMFactory::create(VMKind _kind) switch (_kind) { case VMKind::Interpreter: - return {new EVMC{evmc_create_interpreter()}, default_delete}; + { + static std::unique_ptr s_interpreter{new EVMC{evmc_create_interpreter()}}; + return {s_interpreter.get(), null_delete}; + } case VMKind::DLL: assert(g_evmcDll != nullptr); // Return "fake" owning pointer to global EVMC DLL VM.