Skip to content
This repository has been archived by the owner on Oct 28, 2021. It is now read-only.

EVMC tracing for interpreter #5065

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
217 changes: 132 additions & 85 deletions libaleth-interpreter/VM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@

namespace
{
void destroy(evmc_instance* _instance)
{
(void)_instance;
}

evmc_capabilities_flagset getCapabilities(evmc_instance* _instance) noexcept
{
Expand All @@ -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<dev::eth::VM> 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<InterpreterEvmcInstance*>(_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<dev::eth::VM> vm{new dev::eth::VM};

evmc_result result = {};
dev::eth::owning_bytes_ref output;

auto evmc = static_cast<InterpreterEvmcInstance*>(_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<InterpreterEvmcInstance*>(_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<size_t>(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));
Expand Down Expand Up @@ -262,15 +301,18 @@ 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;
m_message = _msg;
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;
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -388,14 +431,16 @@ void VM::interpretCases()
updateIOGas();
m_context->host->selfdestruct(m_context, &m_message->destination, &destination);
m_bounce = nullptr;
trace();
}
BREAK

CASE(STOP)
{
ON_OP();
updateIOGas();
m_bounce = 0;
m_bounce = nullptr;
trace();
}
BREAK

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
10 changes: 8 additions & 2 deletions libaleth-interpreter/VM.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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;
Expand All @@ -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)

Expand All @@ -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();
Expand Down