diff --git a/examples/examplevm/examplevm.c b/examples/examplevm/examplevm.c index 1ed8dd9..b1b4d57 100644 --- a/examples/examplevm/examplevm.c +++ b/examples/examplevm/examplevm.c @@ -9,6 +9,8 @@ struct examplevm { struct evmc_instance instance; int verbose; + evmc_trace_callback trace_callback; + struct evmc_tracer_context* tracer_context; }; static void destroy(struct evmc_instance* evm) @@ -113,6 +115,15 @@ static struct evmc_result execute(struct evmc_instance* instance, return ret; } +static void set_tracer(struct evmc_instance* instance, + evmc_trace_callback callback, + struct evmc_tracer_context* context) +{ + struct examplevm* vm = (struct examplevm*)instance; + vm->trace_callback = callback; + vm->tracer_context = context; +} + struct evmc_instance* evmc_create_examplevm() { struct evmc_instance init = { @@ -122,6 +133,7 @@ struct evmc_instance* evmc_create_examplevm() .destroy = destroy, .execute = execute, .set_option = set_option, + .set_tracer = set_tracer, }; struct examplevm* vm = calloc(1, sizeof(struct examplevm)); struct evmc_instance* interface = &vm->instance; diff --git a/include/evmc/evmc.h b/include/evmc/evmc.h index 22b064b..699d60c 100644 --- a/include/evmc/evmc.h +++ b/include/evmc/evmc.h @@ -40,7 +40,7 @@ extern "C" { enum { /** The EVMC ABI version number of the interface declared in this file. */ - EVMC_ABI_VERSION = 2 + EVMC_ABI_VERSION = 3 }; /** @@ -675,18 +675,18 @@ enum evmc_revision /** - * Generates and executes machine code for given EVM bytecode. + * Executes the given EVM bytecode using the input in the message * - * All the fun is here. This function actually does something useful. + * This function MAY be invoked multiple times for a single EVM instance. * - * @param instance A EVM instance. - * @param context The pointer to the Host execution context to be passed - * to callback functions. @see ::evmc_context. - * @param rev Requested EVM specification revision. - * @param msg Call parameters. @see ::evmc_message. - * @param code Reference to the bytecode to be executed. - * @param code_size The length of the bytecode. - * @return All execution results. + * @param instance The EVM instance. + * @param context The pointer to the Client execution context to be passed + * to the callback functions. @see ::evmc_context. + * @param rev Requested EVM specification revision. + * @param msg Call parameters. @see ::evmc_message. + * @param code Reference to the bytecode to be executed. + * @param code_size The length of the bytecode. + * @return All execution results. */ typedef struct evmc_result (*evmc_execute_fn)(struct evmc_instance* instance, struct evmc_context* context, @@ -696,6 +696,77 @@ typedef struct evmc_result (*evmc_execute_fn)(struct evmc_instance* instance, size_t code_size); +/** The opaque type representing a Client-side tracer object. */ +struct evmc_tracer_context; + +/** + * The callback to trace instructions execution in an EVM. + * + * This function informs the Client what instruction has been executed in the EVM implementation + * and what are the results of executing this particular instruction. + * The message level information (like call depth, destination address, etc.) are not provided here. + * This piece of information can be acquired by inspecting messages being sent to the EVM in + * ::evmc_execute_fn and the results of the messages execution. + * + * @param context The pointer to the Client-side tracing context. This allows to + * implement the tracer in OOP manner. + * @param code_offset The current instruction position in the code. + * @param status_code The status code of the instruction execution. + * @param gas_left The amount of the gas left after the instruction execution. + * @param stack_num_items The current EVM stack height after the instruction execution. + * @param pushed_stack_item The top EVM stack item pushed as the result of the instruction + * execution. This value is null when the instruction does not push + * anything to the stack. + * @param memory_size The size of the EVM memory after the instruction execution. + * @param changed_memory_offset The offset in number of bytes of the beginning of the memory area + * modified as the result of the instruction execution. + * The Client MAY use this information together with + * @p changed_memory_size and @p changed_memory to incrementally + * update the copy of the full VM's memory. + * @param changed_memory_size The size of the memory area modified as the result of + * the instruction execution. + * @param changed_memory The pointer to the memory area modified as the result of + * the instruction execution. + * The Client MAY access the pointed memory area + * (limited by the @p changed_memory_size) only during the current + * execution of the evmc_trace_callback(). + * The pointer MUST NOT be stored by the Client. + * The Client MUST NOT assume that + * `changed_memory - changed_memory_offset` is a valid base pointer + * of the VM memory. + */ +typedef void (*evmc_trace_callback)(struct evmc_tracer_context* context, + size_t code_offset, + enum evmc_status_code status_code, + int64_t gas_left, + size_t stack_num_items, + const struct evmc_uint256be* pushed_stack_item, + size_t memory_size, + size_t changed_memory_offset, + size_t changed_memory_size, + const uint8_t* changed_memory); + +/** + * Sets the EVM instruction tracer. + * + * When the tracer is set in the EVM instance, the EVM SHOULD call back the tracer with information + * about instructions execution in the EVM. + * @see ::evmc_trace_callback. + * + * This will overwrite the previous settings (the callback and the context). + * + * @param instance The EVM instance. + * @param callback The tracer callback function. This argument MAY be NULL to disable previously + * set tracer. + * @param context The Client-side tracer context. This argument MAY be NULL in case the tracer + * does not require any context. This argument MUST be NULL if the callback + * argument is NULL. + */ +typedef void (*evmc_set_tracer_fn)(struct evmc_instance* instance, + evmc_trace_callback callback, + struct evmc_tracer_context* context); + + /** * The EVM instance. * @@ -731,6 +802,13 @@ struct evmc_instance /** Pointer to function executing a code by the EVM instance. */ evmc_execute_fn execute; + /** + * Optional pointer to function setting the EVM instruction tracer. + * + * If the EVM does not support this feature the pointer can be NULL. + */ + evmc_set_tracer_fn set_tracer; + /** * Optional pointer to function modifying VM's options. * diff --git a/test/vmtester/CMakeLists.txt b/test/vmtester/CMakeLists.txt index 2026953..660fa74 100644 --- a/test/vmtester/CMakeLists.txt +++ b/test/vmtester/CMakeLists.txt @@ -19,6 +19,8 @@ target_link_libraries( CLI11::CLI11 ) +set_target_properties(evmc-vmtester PROPERTIES RUNTIME_OUTPUT_DIRECTORY ..) + install(TARGETS evmc-vmtester RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) add_test(NAME vmtester-help diff --git a/test/vmtester/tests.cpp b/test/vmtester/tests.cpp index 744c97b..178afc2 100644 --- a/test/vmtester/tests.cpp +++ b/test/vmtester/tests.cpp @@ -59,4 +59,13 @@ TEST_F(evmc_vm_test, version) { ASSERT_NE(vm->version, nullptr); EXPECT_GT(std::strlen(vm->version), 0) << "VM name cannot be empty"; -} \ No newline at end of file +} + +TEST_F(evmc_vm_test, set_tracer) +{ + static const auto tracer_callback = [](evmc_tracer_context*, size_t, evmc_status_code, int64_t, + size_t, const evmc_uint256be*, size_t, size_t, size_t, + const uint8_t*) noexcept {}; + if (vm->set_tracer) + vm->set_tracer(vm, tracer_callback, nullptr); +}