Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracing API #32

Merged
merged 9 commits into from
Jul 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions examples/examplevm/examplevm.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ struct examplevm
{
struct evmc_instance instance;
int verbose;
evmc_trace_callback trace_callback;
Copy link

@jwasinger jwasinger Jul 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be useful to have another callback to allow the EVM to log text back to the client.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of a separate callback, actually it could just be added as an additional parameter to evmc_trace_callback.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, EVM can just do this via stderr.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C++ std::cerr works. We could add a logger method to capture VM logs nicely on the Host side. But that is different feature and should be done independently.

struct evmc_tracer_context* tracer_context;
};

static void destroy(struct evmc_instance* evm)
Expand Down Expand Up @@ -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 = {
Expand All @@ -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;
Expand Down
104 changes: 91 additions & 13 deletions include/evmc/evmc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

/**
Expand Down Expand Up @@ -675,18 +675,18 @@ enum evmc_revision


/**
* Generates and executes machine code for given EVM bytecode.
*
* All the fun is here. This function actually does something useful.
*
* @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.
* Executes the given EVM bytecode using the input in the message
*
* This function MAY be invoked multiple times for a single EVM instance.
*
* @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,
Expand All @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So based on these two the client needs to apply the stack changes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any comments?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the tracer want to know what's on stack it has to track the changes. This is even more complicated when you consider SWAP and DUP instructions - the tracer would have to reapply SWAP and DUP semantics.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure there aren't ever going to be instructions pushing more than one item?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory we have them now in form of DUP and SWAP.
In practice, not likely needed. We can also change the API in future. I will check WASM instructions.

* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm glad we discussed these on Gitter, much more cleaner now.

*/
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.
*
Expand Down Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be unset after its being set?

I don't fully remember (and seemingly our documentation isn't clear enough): can an evmc_instance reused for multiple executions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Answered in the latest commit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That commit looks good.


/**
* Optional pointer to function modifying VM's options.
*
Expand Down
2 changes: 2 additions & 0 deletions test/vmtester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion test/vmtester/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}

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