diff --git a/justfile b/justfile index 7612ff23..32d0280c 100644 --- a/justfile +++ b/justfile @@ -168,7 +168,7 @@ all: [group('codegen')] [doc('Regenerate vendored nanopb sources from the debugger schema')] -generate-nanopb: _nanopb +generate-nanopb: mkdir -p src/Debug/nanopb protoc -I src/Debug --nanopb_out=src/Debug/nanopb src/Debug/debug.proto perl -0pi -e 's{#include }{#include "pb.h"}' src/Debug/nanopb/debug.pb.h diff --git a/src/Debug/README.md b/src/Debug/README.md new file mode 100644 index 00000000..573723b3 --- /dev/null +++ b/src/Debug/README.md @@ -0,0 +1,19 @@ +#Debugger code structure + +The debugger implementation is split by responsibility: + +- `debugger.h`: public `Debugger` class and debugger state declarations. +- `debugger.cpp`: debugger lifecycle, message transport, and shared utilities. +- `debugger-command.cpp`: decoding and handling incoming debugger commands. +- `debugger-snapshot.cpp`: collecting and sending snapshots and checkpoints. +- `debugger-proxy.cpp`: debugger operations involving the proxy. +- `debugger-overrides.cpp`: function override management. +- `debugger-decode.h`: protocol framing and protobuf decoding helpers. +- `debugger-encode.h`: protobuf encoding callbacks for debugger data. +- `debugger-private.h`: shared private includes and implementation dependencies. +- `nanopb_encoder.*`: reusable nanopb callback implementations. + +protobuf code: + +- `debug.proto`: protobuf schema for debugger commands, notifications, and state. +- `nanopb/`: [vendored] generated protobuf bindings and the nanopb runtime. diff --git a/src/Debug/debug.proto b/src/Debug/debug.proto index bc69bc5f..9094ed5a 100644 --- a/src/Debug/debug.proto +++ b/src/Debug/debug.proto @@ -6,7 +6,7 @@ // // Frame packet structure: // -// [type: uint8][payload length: varint][protobuf payload] +// [command/notification type: uint8][payload length: varint][protobuf payload] // // Empty commands and notifications have a zero-length protobuf payload. @@ -20,68 +20,67 @@ option cc_enable_arenas = false; // Frontend -> WARDuino. // The receiver selects the payload schema from the command byte. enum Command { - COMMAND_RUN = 0; // no payload - COMMAND_HALT = 1; // no payload - COMMAND_PAUSE = 2; // no payload - COMMAND_STEP = 3; // no payload - COMMAND_STEP_OVER = 4; // no payload - COMMAND_ADD_BREAKPOINT = 5; // Breakpoint - COMMAND_REMOVE_BREAKPOINT = 6; // Breakpoint - - COMMAND_DUMP = 7; // no payload - COMMAND_DUMP_LOCALS = 8; // no payload - COMMAND_SNAPSHOT = 9; // no payload - COMMAND_DUMP_EVENTS = 10; // Range - COMMAND_DUMP_CALLBACKS = 11; // no payload - - COMMAND_UPDATE_FUNCTION = 12; // Function - COMMAND_UPDATE_LOCAL = 13; // ValueUpdate - COMMAND_UPDATE_CALLBACKS = 14; // CallbackMapping - COMMAND_UPDATE_MODULE = 26; // ModuleUpdate - COMMAND_UPDATE_GLOBAL = 27; // ValueUpdate - COMMAND_UPDATE_STACK = 28; // ValueUpdate - - COMMAND_LOAD_SNAPSHOT = 15; // Snapshot - COMMAND_PROXIFY = 16; // no payload - COMMAND_ADD_PROXY = 17; // FunctionRef - COMMAND_REMOVE_PROXY = 18; // FunctionRef - COMMAND_PROXY_CALL = 19; // RemoteFunctionCall - COMMAND_POP_EVENT = 20; // no payload - COMMAND_PUSH_EVENT = 21; // Event - - COMMAND_CONTINUE_FOR = 22; // ContinueFor - COMMAND_INSPECT = 23; // Inspect - COMMAND_RESET = 24; // no payload - COMMAND_INVOKE = 25; // RemoteFunctionCall - + COMMAND_RUN = 0; // no payload + COMMAND_HALT = 1; // no payload + COMMAND_PAUSE = 2; // no payload + COMMAND_STEP = 3; // no payload + COMMAND_STEP_OVER = 4; // no payload + + COMMAND_ADD_BREAKPOINT = 5; // CodeLocation + COMMAND_REMOVE_BREAKPOINT = 6; // CodeLocation + COMMAND_CLEAR_BREAKPOINTS = 7; // no payload + + COMMAND_HEAP_USAGE = 8; // no payload + + COMMAND_SNAPSHOT = 9; // Include + + COMMAND_UPDATE_FUNCTION = 12; // Function + COMMAND_UPDATE_LOCAL = 13; // ValueUpdate + COMMAND_UPDATE_CALLBACKS = 14; // CallbackMapping + COMMAND_UPDATE_MODULE = 26; // ModuleUpdate + COMMAND_UPDATE_GLOBAL = 27; // ValueUpdate + COMMAND_UPDATE_STACK = 28; // ValueUpdate + + COMMAND_LOAD_SNAPSHOT = 15; // Snapshot + COMMAND_PROXIFY = 16; // no payload + COMMAND_ADD_PROXY = 17; // FunctionRef + COMMAND_REMOVE_PROXY = 18; // FunctionRef + COMMAND_PROXY_CALL = 19; // RemoteFunctionCall + COMMAND_POP_EVENT = 20; // no payload + COMMAND_PUSH_EVENT = 21; // Event + + COMMAND_CONTINUE_FOR = 22; // ContinueFor + COMMAND_RESET = 24; // no payload + COMMAND_INVOKE = 25; // RemoteFunctionCall COMMAND_SET_SNAPSHOT_POLICY = 29; // SnapshotPolicyConfig - COMMAND_SET_OVERRIDE = 30; // Override - COMMAND_REMOVE_OVERRIDE = 31; // Override + COMMAND_SET_OVERRIDE = 30; // Override + COMMAND_REMOVE_OVERRIDE = 31; // Override } // WARDuino -> frontend. // The receiver selects the payload schema from the notification byte. enum NotificationType { - NOTIFICATION_CONTINUED = 0; // no payload - NOTIFICATION_HALTED = 1; // no payload - NOTIFICATION_PAUSED = 2; // no payload - NOTIFICATION_STEPPED = 3; // no payload - NOTIFICATION_HIT_BREAKPOINT = 4; // HitBreakpoint - NOTIFICATION_NEW_EVENT = 5; // NewEvent (zero-length payload) - - NOTIFICATION_FUNCTION_DUMP = 6; // Function - NOTIFICATION_LOCALS_DUMP = 7; // Locals - NOTIFICATION_SNAPSHOT = 8; // Snapshot - NOTIFICATION_EVENTS_DUMP = 9; // EventsQueue - NOTIFICATION_CALLBACKS_DUMP = 10; // CallbackMapping - NOTIFICATION_CHANGE_AFFECTED = 11; // no payload - - NOTIFICATION_MALFORMED = 12; // no payload - NOTIFICATION_UNKNOWN_COMMAND = 13; // no payload - NOTIFICATION_OPERATION_RESULT = 14; // OperationResult - NOTIFICATION_REMOTE_FUNCTION_RESULT = 15; // RemoteFunctionResult - NOTIFICATION_CHECKPOINT = 16; // Checkpoint + NOTIFICATION_CONTINUED = 0; // no payload + NOTIFICATION_HALTED = 1; // no payload + NOTIFICATION_PAUSED = 2; // no payload + NOTIFICATION_STEPPED = 3; // no payload + NOTIFICATION_HIT_BREAKPOINT = 4; // CodeLocation + NOTIFICATION_NEW_EVENT = 5; // Event + + NOTIFICATION_FUNCTION_DUMP = 6; // Function + NOTIFICATION_LOCALS_DUMP = 7; // Locals + NOTIFICATION_SNAPSHOT = 8; // Snapshot + NOTIFICATION_EVENTS_DUMP = 9; // EventsQueue + NOTIFICATION_CALLBACKS_DUMP = 10; // CallbackMapping + NOTIFICATION_CHANGE_AFFECTED = 11; // no payload + + NOTIFICATION_MALFORMED = 12; // no payload + NOTIFICATION_UNKNOWN_COMMAND = 13; // no payload + NOTIFICATION_OPERATION_RESULT = 14; // OperationResult + NOTIFICATION_REMOTE_FUNCTION_RESULT = 15; // RemoteFunctionResult + NOTIFICATION_CHECKPOINT = 16; // Checkpoint + NOTIFICATION_HEAP_USAGE = 17; // HeapUsage } enum State { @@ -90,6 +89,7 @@ enum State { STATE_WARDUINO_STEP = 2; STATE_PROXY_RUN = 3; STATE_PROXY_HALT = 4; + STATE_WARDUINO_INIT = 5; } // A virtual program address. @@ -98,26 +98,38 @@ message CodeLocation { uint32 program_counter = 2; } -message Breakpoint { - CodeLocation location = 1; -} - -message HitBreakpoint { - CodeLocation location = 1; +message NewEvent { + Event subject = 1; } -// The notification type carries all information for this event. The empty -// message exists for host-side reflection, but no protobuf bytes are sent. -message NewEvent {} +message HeapUsage { uint32 heap_used = 1; } message ContinueFor { uint32 count = 1; } -// Execution-state selectors understood by the VM. Keeping them as bytes lets -// the protocol add selectors without changing this schema. -message Inspect { - bytes state = 1; +// Payload of Snapshot command: contains field selectors as bit flags. +enum SnapshotSection { + SNAPSHOT_SECTION_UNSPECIFIED = 0; + SNAPSHOT_SECTION_PC = 1; + SNAPSHOT_SECTION_BREAKPOINTS = 2; + SNAPSHOT_SECTION_CALLSTACK = 4; + SNAPSHOT_SECTION_GLOBALS = 8; + SNAPSHOT_SECTION_TABLE = 16; + SNAPSHOT_SECTION_MEMORY = 32; + SNAPSHOT_SECTION_BRANCH_TABLE = 64; + SNAPSHOT_SECTION_STACK = 128; + SNAPSHOT_SECTION_CALLBACKS = 256; + SNAPSHOT_SECTION_EVENTS = 512; + SNAPSHOT_SECTION_IO = 1024; + SNAPSHOT_SECTION_OVERRIDES = 2048; + SNAPSHOT_SECTION_FUNCTIONS = 4096; + SNAPSHOT_SECTION_LOCALS = 8192; +} + +// Payload of Snapshot command: a little-endian bit vector of SnapshotSection values. +message Include { + bytes fields = 1; } message FunctionRef { @@ -147,7 +159,6 @@ message Snapshot { repeated uint32 branch_table = 13; repeated IOState io = 14; repeated Override overrides = 15; - uint32 heap_used = 16; } message Function { @@ -200,7 +211,6 @@ message CallbackEntry { } message EventsQueue { - // Total events in the queue; this can exceed the returned slice length. uint32 total_count = 1; repeated Event events = 2; Range range = 3; @@ -217,6 +227,7 @@ message Range { } message ModuleUpdate { bytes wasm = 1; } + message IndexedValues { repeated Value values = 1; } enum SnapshotPolicy { @@ -229,6 +240,7 @@ message SnapshotPolicyConfig { SnapshotPolicy policy = 1; uint32 interval = 2; uint32 minimum_return_count = 3; + // A little-endian bit vector of SnapshotSection values. bytes selected_state = 4; } @@ -239,19 +251,24 @@ message Override { } message OperationResult { Command command = 1; bool success = 2; } + message RemoteFunctionResult { bool success = 1; repeated Value results = 2; bytes error = 3; } + message Checkpoint { uint32 instruction_count = 1; bool has_primitive_call = 2; - uint32 primitive_function_index = 3; + uint32 primitive_function_index = 3; // previously: fidx_called repeated Value arguments = 4; repeated Value results = 5; Snapshot snapshot = 6; } + message TableState { uint32 initial = 1; uint32 maximum = 2; repeated uint32 entries = 3; } + message MemoryState { uint32 initial = 1; uint32 maximum = 2; uint32 pages = 3; bytes bytes = 4; } + message IOState { string key = 1; bool output = 2; sint32 value = 3; } diff --git a/src/Debug/debugger-command.cpp b/src/Debug/debugger-command.cpp index 2fb51ea5..d74e86f2 100644 --- a/src/Debug/debugger-command.cpp +++ b/src/Debug/debugger-command.cpp @@ -1,7 +1,7 @@ -#include "debugger-detail.h" -#include "debugger-protocol.h" +#include "debugger-decode.h" +#include "debugger-encode.h" -bool Debugger::check_debug_messages(Module *m, RunningState *program_state) { +bool Debugger::check_debug_messages(Module *m, debug_State *program_state) { std::optional message = get_debug_message(); if (!message) return false; @@ -44,18 +44,15 @@ bool Debugger::check_debug_messages(Module *m, RunningState *program_state) { break; case debug_Command_COMMAND_ADD_BREAKPOINT: case debug_Command_COMMAND_REMOVE_BREAKPOINT: { - debug_Breakpoint breakpoint = debug_Breakpoint_init_zero; - if (!decode_payload(message->payload, debug_Breakpoint_fields, - &breakpoint) || - !breakpoint.has_location || - breakpoint.location.module_index != 0 || - !isToPhysicalAddrPossible(breakpoint.location.program_counter, - m)) { + debug_CodeLocation location = debug_CodeLocation_init_zero; + if (!decode_payload(message->payload, debug_CodeLocation_fields, + &location) || + location.module_index != 0 || + !isToPhysicalAddrPossible(location.program_counter, m)) { malformed(); break; } - uint8_t *address = - toPhysicalAddress(breakpoint.location.program_counter, m); + uint8_t *address = toPhysicalAddress(location.program_counter, m); if (message->type == debug_Command_COMMAND_ADD_BREAKPOINT) add_breakpoint(address); else @@ -63,6 +60,12 @@ bool Debugger::check_debug_messages(Module *m, RunningState *program_state) { send_operation_result(message->type, true); break; } + case debug_Command_COMMAND_CLEAR_BREAKPOINTS: { + if (!require_empty()) break; + breakpoints.clear(); + send_operation_result(message->type, true); + break; + } case debug_Command_COMMAND_CONTINUE_FOR: { debug_ContinueFor request = debug_ContinueFor_init_zero; if (!decode_payload(message->payload, debug_ContinueFor_fields, @@ -72,46 +75,34 @@ bool Debugger::check_debug_messages(Module *m, RunningState *program_state) { break; } remaining_instructions = static_cast(request.count); - *program_state = WARDUINOrun; + *program_state = debug_State_STATE_WARDUINO_RUN; send_notification(debug_NotificationType_NOTIFICATION_CONTINUED); break; } - case debug_Command_COMMAND_DUMP: - if (!require_empty()) break; - pause_runtime(m); - encode_snapshot( - m, - snapshotPc | snapshotBreakpoints | snapshotCallstack | - snapshotGlobals | snapshotTable | snapshotBranchTable | - snapshotStack | snapshotCallbacks | snapshotEvents | - snapshotIO | snapshotOverrides | snapshotHeap | - snapshotLocals, - debug_NotificationType_NOTIFICATION_SNAPSHOT); - break; - case debug_Command_COMMAND_DUMP_LOCALS: + case debug_Command_COMMAND_HEAP_USAGE: if (!require_empty()) break; - pause_runtime(m); - dump_locals(m); + dump_heap_info(m); break; - case debug_Command_COMMAND_SNAPSHOT: - if (!require_empty()) break; - pause_runtime(m); - snapshot(m); - break; - case debug_Command_COMMAND_DUMP_EVENTS: { - debug_Range range = debug_Range_init_zero; - if (!decode_payload(message->payload, debug_Range_fields, &range) || - range.end < range.start) { + case debug_Command_COMMAND_SNAPSHOT: { + debug_Include request = debug_Include_init_zero; + std::vector fields; + set_decode_callback(&request.fields, &fields); + if (!decode_payload(message->payload, debug_Include_fields, + &request)) { malformed(); break; } - dump_events(range.start, range.end - range.start); + SnapshotSelection selection = 0; + if (!parse_selection(fields.data(), fields.size(), &selection)) { + malformed(); + break; + } + if (selection == 0) selection = full_snapshot_selection(); + pause_runtime(m); + send_snapshot(m, selection, + debug_NotificationType_NOTIFICATION_SNAPSHOT); break; } - case debug_Command_COMMAND_DUMP_CALLBACKS: - if (!require_empty()) break; - dump_callback_mapping(); - break; case debug_Command_COMMAND_UPDATE_LOCAL: { const auto update = update_value(message->payload); ExecutionContext *context = m->warduino->execution_context; @@ -232,23 +223,21 @@ bool Debugger::check_debug_messages(Module *m, RunningState *program_state) { break; } snapshotPolicy = static_cast(config.policy); - checkpointInterval = config.interval == 0 ? 1 : config.interval; - min_return_values = config.minimum_return_count; - free(checkpoint_state); - checkpoint_state = nullptr; - checkpoint_state_size = static_cast(selectedState.size()); - if (!selectedState.empty()) { - checkpoint_state = - static_cast(malloc(selectedState.size())); - if (checkpoint_state == nullptr) { - send_operation_result(message->type, false); - break; - } - memcpy(checkpoint_state, selectedState.data(), - selectedState.size()); - } - if (snapshotPolicy == SnapshotPolicy::checkpointing) + if (snapshotPolicy == SnapshotPolicy::checkpointing) { + checkpointInterval = config.interval; + min_return_values = config.minimum_return_count; + checkpointSelection = selectedMask; + // main allocated checkpoint_state even for an empty selection. + hasCheckpointSelection = true; + instructions_executed = 0; + instructions_since_full_snapshot = 0; + // make first initial checkpoint checkpoint(m, true); + } else { + min_return_values = 0; + checkpointSelection = 0; + hasCheckpointSelection = false; + } send_operation_result(message->type, true); break; } @@ -282,26 +271,6 @@ bool Debugger::check_debug_messages(Module *m, RunningState *program_state) { send_operation_result(message->type, true); break; } - case debug_Command_COMMAND_INSPECT: { - debug_Inspect request = debug_Inspect_init_zero; - std::vector selected; - set_decode_callback(&request.state, &selected); - if (!decode_payload(message->payload, debug_Inspect_fields, - &request)) { - malformed(); - break; - } - SnapshotSelection selection = 0; - if (!parse_selection(selected.data(), selected.size(), - &selection)) { - malformed(); - break; - } - pause_runtime(m); - encode_snapshot(m, selection, - debug_NotificationType_NOTIFICATION_SNAPSHOT); - break; - } case debug_Command_COMMAND_LOAD_SNAPSHOT: { debug_Snapshot state = debug_Snapshot_init_zero; if (!decode_payload(message->payload, debug_Snapshot_fields, @@ -368,8 +337,8 @@ bool Debugger::check_debug_messages(Module *m, RunningState *program_state) { m->functions[call.function_index].type, arguments)); break; } - const RunningState current = m->warduino->program_state; - m->warduino->program_state = WARDUINOrun; + const debug_State current = m->warduino->program_state; + m->warduino->program_state = debug_State_STATE_WARDUINO_RUN; exception[0] = "\0"[0]; const auto results = m->warduino->invoke( m, call.function_index, static_cast(values.size()), diff --git a/src/Debug/debugger-protocol.h b/src/Debug/debugger-decode.h similarity index 100% rename from src/Debug/debugger-protocol.h rename to src/Debug/debugger-decode.h diff --git a/src/Debug/debugger-detail.h b/src/Debug/debugger-encode.h similarity index 90% rename from src/Debug/debugger-detail.h rename to src/Debug/debugger-encode.h index c086eb1d..41d12cbc 100644 --- a/src/Debug/debugger-detail.h +++ b/src/Debug/debugger-encode.h @@ -1,30 +1,12 @@ +/** + * This file contains the protobuf callbacks used for encoding debugger data. + */ #pragma once #include "debugger-private.h" #pragma GCC diagnostic ignored "-Wunused-function" -/** - * Validate if there are interrupts and execute them - * - * The various kinds of interrupts are preceded by an identifier: - * - * - `0x01` : Continue running - * - `0x02` : Halt the execution - * - `0x03` : Pause execution - * - `0x04` : Execute one operation and then pause - * - `0x06` : Add a breakpoint, the address is specified as a pointer. - * The pointer should be specified as: 06[length][pointer] - * eg: 060655a5994fa3d6 (note the lack of spaces between the - * arguments, the 'length' is halve the size of the address string) - * - `0x07` : Remove the breakpoint at the address specified as a pointer if it - * exists (see `0x06`) - * - `0x10` : Dump information about the program - * - `0x11` : show locals - * - `0x12` : Dump full information - * - `0x20` : Replace the content body of a function by a new function given - * as payload (immediately following `0x10`), see #readChange - */ namespace { bool collect_bytes(pb_istream_t *stream, const pb_field_iter_t *, void **arg) { @@ -156,6 +138,16 @@ struct ValueView { Global *const *globals; }; +struct Uint32ValueView { + const uint32_t *values; + size_t size; +}; + +struct ReverseUint32ValueView { + const StackValue *top; + size_t size; +}; + struct EventRangeView { size_t begin; size_t size; @@ -210,6 +202,39 @@ bool encode_value_range(pb_ostream_t *stream, const pb_field_t *field, return true; } +bool encode_uint32_value(pb_ostream_t *stream, const pb_field_t *field, + const uint32_t source, const size_t index) { + debug_Value value = debug_Value_init_zero; + value.index = static_cast(index); + value.which_data = debug_Value_i32_bits_tag; + value.data.i32_bits = source; + return pb_encode_tag_for_field(stream, field) && + pb_encode_submessage(stream, debug_Value_fields, &value); +} + +bool encode_uint32_range(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *view = static_cast(*arg); + for (size_t index = 0; index < view->size; ++index) { + if (!encode_uint32_value(stream, field, view->values[index], index)) + return false; + } + return true; +} + +bool encode_reverse_uint32_stack_range(pb_ostream_t *stream, + const pb_field_t *field, + void *const *arg) { + const auto *view = static_cast(*arg); + for (size_t index = 0; index < view->size; ++index) { + if (!encode_uint32_value( + stream, field, + view->top[-static_cast(index)].value.uint32, index)) + return false; + } + return true; +} + bool encode_values(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) { const auto *values = static_cast *>(*arg); diff --git a/src/Debug/debugger-proxy.cpp b/src/Debug/debugger-proxy.cpp index 07bbfb72..9a5e20ff 100644 --- a/src/Debug/debugger-proxy.cpp +++ b/src/Debug/debugger-proxy.cpp @@ -1,11 +1,11 @@ #include "debugger-private.h" void Debugger::proxify() { - WARDuino::instance()->program_state = PROXYhalt; + WARDuino::instance()->program_state = debug_State_STATE_PROXY_HALT; this->proxy = new Proxy(); // TODO delete } -void Debugger::handle_proxy_call(Module *m, RunningState *, +void Debugger::handle_proxy_call(Module *m, debug_State *, uint8_t *interruptData) const { if (this->proxy == nullptr) { dbg_info("No proxy available to send proxy call to.\n"); diff --git a/src/Debug/debugger-snapshot.cpp b/src/Debug/debugger-snapshot.cpp index cf0897fa..5dd79e10 100644 --- a/src/Debug/debugger-snapshot.cpp +++ b/src/Debug/debugger-snapshot.cpp @@ -1,225 +1,197 @@ -#include "debugger-detail.h" -#include "debugger-protocol.h" +#include "debugger-decode.h" +#include "debugger-encode.h" -std::optional Debugger::update_value( - const std::vector &payload) const { - debug_ValueUpdate update = debug_ValueUpdate_init_zero; - if (!decode_payload(payload, debug_ValueUpdate_fields, &update) || - !update.has_value) { - return std::nullopt; - } - return update; -} - -void Debugger::dump(Module *m, bool) const { snapshot(m); } - -void Debugger::dump_stack(const Module *m) const { - const ExecutionContext *ectx = m->warduino->execution_context; - ValueView values{ectx->stack, - ectx->sp >= 0 ? static_cast(ectx->sp + 1) : 0, - nullptr}; - /* ValueView points directly at the execution stack. */ - debug_Locals locals = debug_Locals_init_zero; - locals.values.funcs.encode = encode_value_range; - locals.values.arg = &values; - send_notification(debug_NotificationType_NOTIFICATION_LOCALS_DUMP, - debug_Locals_fields, &locals); +void Debugger::dump_heap_info(Module *m) const { + debug_HeapUsage heap_usage = debug_HeapUsage_init_zero; + heap_usage.heap_used = m->warduino->get_heap_used(); + send_notification(debug_NotificationType_NOTIFICATION_HEAP_USAGE, + debug_HeapUsage_fields, &heap_usage); } -void Debugger::dump_breakpoints(Module *) const {} - -void Debugger::dump_functions(Module *) const {} +bool Debugger::parse_selection(const uint8_t *fields, const size_t size, + SnapshotSelection *selection) { + constexpr uint32_t validSections = + debug_SnapshotSection_SNAPSHOT_SECTION_PC | + debug_SnapshotSection_SNAPSHOT_SECTION_BREAKPOINTS | + debug_SnapshotSection_SNAPSHOT_SECTION_CALLSTACK | + debug_SnapshotSection_SNAPSHOT_SECTION_GLOBALS | + debug_SnapshotSection_SNAPSHOT_SECTION_TABLE | + debug_SnapshotSection_SNAPSHOT_SECTION_MEMORY | + debug_SnapshotSection_SNAPSHOT_SECTION_BRANCH_TABLE | + debug_SnapshotSection_SNAPSHOT_SECTION_STACK | + debug_SnapshotSection_SNAPSHOT_SECTION_CALLBACKS | + debug_SnapshotSection_SNAPSHOT_SECTION_EVENTS | + debug_SnapshotSection_SNAPSHOT_SECTION_IO | + debug_SnapshotSection_SNAPSHOT_SECTION_OVERRIDES | + debug_SnapshotSection_SNAPSHOT_SECTION_FUNCTIONS | + debug_SnapshotSection_SNAPSHOT_SECTION_LOCALS; + if (size > sizeof(*selection)) return false; -/* - * {"type":%u,"fidx":"0x%x","sp":%d,"fp":%d,"ra":"%p"}%s - */ -void Debugger::dump_callstack(Module *) const {} + uint32_t mask = 0; + for (size_t index = 0; index < size; ++index) + mask |= static_cast(fields[index]) << (index * 8); + if ((mask & ~validSections) != 0) return false; -void Debugger::dump_locals(const Module *m) const { - ValueView values = current_locals(m->warduino->execution_context); - debug_Locals locals = debug_Locals_init_zero; - locals.values.funcs.encode = encode_value_range; - locals.values.arg = &values; - send_notification(debug_NotificationType_NOTIFICATION_LOCALS_DUMP, - debug_Locals_fields, &locals); + *selection = static_cast(mask); + return true; } -void Debugger::dump_events(long start, long size) const { - const size_t total = CallbackHandler::event_count(); - const size_t first = - std::min(start < 0 ? size_t{0} : static_cast(start), total); - const size_t count = - size < 0 ? 0 : std::min(static_cast(size), total - first); - EventRangeView range{first, count}; - debug_EventsQueue queue = debug_EventsQueue_init_zero; - queue.total_count = static_cast(total); - queue.has_range = true; - queue.range.start = static_cast(first); - queue.range.end = static_cast(first + count); - queue.events.funcs.encode = encode_events; - queue.events.arg = ⦥ - send_notification(debug_NotificationType_NOTIFICATION_EVENTS_DUMP, - debug_EventsQueue_fields, &queue); -} +class SnapshotEncodingContext { + public: + SnapshotEncodingContext( + Module *module, const Debugger *debugger, + const std::unordered_map, uint32_t, + FNV1aVectorHash> *overrides) + : module(module), + context(module->warduino->execution_context), + view{module, debugger, context, overrides}, + globals{nullptr, module->global_count, module->globals}, + stack{context->stack, + context->sp >= 0 ? static_cast(context->sp + 1) : 0, + nullptr}, + locals(current_locals(context)), + table{module->table.entries, + module->table.entries == nullptr ? 0 : module->table.size}, + memory{module->memory.bytes, + module->memory.bytes == nullptr + ? 0 + : static_cast(module->memory.pages) * PAGE_SIZE}, + branch{context->br_table, context->br_table == nullptr + ? size_t{0} + : static_cast(BR_TABLE_SIZE)}, + events{0, CallbackHandler::event_count()} {} -void Debugger::dump_callback_mapping() const { - const auto &callbacks = CallbackHandler::callback_map(); - debug_CallbackMapping mapping = debug_CallbackMapping_init_zero; - mapping.entries.funcs.encode = encode_callbacks; - mapping.entries.arg = - const_cast(&callbacks); - send_notification(debug_NotificationType_NOTIFICATION_CALLBACKS_DUMP, - debug_CallbackMapping_fields, &mapping); -} + ~SnapshotEncodingContext() { + for (IOStateElement *entry : ioState) delete entry; + } -void Debugger::dump_heap_info(Module *) const {} + void populate(debug_Snapshot *state, const SnapshotSelection selection) { + constexpr SnapshotSelection sections[] = { + debug_SnapshotSection_SNAPSHOT_SECTION_PC, + debug_SnapshotSection_SNAPSHOT_SECTION_BREAKPOINTS, + debug_SnapshotSection_SNAPSHOT_SECTION_FUNCTIONS, + debug_SnapshotSection_SNAPSHOT_SECTION_CALLSTACK, + debug_SnapshotSection_SNAPSHOT_SECTION_GLOBALS, + debug_SnapshotSection_SNAPSHOT_SECTION_STACK, + debug_SnapshotSection_SNAPSHOT_SECTION_LOCALS, + debug_SnapshotSection_SNAPSHOT_SECTION_TABLE, + debug_SnapshotSection_SNAPSHOT_SECTION_MEMORY, + debug_SnapshotSection_SNAPSHOT_SECTION_BRANCH_TABLE, + debug_SnapshotSection_SNAPSHOT_SECTION_CALLBACKS, + debug_SnapshotSection_SNAPSHOT_SECTION_EVENTS, + debug_SnapshotSection_SNAPSHOT_SECTION_IO, + debug_SnapshotSection_SNAPSHOT_SECTION_OVERRIDES, + }; -bool Debugger::parse_selection(const uint8_t *state, const size_t size, - SnapshotSelection *selection) { - *selection = 0; - for (size_t index = 0; index < size; ++index) { - if (state[index] < pcState || state[index] > heapState) return false; - *selection |= static_cast(1u << (state[index] - 1)); - } - return true; -} + for (const SnapshotSelection section : sections) { + if (!(selection & section)) continue; -bool Debugger::encode_snapshot( - Module *m, const SnapshotSelection selection, - const debug_NotificationType notification) const { - ExecutionContext *ectx = m->warduino->execution_context; - SnapshotView view{m, this, ectx, &overrides}; - debug_Snapshot state = debug_Snapshot_init_zero; - std::vector ioState; - if (selection & snapshotPc) { - state.program_counter = toVirtualAddress(ectx->pc_ptr, m); - switch (m->warduino->program_state) { - case WARDUINOrun: - state.state = debug_State_STATE_WARDUINO_RUN; - break; - case WARDUINOstep: - state.state = debug_State_STATE_WARDUINO_STEP; - break; - case PROXYrun: - state.state = debug_State_STATE_PROXY_RUN; - break; - case PROXYhalt: - state.state = debug_State_STATE_PROXY_HALT; - break; - default: - state.state = debug_State_STATE_WARDUINO_PAUSE; - break; + switch (section) { + case debug_SnapshotSection_SNAPSHOT_SECTION_PC: + state->program_counter = + toVirtualAddress(context->pc_ptr, module); + state->state = module->warduino->program_state; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_BREAKPOINTS: + state->breakpoints.funcs.encode = encode_breakpoints; + state->breakpoints.arg = &view; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_FUNCTIONS: + state->functions.funcs.encode = encode_functions; + state->functions.arg = &view; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_CALLSTACK: + state->callstack.funcs.encode = encode_callstack; + state->callstack.arg = &view; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_GLOBALS: + state->globals.funcs.encode = encode_value_range; + state->globals.arg = &globals; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_STACK: + state->stack.funcs.encode = encode_value_range; + state->stack.arg = &stack; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_LOCALS: + state->has_locals = true; + state->locals.values.funcs.encode = encode_value_range; + state->locals.values.arg = &locals; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_TABLE: + state->has_table = true; + state->table.initial = module->table.initial; + state->table.maximum = module->table.maximum; + state->table.entries.funcs.encode = + nanopb_encoder::encode_varints; + state->table.entries.arg = &table; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_MEMORY: + state->has_memory = true; + state->memory.initial = module->memory.initial; + state->memory.maximum = module->memory.maximum; + state->memory.pages = module->memory.pages; + state->memory.bytes.funcs.encode = + nanopb_encoder::encode_bytes; + state->memory.bytes.arg = &memory; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_BRANCH_TABLE: + state->branch_table.funcs.encode = + nanopb_encoder::encode_varints; + state->branch_table.arg = &branch; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_CALLBACKS: + state->has_callbacks = true; + state->callbacks.entries.funcs.encode = encode_callbacks; + state->callbacks.entries.arg = + const_cast( + &CallbackHandler::callback_map()); + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_EVENTS: + state->has_queue = true; + state->queue.total_count = + static_cast(events.size); + state->queue.has_range = true; + state->queue.range.start = 0; + state->queue.range.end = static_cast(events.size); + state->queue.events.funcs.encode = encode_events; + state->queue.events.arg = &events; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_IO: + ioState = + module->warduino->interpreter->get_io_state(module); + state->io.funcs.encode = encode_io_state; + state->io.arg = &ioState; + break; + case debug_SnapshotSection_SNAPSHOT_SECTION_OVERRIDES: + state->overrides.funcs.encode = encode_overrides; + state->overrides.arg = &view; + break; + default: + break; + } } } - if (selection & snapshotBreakpoints) { - state.breakpoints.funcs.encode = encode_breakpoints; - state.breakpoints.arg = &view; - } - if (selection & snapshotFunctions) { - state.functions.funcs.encode = encode_functions; - state.functions.arg = &view; - } - if (selection & snapshotCallstack) { - state.callstack.funcs.encode = encode_callstack; - state.callstack.arg = &view; - } - ValueView globals{nullptr, m->global_count, m->globals}; - if (selection & snapshotGlobals) { - state.globals.funcs.encode = encode_value_range; - state.globals.arg = &globals; - } - ValueView stackValues{ectx->stack, - ectx->sp >= 0 ? static_cast(ectx->sp + 1) : 0, - nullptr}; - if (selection & snapshotStack) { - state.stack.funcs.encode = encode_value_range; - state.stack.arg = &stackValues; - } - ValueView locals = current_locals(ectx); - if (selection & snapshotLocals) { - state.has_locals = true; - state.locals.values.funcs.encode = encode_value_range; - state.locals.values.arg = &locals; - } - Uint32View table{m->table.entries, - m->table.entries == nullptr ? 0 : m->table.size}; - if (selection & snapshotTable) { - state.has_table = true; - state.table.initial = m->table.initial; - state.table.maximum = m->table.maximum; - state.table.entries.funcs.encode = nanopb_encoder::encode_varints; - state.table.entries.arg = &table; - } - const size_t memorySize = static_cast(m->memory.pages) * PAGE_SIZE; - ByteView memory{m->memory.bytes, - m->memory.bytes == nullptr ? 0 : memorySize}; - if (selection & snapshotMemory) { - state.has_memory = true; - state.memory.initial = m->memory.initial; - state.memory.maximum = m->memory.maximum; - state.memory.pages = m->memory.pages; - state.memory.bytes.funcs.encode = nanopb_encoder::encode_bytes; - state.memory.bytes.arg = &memory; - } - Uint32View branch{ectx->br_table, ectx->br_table == nullptr - ? size_t{0} - : static_cast(BR_TABLE_SIZE)}; - if (selection & snapshotBranchTable) { - state.branch_table.funcs.encode = nanopb_encoder::encode_varints; - state.branch_table.arg = &branch; - } - const auto &callbacks = CallbackHandler::callback_map(); - if (selection & snapshotCallbacks) { - state.has_callbacks = true; - state.callbacks.entries.funcs.encode = encode_callbacks; - state.callbacks.entries.arg = - const_cast(&callbacks); - } - const size_t eventCount = CallbackHandler::event_count(); - EventRangeView events{0, eventCount}; - if (selection & snapshotEvents) { - state.has_queue = true; - state.queue.total_count = static_cast(eventCount); - state.queue.has_range = true; - state.queue.range.start = 0; - state.queue.range.end = static_cast(eventCount); - state.queue.events.funcs.encode = encode_events; - state.queue.events.arg = &events; - } - if (selection & snapshotIO) { - ioState = m->warduino->interpreter->get_io_state(m); - state.io.funcs.encode = encode_io_state; - state.io.arg = &ioState; - } - if (selection & snapshotOverrides) { - state.overrides.funcs.encode = encode_overrides; - state.overrides.arg = &view; - } - if (selection & snapshotHeap) - state.heap_used = m->warduino->get_heap_used(); - const bool sent = - send_notification(notification, debug_Snapshot_fields, &state); - for (IOStateElement *entry : ioState) delete entry; - return sent; -} -void Debugger::snapshot(Module *m) const { - constexpr SnapshotSelection complete = - snapshotPc | snapshotBreakpoints | snapshotCallstack | snapshotGlobals | - snapshotTable | snapshotMemory | snapshotBranchTable | snapshotStack | - snapshotCallbacks | snapshotEvents | snapshotIO | snapshotOverrides | - snapshotHeap | snapshotFunctions | snapshotLocals; - encode_snapshot(m, complete, debug_NotificationType_NOTIFICATION_SNAPSHOT); -} + private: + Module *module; + ExecutionContext *context; + SnapshotView view; + ValueView globals; + ValueView stack; + ValueView locals; + Uint32View table; + ByteView memory; + Uint32View branch; + EventRangeView events; + std::vector ioState; +}; -void Debugger::inspect(Module *m, const uint16_t size, - const uint8_t *state) const { - SnapshotSelection selection = 0; - if (!parse_selection(state, size, &selection)) { - send_notification(debug_NotificationType_NOTIFICATION_MALFORMED); - return; - } - encode_snapshot(m, selection, debug_NotificationType_NOTIFICATION_SNAPSHOT); +bool Debugger::send_snapshot(Module *m, const SnapshotSelection selection, + const debug_NotificationType notification) const { + SnapshotEncodingContext context{m, this, &overrides}; + debug_Snapshot state = debug_Snapshot_init_zero; + context.populate(&state, selection); + return send_notification(notification, debug_Snapshot_fields, &state); } std::optional get_primitive_being_called(Module *m, uint8_t *pc_ptr) { @@ -241,12 +213,8 @@ std::optional get_primitive_being_called(Module *m, uint8_t *pc_ptr) { void Debugger::handle_snapshot_policy(Module *m) { if (snapshotPolicy == SnapshotPolicy::atEveryInstruction) { - SnapshotSelection selection = 0; - if (checkpoint_state != nullptr && - parse_selection(checkpoint_state, checkpoint_state_size, - &selection)) - encode_snapshot(m, selection, - debug_NotificationType_NOTIFICATION_SNAPSHOT); + send_snapshot(m, full_snapshot_selection(), + debug_NotificationType_NOTIFICATION_SNAPSHOT); } else if (snapshotPolicy == SnapshotPolicy::checkpointing) { if (instructions_executed >= checkpointInterval || fidx_called) { if (min_return_values == 0) { @@ -261,10 +229,21 @@ void Debugger::handle_snapshot_policy(Module *m) { } } + // In tracing mode, insert a full checkpoint on the same cadence as + // main. UINT32_MAX explicitly disables this behaviour. + if (hasCheckpointSelection) { + if (checkpointInterval != UINT32_MAX && + instructions_since_full_snapshot >= checkpointInterval) { + checkpoint(m, true, true); + instructions_since_full_snapshot = 0; + } + instructions_since_full_snapshot++; + } + instructions_executed++; ExecutionContext *ectx = m->warduino->execution_context; - // Store arguments of last primitive call. + // Store arguments of last primitive call in declaration order. if ((fidx_called = get_primitive_being_called(m, ectx->pc_ptr))) { const Type *type = m->functions[*fidx_called].type; for (uint32_t i = 0; i < type->param_count; i++) { @@ -277,50 +256,36 @@ void Debugger::handle_snapshot_policy(Module *m) { } } -void Debugger::checkpoint(Module *m, const bool force) { +void Debugger::checkpoint(Module *m, const bool force, const bool full) { if (instructions_executed == 0 && !force) return; debug_Checkpoint notification = debug_Checkpoint_init_zero; notification.instruction_count = instructions_executed; + + const SnapshotSelection selection = full || !hasCheckpointSelection + ? full_snapshot_selection() + : checkpointSelection; + SnapshotEncodingContext snapshot{m, this, &overrides}; + notification.has_snapshot = true; + snapshot.populate(¬ification.snapshot, selection); + + Uint32ValueView arguments{nullptr, 0}; + ReverseUint32ValueView results{nullptr, 0}; if (fidx_called) { + const Type *type = m->functions[*fidx_called].type; notification.has_primitive_call = true; notification.primitive_function_index = *fidx_called; - } - SnapshotSelection selection = 0; - if (checkpoint_state != nullptr && - parse_selection(checkpoint_state, checkpoint_state_size, &selection) && - selection != 0) { - // Checkpoints only materialize the requested fields. Keep their views - // on this stack through nanopb sizing and encoding. + arguments = {prim_args, type->param_count}; + notification.arguments.funcs.encode = encode_uint32_range; + notification.arguments.arg = &arguments; + ExecutionContext *ectx = m->warduino->execution_context; - notification.has_snapshot = true; - if (selection & snapshotPc) { - notification.snapshot.program_counter = - toVirtualAddress(ectx->pc_ptr, m); - notification.snapshot.state = - m->warduino->program_state == WARDUINOrun - ? debug_State_STATE_WARDUINO_RUN - : debug_State_STATE_WARDUINO_PAUSE; - } - ValueView globals{nullptr, m->global_count, m->globals}; - /* Globals are read directly while nanopb encodes this checkpoint. */ - if (selection & snapshotGlobals) { - notification.snapshot.globals.funcs.encode = encode_value_range; - notification.snapshot.globals.arg = &globals; - } - ValueView stack{ectx->stack, - ectx->sp >= 0 ? static_cast(ectx->sp + 1) : 0, - nullptr}; - /* Stack values are read directly while nanopb encodes this checkpoint. - */ - if (selection & snapshotStack) { - notification.snapshot.stack.funcs.encode = encode_value_range; - notification.snapshot.stack.arg = &stack; - } - if (selection & snapshotHeap) - notification.snapshot.heap_used = m->warduino->get_heap_used(); + results = {ectx->stack + ectx->sp, type->result_count}; + notification.results.funcs.encode = encode_reverse_uint32_stack_range; + notification.results.arg = &results; } + send_notification(debug_NotificationType_NOTIFICATION_CHECKPOINT, debug_Checkpoint_fields, ¬ification); instructions_executed = 0; diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 2dc3f25b..bf31595f 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -1,5 +1,15 @@ +#include "debugger-decode.h" #include "debugger-private.h" -#include "debugger-protocol.h" + +std::optional Debugger::update_value( + const std::vector &payload) const { + debug_ValueUpdate update = debug_ValueUpdate_init_zero; + if (!decode_payload(payload, debug_ValueUpdate_fields, &update) || + !update.has_value) { + return std::nullopt; + } + return update; +} // Debugger @@ -10,10 +20,11 @@ Debugger::Debugger(Channel *duplex) { this->snapshotPolicy = SnapshotPolicy::none; this->checkpointInterval = 10; this->instructions_executed = 0; + this->instructions_since_full_snapshot = 0; this->fidx_called = {}; this->min_return_values = 0; - this->checkpoint_state = nullptr; - this->checkpoint_state_size = 0; + this->checkpointSelection = 0; + this->hasCheckpointSelection = false; this->remaining_instructions = -1; } @@ -27,7 +38,7 @@ void Debugger::stop() { } void Debugger::pause_runtime(const Module *m) { - m->warduino->program_state = WARDUINOpause; + m->warduino->program_state = debug_State_STATE_WARDUINO_PAUSE; this->mark = nullptr; } @@ -159,30 +170,30 @@ bool Debugger::is_breakpoint(uint8_t *loc) { void Debugger::notify_breakpoint(Module *m, uint8_t *pc_ptr) { if (snapshotPolicy == SnapshotPolicy::checkpointing) checkpoint(m); mark = nullptr; - debug_HitBreakpoint hit = debug_HitBreakpoint_init_zero; - hit.has_location = true; - hit.location.module_index = 0; - hit.location.program_counter = toVirtualAddress(pc_ptr, m); + debug_CodeLocation location = debug_CodeLocation_init_zero; + location.module_index = 0; + location.program_counter = toVirtualAddress(pc_ptr, m); send_notification(debug_NotificationType_NOTIFICATION_HIT_BREAKPOINT, - debug_HitBreakpoint_fields, &hit); + debug_CodeLocation_fields, &location); } void Debugger::handle_interrupt_run(const Module *m, - RunningState *program_state) { + debug_State *program_state) { ExecutionContext *ectx = m->warduino->execution_context; - if (*program_state == WARDUINOpause && this->is_breakpoint(ectx->pc_ptr)) { + if (*program_state == debug_State_STATE_WARDUINO_PAUSE && + this->is_breakpoint(ectx->pc_ptr)) { this->skipBreakpoint = ectx->pc_ptr; } - *program_state = WARDUINOrun; + *program_state = debug_State_STATE_WARDUINO_RUN; } -void Debugger::handle_step(const Module *m, RunningState *program_state) { +void Debugger::handle_step(const Module *m, debug_State *program_state) { ExecutionContext *ectx = m->warduino->execution_context; - *program_state = WARDUINOstep; + *program_state = debug_State_STATE_WARDUINO_STEP; this->skipBreakpoint = ectx->pc_ptr; } -void Debugger::handle_step_over(const Module *m, RunningState *program_state) { +void Debugger::handle_step_over(const Module *m, debug_State *program_state) { ExecutionContext *ectx = m->warduino->execution_context; this->skipBreakpoint = ectx->pc_ptr; uint8_t const opcode = *ectx->pc_ptr; @@ -190,14 +201,14 @@ void Debugger::handle_step_over(const Module *m, RunningState *program_state) { uint8_t *ptr_cpy = ectx->pc_ptr + 1; read_LEB_32(&ptr_cpy); this->mark = ectx->pc_ptr + (ptr_cpy - ectx->pc_ptr); - *program_state = WARDUINOrun; + *program_state = debug_State_STATE_WARDUINO_RUN; // warning: ack will be BP hit } else if (opcode == 0x11) { // step over indirect call uint8_t *ptr_cpy = ectx->pc_ptr + 1; read_LEB_32(&ptr_cpy); read_LEB_32(&ptr_cpy); this->mark = ectx->pc_ptr + (ptr_cpy - ectx->pc_ptr); - *program_state = WARDUINOrun; + *program_state = debug_State_STATE_WARDUINO_RUN; } else { // normal step this->handle_step(m, program_state); @@ -207,6 +218,7 @@ void Debugger::handle_step_over(const Module *m, RunningState *program_state) { bool Debugger::reset(Module *m) { m->warduino->reset_module(m); instructions_executed = 0; + instructions_since_full_snapshot = 0; debug("Reset WARDuino.\n"); return true; } diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 9536deaf..7d83409e 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -24,53 +24,7 @@ struct DebugMessage { std::vector payload; }; -enum RunningState { - WARDUINOinit, - WARDUINOrun, - WARDUINOpause, - WARDUINOstep, - PROXYrun, // Running state used when executing a proxy call. During - // this state the call is set up and executed by the main - // loop. After execution, the state is restored to - // PROXYhalt - PROXYhalt // Do not run the program (program runs on computer, which - // sends messages for primitives, do forward interrupts) -}; - -enum ExecutionState { - pcState = 0x01, - breakpointsState = 0x02, - callstackState = 0x03, - globalsState = 0x04, - tableState = 0x05, - memoryState = 0x06, - branchingTableState = 0x07, - stackState = 0x08, - callbacksState = 0x09, - eventsState = 0x0A, - ioState = 0x0B, - overridesState = 0x0C, - heapState = 0x0D, -}; - using SnapshotSelection = uint16_t; -enum SnapshotSection : SnapshotSelection { - snapshotPc = 1u << 0, - snapshotBreakpoints = 1u << 1, - snapshotCallstack = 1u << 2, - snapshotGlobals = 1u << 3, - snapshotTable = 1u << 4, - snapshotMemory = 1u << 5, - snapshotBranchTable = 1u << 6, - snapshotStack = 1u << 7, - snapshotCallbacks = 1u << 8, - snapshotEvents = 1u << 9, - snapshotIO = 1u << 10, - snapshotOverrides = 1u << 11, - snapshotHeap = 1u << 12, - snapshotFunctions = 1u << 13, - snapshotLocals = 1u << 14 -}; enum ProxyInterruptTypes { interruptProxyCall = 0x64, @@ -126,13 +80,14 @@ class Debugger { // Checkpointing SnapshotPolicy snapshotPolicy; - uint32_t checkpointInterval; // #instructions between checkpoints - uint32_t instructions_executed; // #instructions since last checkpoint + uint32_t checkpointInterval; // #instructions between checkpoints + uint32_t instructions_executed; // #instructions since last checkpoint + uint32_t instructions_since_full_snapshot; std::optional fidx_called; // The primitive that was executed uint32_t prim_args[8]; // The arguments of the executed prim uint32_t min_return_values; - uint32_t checkpoint_state_size; - uint8_t *checkpoint_state; + SnapshotSelection checkpointSelection; + bool hasCheckpointSelection; // Continue for int32_t remaining_instructions; @@ -151,42 +106,44 @@ class Debugger { //// Handle Interrupt Types - void handle_interrupt_run(const Module *m, RunningState *program_state); + void handle_interrupt_run(const Module *m, debug_State *program_state); - void handle_step(const Module *m, RunningState *program_state); + void handle_step(const Module *m, debug_State *program_state); - void handle_step_over(const Module *m, RunningState *program_state); + void handle_step_over(const Module *m, debug_State *program_state); //// Information dumps - void dump(Module *m, bool full = false) const; - - void dump_stack(const Module *m) const; - - void dump_locals(const Module *m) const; - - void dump_breakpoints(Module *m) const; - - void dump_functions(Module *m) const; - - void dump_callstack(Module *m) const; - - void dump_events(long start, long size) const; - - void dump_callback_mapping() const; - void dump_heap_info(Module *m) const; - void inspect(Module *m, uint16_t sizeStateArray, - const uint8_t *state) const; - bool encode_snapshot(Module *m, SnapshotSelection selection, - debug_NotificationType notification) const; - static bool parse_selection(const uint8_t *state, size_t size, + bool send_snapshot(Module *m, SnapshotSelection selection, + debug_NotificationType notification) const; + + static constexpr SnapshotSelection full_snapshot_selection() { + return debug_SnapshotSection_SNAPSHOT_SECTION_PC | + debug_SnapshotSection_SNAPSHOT_SECTION_BREAKPOINTS | + debug_SnapshotSection_SNAPSHOT_SECTION_CALLSTACK | + debug_SnapshotSection_SNAPSHOT_SECTION_GLOBALS | + debug_SnapshotSection_SNAPSHOT_SECTION_TABLE | + debug_SnapshotSection_SNAPSHOT_SECTION_MEMORY | + debug_SnapshotSection_SNAPSHOT_SECTION_BRANCH_TABLE | + debug_SnapshotSection_SNAPSHOT_SECTION_STACK | + debug_SnapshotSection_SNAPSHOT_SECTION_CALLBACKS | + debug_SnapshotSection_SNAPSHOT_SECTION_EVENTS | + debug_SnapshotSection_SNAPSHOT_SECTION_IO | + debug_SnapshotSection_SNAPSHOT_SECTION_OVERRIDES; + } + + static bool parse_selection(const uint8_t *fields, size_t size, SnapshotSelection *selection); + //// Util functions + std::optional update_value( const std::vector &payload) const; + //// reset + bool reset(Module *m); public: @@ -225,7 +182,7 @@ class Debugger { std::optional get_debug_message(); - bool check_debug_messages(Module *m, RunningState *program_state); + bool check_debug_messages(Module *m, debug_State *program_state); // Breakpoints @@ -237,17 +194,25 @@ class Debugger { void notify_breakpoint(Module *m, uint8_t *pc_ptr); - // Out-of-place debugging: EDWARD - - void snapshot(Module *m) const; + // Multiverse debugging: MIO void handle_snapshot_policy(Module *m); + inline SnapshotPolicy get_snapshot_policy() { return snapshotPolicy; } + bool handle_continue_for(Module *m); + // Concolic Multiverse Debugging + + bool get_mock_for_args(Module *m, uint32_t fidx, uint32_t &result); + + void checkpoint(Module *m, bool force = false, bool full = false); + + // Out-of-place debugging: EDWARD + void proxify(); - void handle_proxy_call(Module *m, RunningState *program_state, + void handle_proxy_call(Module *m, debug_State *program_state, uint8_t *interruptData) const; RFC *top_proxy_call() const; @@ -273,11 +238,4 @@ class Debugger { void notify_pushed_event() const; bool handle_pushed_event(char *bytes) const; - - // Concolic Multiverse Debugging - bool get_mock_for_args(Module *m, uint32_t fidx, uint32_t &result); - - // Checkpointing - void checkpoint(Module *m, bool force = false); - inline SnapshotPolicy get_snapshot_policy() { return snapshotPolicy; } }; diff --git a/src/Debug/nanopb/debug.pb.c b/src/Debug/nanopb/debug.pb.c index d5f6fee3..9468af58 100644 --- a/src/Debug/nanopb/debug.pb.c +++ b/src/Debug/nanopb/debug.pb.c @@ -9,19 +9,16 @@ PB_BIND(debug_CodeLocation, debug_CodeLocation, AUTO) -PB_BIND(debug_Breakpoint, debug_Breakpoint, AUTO) - - -PB_BIND(debug_HitBreakpoint, debug_HitBreakpoint, AUTO) +PB_BIND(debug_NewEvent, debug_NewEvent, AUTO) -PB_BIND(debug_NewEvent, debug_NewEvent, AUTO) +PB_BIND(debug_HeapUsage, debug_HeapUsage, AUTO) PB_BIND(debug_ContinueFor, debug_ContinueFor, AUTO) -PB_BIND(debug_Inspect, debug_Inspect, AUTO) +PB_BIND(debug_Include, debug_Include, AUTO) PB_BIND(debug_FunctionRef, debug_FunctionRef, AUTO) @@ -102,3 +99,5 @@ PB_BIND(debug_IOState, debug_IOState, AUTO) + + diff --git a/src/Debug/nanopb/debug.pb.h b/src/Debug/nanopb/debug.pb.h index cf2bed6b..9c28b1a7 100644 --- a/src/Debug/nanopb/debug.pb.h +++ b/src/Debug/nanopb/debug.pb.h @@ -18,13 +18,11 @@ typedef enum _debug_Command { debug_Command_COMMAND_PAUSE = 2, /* no payload */ debug_Command_COMMAND_STEP = 3, /* no payload */ debug_Command_COMMAND_STEP_OVER = 4, /* no payload */ - debug_Command_COMMAND_ADD_BREAKPOINT = 5, /* Breakpoint */ - debug_Command_COMMAND_REMOVE_BREAKPOINT = 6, /* Breakpoint */ - debug_Command_COMMAND_DUMP = 7, /* no payload */ - debug_Command_COMMAND_DUMP_LOCALS = 8, /* no payload */ - debug_Command_COMMAND_SNAPSHOT = 9, /* no payload */ - debug_Command_COMMAND_DUMP_EVENTS = 10, /* Range */ - debug_Command_COMMAND_DUMP_CALLBACKS = 11, /* no payload */ + debug_Command_COMMAND_ADD_BREAKPOINT = 5, /* CodeLocation */ + debug_Command_COMMAND_REMOVE_BREAKPOINT = 6, /* CodeLocation */ + debug_Command_COMMAND_CLEAR_BREAKPOINTS = 7, /* no payload */ + debug_Command_COMMAND_HEAP_USAGE = 8, /* no payload */ + debug_Command_COMMAND_SNAPSHOT = 9, /* Include */ debug_Command_COMMAND_UPDATE_FUNCTION = 12, /* Function */ debug_Command_COMMAND_UPDATE_LOCAL = 13, /* ValueUpdate */ debug_Command_COMMAND_UPDATE_CALLBACKS = 14, /* CallbackMapping */ @@ -39,7 +37,6 @@ typedef enum _debug_Command { debug_Command_COMMAND_POP_EVENT = 20, /* no payload */ debug_Command_COMMAND_PUSH_EVENT = 21, /* Event */ debug_Command_COMMAND_CONTINUE_FOR = 22, /* ContinueFor */ - debug_Command_COMMAND_INSPECT = 23, /* Inspect */ debug_Command_COMMAND_RESET = 24, /* no payload */ debug_Command_COMMAND_INVOKE = 25, /* RemoteFunctionCall */ debug_Command_COMMAND_SET_SNAPSHOT_POLICY = 29, /* SnapshotPolicyConfig */ @@ -54,8 +51,8 @@ typedef enum _debug_NotificationType { debug_NotificationType_NOTIFICATION_HALTED = 1, /* no payload */ debug_NotificationType_NOTIFICATION_PAUSED = 2, /* no payload */ debug_NotificationType_NOTIFICATION_STEPPED = 3, /* no payload */ - debug_NotificationType_NOTIFICATION_HIT_BREAKPOINT = 4, /* HitBreakpoint */ - debug_NotificationType_NOTIFICATION_NEW_EVENT = 5, /* NewEvent (zero-length payload) */ + debug_NotificationType_NOTIFICATION_HIT_BREAKPOINT = 4, /* CodeLocation */ + debug_NotificationType_NOTIFICATION_NEW_EVENT = 5, /* Event */ debug_NotificationType_NOTIFICATION_FUNCTION_DUMP = 6, /* Function */ debug_NotificationType_NOTIFICATION_LOCALS_DUMP = 7, /* Locals */ debug_NotificationType_NOTIFICATION_SNAPSHOT = 8, /* Snapshot */ @@ -66,7 +63,8 @@ typedef enum _debug_NotificationType { debug_NotificationType_NOTIFICATION_UNKNOWN_COMMAND = 13, /* no payload */ debug_NotificationType_NOTIFICATION_OPERATION_RESULT = 14, /* OperationResult */ debug_NotificationType_NOTIFICATION_REMOTE_FUNCTION_RESULT = 15, /* RemoteFunctionResult */ - debug_NotificationType_NOTIFICATION_CHECKPOINT = 16 /* Checkpoint */ + debug_NotificationType_NOTIFICATION_CHECKPOINT = 16, /* Checkpoint */ + debug_NotificationType_NOTIFICATION_HEAP_USAGE = 17 /* HeapUsage */ } debug_NotificationType; typedef enum _debug_State { @@ -74,9 +72,29 @@ typedef enum _debug_State { debug_State_STATE_WARDUINO_PAUSE = 1, debug_State_STATE_WARDUINO_STEP = 2, debug_State_STATE_PROXY_RUN = 3, - debug_State_STATE_PROXY_HALT = 4 + debug_State_STATE_PROXY_HALT = 4, + debug_State_STATE_WARDUINO_INIT = 5 } debug_State; +/* Payload of Snapshot command: contains field selectors as bit flags. */ +typedef enum _debug_SnapshotSection { + debug_SnapshotSection_SNAPSHOT_SECTION_UNSPECIFIED = 0, + debug_SnapshotSection_SNAPSHOT_SECTION_PC = 1, + debug_SnapshotSection_SNAPSHOT_SECTION_BREAKPOINTS = 2, + debug_SnapshotSection_SNAPSHOT_SECTION_CALLSTACK = 4, + debug_SnapshotSection_SNAPSHOT_SECTION_GLOBALS = 8, + debug_SnapshotSection_SNAPSHOT_SECTION_TABLE = 16, + debug_SnapshotSection_SNAPSHOT_SECTION_MEMORY = 32, + debug_SnapshotSection_SNAPSHOT_SECTION_BRANCH_TABLE = 64, + debug_SnapshotSection_SNAPSHOT_SECTION_STACK = 128, + debug_SnapshotSection_SNAPSHOT_SECTION_CALLBACKS = 256, + debug_SnapshotSection_SNAPSHOT_SECTION_EVENTS = 512, + debug_SnapshotSection_SNAPSHOT_SECTION_IO = 1024, + debug_SnapshotSection_SNAPSHOT_SECTION_OVERRIDES = 2048, + debug_SnapshotSection_SNAPSHOT_SECTION_FUNCTIONS = 4096, + debug_SnapshotSection_SNAPSHOT_SECTION_LOCALS = 8192 +} debug_SnapshotSection; + typedef enum _debug_SnapshotPolicy { debug_SnapshotPolicy_SNAPSHOT_POLICY_NONE = 0, debug_SnapshotPolicy_SNAPSHOT_POLICY_EVERY_INSTRUCTION = 1, @@ -90,31 +108,18 @@ typedef struct _debug_CodeLocation { uint32_t program_counter; } debug_CodeLocation; -typedef struct _debug_Breakpoint { - bool has_location; - debug_CodeLocation location; -} debug_Breakpoint; - -typedef struct _debug_HitBreakpoint { - bool has_location; - debug_CodeLocation location; -} debug_HitBreakpoint; - -/* The notification type carries all information for this event. The empty - message exists for host-side reflection, but no protobuf bytes are sent. */ -typedef struct _debug_NewEvent { - char dummy_field; -} debug_NewEvent; +typedef struct _debug_HeapUsage { + uint32_t heap_used; +} debug_HeapUsage; typedef struct _debug_ContinueFor { uint32_t count; } debug_ContinueFor; -/* Execution-state selectors understood by the VM. Keeping them as bytes lets - the protocol add selectors without changing this schema. */ -typedef struct _debug_Inspect { - pb_callback_t state; -} debug_Inspect; +/* Payload of Snapshot command: a little-endian bit vector of SnapshotSection values. */ +typedef struct _debug_Include { + pb_callback_t fields; +} debug_Include; typedef struct _debug_FunctionRef { uint32_t function_index; @@ -173,6 +178,11 @@ typedef struct _debug_Event { pb_callback_t payload; } debug_Event; +typedef struct _debug_NewEvent { + bool has_subject; + debug_Event subject; +} debug_NewEvent; + typedef struct _debug_Range { uint32_t start; uint32_t end; @@ -188,7 +198,6 @@ typedef struct _debug_Function { } debug_Function; typedef struct _debug_EventsQueue { - /* Total events in the queue; this can exceed the returned slice length. */ uint32_t total_count; pb_callback_t events; bool has_range; @@ -207,6 +216,7 @@ typedef struct _debug_SnapshotPolicyConfig { debug_SnapshotPolicy policy; uint32_t interval; uint32_t minimum_return_count; + /* A little-endian bit vector of SnapshotSection values. */ pb_callback_t selected_state; } debug_SnapshotPolicyConfig; @@ -261,7 +271,6 @@ typedef struct _debug_Snapshot { pb_callback_t branch_table; pb_callback_t io; pb_callback_t overrides; - uint32_t heap_used; } debug_Snapshot; typedef struct _debug_Checkpoint { @@ -291,12 +300,16 @@ extern "C" { #define _debug_Command_ARRAYSIZE ((debug_Command)(debug_Command_COMMAND_REMOVE_OVERRIDE+1)) #define _debug_NotificationType_MIN debug_NotificationType_NOTIFICATION_CONTINUED -#define _debug_NotificationType_MAX debug_NotificationType_NOTIFICATION_CHECKPOINT -#define _debug_NotificationType_ARRAYSIZE ((debug_NotificationType)(debug_NotificationType_NOTIFICATION_CHECKPOINT+1)) +#define _debug_NotificationType_MAX debug_NotificationType_NOTIFICATION_HEAP_USAGE +#define _debug_NotificationType_ARRAYSIZE ((debug_NotificationType)(debug_NotificationType_NOTIFICATION_HEAP_USAGE+1)) #define _debug_State_MIN debug_State_STATE_WARDUINO_RUN -#define _debug_State_MAX debug_State_STATE_PROXY_HALT -#define _debug_State_ARRAYSIZE ((debug_State)(debug_State_STATE_PROXY_HALT+1)) +#define _debug_State_MAX debug_State_STATE_WARDUINO_INIT +#define _debug_State_ARRAYSIZE ((debug_State)(debug_State_STATE_WARDUINO_INIT+1)) + +#define _debug_SnapshotSection_MIN debug_SnapshotSection_SNAPSHOT_SECTION_UNSPECIFIED +#define _debug_SnapshotSection_MAX debug_SnapshotSection_SNAPSHOT_SECTION_LOCALS +#define _debug_SnapshotSection_ARRAYSIZE ((debug_SnapshotSection)(debug_SnapshotSection_SNAPSHOT_SECTION_LOCALS+1)) #define _debug_SnapshotPolicy_MIN debug_SnapshotPolicy_SNAPSHOT_POLICY_NONE #define _debug_SnapshotPolicy_MAX debug_SnapshotPolicy_SNAPSHOT_POLICY_CHECKPOINTING @@ -309,7 +322,6 @@ extern "C" { - #define debug_Snapshot_state_ENUMTYPE debug_State @@ -337,14 +349,13 @@ extern "C" { /* Initializer values for message structs */ #define debug_CodeLocation_init_default {0, 0} -#define debug_Breakpoint_init_default {false, debug_CodeLocation_init_default} -#define debug_HitBreakpoint_init_default {false, debug_CodeLocation_init_default} -#define debug_NewEvent_init_default {0} +#define debug_NewEvent_init_default {false, debug_Event_init_default} +#define debug_HeapUsage_init_default {0} #define debug_ContinueFor_init_default {0} -#define debug_Inspect_init_default {{{NULL}, NULL}} +#define debug_Include_init_default {{{NULL}, NULL}} #define debug_FunctionRef_init_default {0} #define debug_ValueUpdate_init_default {0, false, debug_Value_init_default} -#define debug_Snapshot_init_default {0, _debug_State_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_Locals_init_default, false, debug_EventsQueue_init_default, false, debug_CallbackMapping_init_default, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_TableState_init_default, false, debug_MemoryState_init_default, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, 0} +#define debug_Snapshot_init_default {0, _debug_State_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_Locals_init_default, false, debug_EventsQueue_init_default, false, debug_CallbackMapping_init_default, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_TableState_init_default, false, debug_MemoryState_init_default, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}} #define debug_Function_init_default {0, false, debug_Range_init_default, false, debug_Locals_init_default, {{NULL}, NULL}} #define debug_RemoteFunctionCall_init_default {0, {{NULL}, NULL}} #define debug_CallstackEntry_init_default {0, 0, 0, 0, 0, 0} @@ -366,14 +377,13 @@ extern "C" { #define debug_MemoryState_init_default {0, 0, 0, {{NULL}, NULL}} #define debug_IOState_init_default {{{NULL}, NULL}, 0, 0} #define debug_CodeLocation_init_zero {0, 0} -#define debug_Breakpoint_init_zero {false, debug_CodeLocation_init_zero} -#define debug_HitBreakpoint_init_zero {false, debug_CodeLocation_init_zero} -#define debug_NewEvent_init_zero {0} +#define debug_NewEvent_init_zero {false, debug_Event_init_zero} +#define debug_HeapUsage_init_zero {0} #define debug_ContinueFor_init_zero {0} -#define debug_Inspect_init_zero {{{NULL}, NULL}} +#define debug_Include_init_zero {{{NULL}, NULL}} #define debug_FunctionRef_init_zero {0} #define debug_ValueUpdate_init_zero {0, false, debug_Value_init_zero} -#define debug_Snapshot_init_zero {0, _debug_State_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_Locals_init_zero, false, debug_EventsQueue_init_zero, false, debug_CallbackMapping_init_zero, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_TableState_init_zero, false, debug_MemoryState_init_zero, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, 0} +#define debug_Snapshot_init_zero {0, _debug_State_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_Locals_init_zero, false, debug_EventsQueue_init_zero, false, debug_CallbackMapping_init_zero, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_TableState_init_zero, false, debug_MemoryState_init_zero, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}} #define debug_Function_init_zero {0, false, debug_Range_init_zero, false, debug_Locals_init_zero, {{NULL}, NULL}} #define debug_RemoteFunctionCall_init_zero {0, {{NULL}, NULL}} #define debug_CallstackEntry_init_zero {0, 0, 0, 0, 0, 0} @@ -398,10 +408,9 @@ extern "C" { /* Field tags (for use in manual encoding/decoding) */ #define debug_CodeLocation_module_index_tag 1 #define debug_CodeLocation_program_counter_tag 2 -#define debug_Breakpoint_location_tag 1 -#define debug_HitBreakpoint_location_tag 1 +#define debug_HeapUsage_heap_used_tag 1 #define debug_ContinueFor_count_tag 1 -#define debug_Inspect_state_tag 1 +#define debug_Include_fields_tag 1 #define debug_FunctionRef_function_index_tag 1 #define debug_RemoteFunctionCall_function_index_tag 1 #define debug_RemoteFunctionCall_arguments_tag 2 @@ -425,6 +434,7 @@ extern "C" { #define debug_CallbackEntry_table_indexes_tag 2 #define debug_Event_topic_tag 1 #define debug_Event_payload_tag 2 +#define debug_NewEvent_subject_tag 1 #define debug_Range_start_tag 1 #define debug_Range_end_tag 2 #define debug_Function_function_index_tag 1 @@ -470,7 +480,6 @@ extern "C" { #define debug_Snapshot_branch_table_tag 13 #define debug_Snapshot_io_tag 14 #define debug_Snapshot_overrides_tag 15 -#define debug_Snapshot_heap_used_tag 16 #define debug_Checkpoint_instruction_count_tag 1 #define debug_Checkpoint_has_primitive_call_tag 2 #define debug_Checkpoint_primitive_function_index_tag 3 @@ -488,32 +497,26 @@ X(a, STATIC, SINGULAR, UINT32, program_counter, 2) #define debug_CodeLocation_CALLBACK NULL #define debug_CodeLocation_DEFAULT NULL -#define debug_Breakpoint_FIELDLIST(X, a) \ -X(a, STATIC, OPTIONAL, MESSAGE, location, 1) -#define debug_Breakpoint_CALLBACK NULL -#define debug_Breakpoint_DEFAULT NULL -#define debug_Breakpoint_location_MSGTYPE debug_CodeLocation - -#define debug_HitBreakpoint_FIELDLIST(X, a) \ -X(a, STATIC, OPTIONAL, MESSAGE, location, 1) -#define debug_HitBreakpoint_CALLBACK NULL -#define debug_HitBreakpoint_DEFAULT NULL -#define debug_HitBreakpoint_location_MSGTYPE debug_CodeLocation - #define debug_NewEvent_FIELDLIST(X, a) \ - +X(a, STATIC, OPTIONAL, MESSAGE, subject, 1) #define debug_NewEvent_CALLBACK NULL #define debug_NewEvent_DEFAULT NULL +#define debug_NewEvent_subject_MSGTYPE debug_Event + +#define debug_HeapUsage_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, heap_used, 1) +#define debug_HeapUsage_CALLBACK NULL +#define debug_HeapUsage_DEFAULT NULL #define debug_ContinueFor_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, count, 1) #define debug_ContinueFor_CALLBACK NULL #define debug_ContinueFor_DEFAULT NULL -#define debug_Inspect_FIELDLIST(X, a) \ -X(a, CALLBACK, SINGULAR, BYTES, state, 1) -#define debug_Inspect_CALLBACK pb_default_field_callback -#define debug_Inspect_DEFAULT NULL +#define debug_Include_FIELDLIST(X, a) \ +X(a, CALLBACK, SINGULAR, BYTES, fields, 1) +#define debug_Include_CALLBACK pb_default_field_callback +#define debug_Include_DEFAULT NULL #define debug_FunctionRef_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, function_index, 1) @@ -542,8 +545,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, table, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, memory, 12) \ X(a, CALLBACK, REPEATED, UINT32, branch_table, 13) \ X(a, CALLBACK, REPEATED, MESSAGE, io, 14) \ -X(a, CALLBACK, REPEATED, MESSAGE, overrides, 15) \ -X(a, STATIC, SINGULAR, UINT32, heap_used, 16) +X(a, CALLBACK, REPEATED, MESSAGE, overrides, 15) #define debug_Snapshot_CALLBACK pb_default_field_callback #define debug_Snapshot_DEFAULT NULL #define debug_Snapshot_functions_MSGTYPE debug_Function @@ -710,11 +712,10 @@ X(a, STATIC, SINGULAR, SINT32, value, 3) #define debug_IOState_DEFAULT NULL extern const pb_msgdesc_t debug_CodeLocation_msg; -extern const pb_msgdesc_t debug_Breakpoint_msg; -extern const pb_msgdesc_t debug_HitBreakpoint_msg; extern const pb_msgdesc_t debug_NewEvent_msg; +extern const pb_msgdesc_t debug_HeapUsage_msg; extern const pb_msgdesc_t debug_ContinueFor_msg; -extern const pb_msgdesc_t debug_Inspect_msg; +extern const pb_msgdesc_t debug_Include_msg; extern const pb_msgdesc_t debug_FunctionRef_msg; extern const pb_msgdesc_t debug_ValueUpdate_msg; extern const pb_msgdesc_t debug_Snapshot_msg; @@ -741,11 +742,10 @@ extern const pb_msgdesc_t debug_IOState_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define debug_CodeLocation_fields &debug_CodeLocation_msg -#define debug_Breakpoint_fields &debug_Breakpoint_msg -#define debug_HitBreakpoint_fields &debug_HitBreakpoint_msg #define debug_NewEvent_fields &debug_NewEvent_msg +#define debug_HeapUsage_fields &debug_HeapUsage_msg #define debug_ContinueFor_fields &debug_ContinueFor_msg -#define debug_Inspect_fields &debug_Inspect_msg +#define debug_Include_fields &debug_Include_msg #define debug_FunctionRef_fields &debug_FunctionRef_msg #define debug_ValueUpdate_fields &debug_ValueUpdate_msg #define debug_Snapshot_fields &debug_Snapshot_msg @@ -771,7 +771,8 @@ extern const pb_msgdesc_t debug_IOState_msg; #define debug_IOState_fields &debug_IOState_msg /* Maximum encoded size of messages (where known) */ -/* debug_Inspect_size depends on runtime parameters */ +/* debug_NewEvent_size depends on runtime parameters */ +/* debug_Include_size depends on runtime parameters */ /* debug_ValueUpdate_size depends on runtime parameters */ /* debug_Snapshot_size depends on runtime parameters */ /* debug_Function_size depends on runtime parameters */ @@ -792,13 +793,11 @@ extern const pb_msgdesc_t debug_IOState_msg; /* debug_MemoryState_size depends on runtime parameters */ /* debug_IOState_size depends on runtime parameters */ #define DEBUG_DEBUG_PB_H_MAX_SIZE debug_CallstackEntry_size -#define debug_Breakpoint_size 14 #define debug_CallstackEntry_size 36 #define debug_CodeLocation_size 12 #define debug_ContinueFor_size 6 #define debug_FunctionRef_size 6 -#define debug_HitBreakpoint_size 14 -#define debug_NewEvent_size 0 +#define debug_HeapUsage_size 6 #define debug_OperationResult_size 4 #define debug_Range_size 12 diff --git a/src/Debug/nanopb_encoder.h b/src/Debug/nanopb_encoder.h index e6e53863..5789346f 100644 --- a/src/Debug/nanopb_encoder.h +++ b/src/Debug/nanopb_encoder.h @@ -2,8 +2,6 @@ #include "nanopb/pb_encode.h" -// Small, non-owning helpers for nanopb callback fields. The pointed-to data -// must remain stable for nanopb's sizing and output passes. namespace nanopb_encoder { struct ByteView { diff --git a/src/Edward/proxy.cpp b/src/Edward/proxy.cpp index 3182874f..f2cdf34a 100644 --- a/src/Edward/proxy.cpp +++ b/src/Edward/proxy.cpp @@ -33,7 +33,7 @@ void Proxy::pushRFC(Module *m, RFC *rfc) { // execute primitives directly ((Primitive)m->functions[rfc->fidx].func_ptr)(m); // send result directly - m->warduino->program_state = PROXYhalt; + m->warduino->program_state = debug_State_STATE_PROXY_HALT; m->warduino->debugger->send_proxy_call_result(m); return; } @@ -43,7 +43,7 @@ void Proxy::pushRFC(Module *m, RFC *rfc) { // push proxy guard block to stack this->pushProxyGuard(m); - m->warduino->program_state = PROXYrun; + m->warduino->program_state = debug_State_STATE_PROXY_RUN; } RFC *Proxy::topRFC() { return this->calls->top(); } diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index 84630dd6..10978b39 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -36,7 +36,7 @@ Block *Interpreter::pop_block(Module *m) { } if (frame->block->block_type == 0xfe) { - m->warduino->program_state = PROXYhalt; + m->warduino->program_state = debug_State_STATE_PROXY_HALT; m->warduino->debugger->send_proxy_call_result(m); // free if proxy guard free(frame->block); @@ -226,12 +226,12 @@ bool Interpreter::interpret(Module *m, bool waiting) { while ((!program_done && success) || waiting) { m = ectx->current_module; - if (m->warduino->program_state == WARDUINOstep) { + if (m->warduino->program_state == debug_State_STATE_WARDUINO_STEP) { m->warduino->debugger->notify_complete_step(m); m->warduino->debugger->pause_runtime(m); } - while (m->warduino->program_state != WARDUINOinit && + while (m->warduino->program_state != debug_State_STATE_WARDUINO_INIT && m->warduino->debugger->check_debug_messages( m, &m->warduino->program_state)) { } @@ -243,10 +243,11 @@ bool Interpreter::interpret(Module *m, bool waiting) { CallbackHandler::resolve_event(); // Sleep interpret loop if 'paused' or 'waiting drone' - if (m->warduino->program_state == WARDUINOpause || - m->warduino->program_state == PROXYhalt) { + if (m->warduino->program_state == debug_State_STATE_WARDUINO_PAUSE || + m->warduino->program_state == debug_State_STATE_PROXY_HALT) { // wait until new debug messages arrive - if (m->warduino->program_state == WARDUINOpause) { + if (m->warduino->program_state == + debug_State_STATE_WARDUINO_PAUSE) { warduino::unique_lock lock( m->warduino->debugger->messageQueueMutex); m->warduino->debugger->messageQueueConditionVariable.wait( @@ -260,7 +261,7 @@ bool Interpreter::interpret(Module *m, bool waiting) { // If BP and not the one we just unpaused if (m->warduino->debugger->is_breakpoint(ectx->pc_ptr) && m->warduino->debugger->skipBreakpoint != ectx->pc_ptr && - m->warduino->program_state != PROXYrun) { + m->warduino->program_state != debug_State_STATE_PROXY_RUN) { m->warduino->debugger->pause_runtime(m); m->warduino->debugger->notify_breakpoint(m, ectx->pc_ptr); continue; @@ -272,7 +273,7 @@ bool Interpreter::interpret(Module *m, bool waiting) { } // Take snapshot before executing an instruction - if (m->warduino->program_state != WARDUINOinit) { + if (m->warduino->program_state != debug_State_STATE_WARDUINO_INIT) { m->warduino->debugger->handle_snapshot_policy(m); } @@ -475,7 +476,7 @@ bool Interpreter::interpret(Module *m, bool waiting) { } } - if (m->warduino->program_state == PROXYrun) { + if (m->warduino->program_state == debug_State_STATE_PROXY_RUN) { dbg_info("Trap was thrown during proxy call.\n"); RFC *rfc = m->warduino->debugger->top_proxy_call(); rfc->success = false; diff --git a/src/WARDuino.h b/src/WARDuino.h index 5878093f..85e7c75b 100644 --- a/src/WARDuino.h +++ b/src/WARDuino.h @@ -45,7 +45,7 @@ class WARDuino { public: Debugger *debugger; Interpreter *interpreter; - RunningState program_state = WARDUINOrun; + debug_State program_state = debug_State_STATE_WARDUINO_RUN; ExecutionContext *execution_context = nullptr; ~WARDuino(); diff --git a/src/WARDuino/CallbackHandler.cpp b/src/WARDuino/CallbackHandler.cpp index 4f9796a3..2ab21c87 100644 --- a/src/WARDuino/CallbackHandler.cpp +++ b/src/WARDuino/CallbackHandler.cpp @@ -34,8 +34,8 @@ bool CallbackHandler::resolving_event = false; size_t CallbackHandler::pushed_cursor = 0; bool should_push_event() { - return WARDuino::instance()->program_state == PROXYrun || - WARDuino::instance()->program_state == PROXYhalt; + return WARDuino::instance()->program_state == debug_State_STATE_PROXY_RUN || + WARDuino::instance()->program_state == debug_State_STATE_PROXY_HALT; } std::unordered_map *> @@ -105,7 +105,8 @@ bool CallbackHandler::resolve_event(bool force) { } if (!force && (CallbackHandler::manual_event_resolution || - WARDuino::instance()->program_state == WARDUINOpause)) { + WARDuino::instance()->program_state == + debug_State_STATE_WARDUINO_PAUSE)) { return true; } diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index d922ecd7..5fab3e9c 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -268,8 +268,8 @@ void find_blocks(Module *m) { void WARDuino::run_init_expr(Module *m, uint8_t type, uint8_t **pc) { // Run the init_expr ExecutionContext *ectx = execution_context; - RunningState current = instance()->program_state; - WARDuino::instance()->program_state = WARDUINOinit; + debug_State current = instance()->program_state; + WARDuino::instance()->program_state = debug_State_STATE_WARDUINO_INIT; Block block; block.block_type = 0x01; block.type = get_block_type(m, type); @@ -342,8 +342,8 @@ void WARDuino::instantiate_module(Module *m, uint8_t *bytes, uint8_t *bytes_end = bytes + byte_count; // Needed for run_init_expr - RunningState oldState = this->program_state; - this->program_state = WARDUINOrun; + debug_State oldState = this->program_state; + this->program_state = debug_State_STATE_WARDUINO_RUN; execution_context->current_module = m; @@ -1226,7 +1226,7 @@ void WARDuino::free_module_state(Module *m) { void WARDuino::reset_module(Module *m) { const uint32_t byte_count = m->byte_count; free_module_state(m); // Does not reset m->bytes - program_state = WARDUINOinit; + program_state = debug_State_STATE_WARDUINO_INIT; ExecutionContext *ectx = execution_context; ectx->sp = -1; ectx->fp = -1; @@ -1238,7 +1238,7 @@ void WARDuino::reset_module(Module *m) { // execute main if (fidx != UNDEF) { interpreter->setup_call(m, fidx); - program_state = WARDUINOrun; + program_state = debug_State_STATE_WARDUINO_RUN; } // wait @@ -1246,7 +1246,7 @@ void WARDuino::reset_module(Module *m) { } void WARDuino::update_module(Module *m, uint8_t *wasm, uint32_t wasm_len) { - m->warduino->program_state = WARDUINOinit; + m->warduino->program_state = debug_State_STATE_WARDUINO_INIT; this->free_module_state(m); @@ -1262,7 +1262,7 @@ void WARDuino::update_module(Module *m, uint8_t *wasm, uint32_t wasm_len) { // execute main if (fidx != UNDEF) { interpreter->setup_call(m, fidx); - m->warduino->program_state = WARDUINOrun; + m->warduino->program_state = debug_State_STATE_WARDUINO_RUN; } // wait diff --git a/tests/latch/latch-0.7.0.tgz b/tests/latch/latch-0.7.0.tgz index 3a92ae55..a7b9c0fb 100644 Binary files a/tests/latch/latch-0.7.0.tgz and b/tests/latch/latch-0.7.0.tgz differ diff --git a/tests/latch/package-lock.json b/tests/latch/package-lock.json index 31e0568f..2de2c2ca 100644 --- a/tests/latch/package-lock.json +++ b/tests/latch/package-lock.json @@ -25,9 +25,9 @@ } }, "node_modules/@bufbuild/protobuf": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.14.0.tgz", - "integrity": "sha512-C3UGsiCwSprE2NKIIFA3hCDlpXTMCAXRZuEVp88L1GY36Y41+rYL5fryE+nOFhp4p4JPQvdV8PQ4DWgHgeTE+w==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.14.1.tgz", + "integrity": "sha512-agRJn3+EJDUe8AvxTx/LnHA/GErvLE62pSaSk7+MwFOtOv8eWBu/qCq2qoZjBjVZ3C2aiFJCveuSs17KMkYGOw==", "dev": true, "license": "(Apache-2.0 AND BSD-3-Clause)" }, @@ -369,9 +369,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz", - "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", + "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", "dev": true, "license": "MIT", "dependencies": { @@ -1002,7 +1002,7 @@ "node_modules/latch": { "version": "0.7.0", "resolved": "file:latch-0.7.0.tgz", - "integrity": "sha512-WHV03r12Y+neYUdXg4MqT3lRtpAH3XclJOufpZAjyS8QADdUyGk5KyokkQ5mnGfnK8tzMIadpbfBt5o5HeWZTA==", + "integrity": "sha512-2TwlXVk3l9s5oUoAca3NCXipVRj0E4dzbfU+djbag7IMkbuDy/QnMrodW1uOUU61N4EvsEBRrAVjDoT1w6X02A==", "dev": true, "dependencies": { "@bufbuild/protobuf": "^2.14.0", @@ -1689,9 +1689,9 @@ } }, "node_modules/socks": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", - "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/tests/latch/src/debugger.test.ts b/tests/latch/src/debugger.test.ts index 6948e1f1..17a917f5 100644 --- a/tests/latch/src/debugger.test.ts +++ b/tests/latch/src/debugger.test.ts @@ -2,6 +2,7 @@ import { Behaviour, + Breakpoint, DebugProtocol, Description, EmulatorSpecification, @@ -12,7 +13,6 @@ import { Message, Request, Step, Suite, TestScenario, - Breakpoint, WASM, WARDuino, Verbosity @@ -46,7 +46,7 @@ const expectSnapshot: Expectation[] = [ const SNAPSHOT: Step = { title: 'Send SNAPSHOT command', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: expectSnapshot }; @@ -57,7 +57,7 @@ const snapshotTest: TestScenario = { program: `${EXAMPLES}blink.wast`, steps: [{ title: 'Send snapshot command', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: expectSnapshot }] }; @@ -69,6 +69,12 @@ const expectSnapshotLocals: Expectation[] = [ {'locals': {kind: 'description', value: Description.defined} as Expected}, {'locals.values': {kind: 'description', value: Description.defined} as Expected>} ]; +const snapshotWithLocals: Request = { + ...Message.snapshot(), + payload: () => DebugProtocol.Include.encode({ + fields: Buffer.from([0x00, 0x40]) + }).finish() +}; integration.test({ title: 'Test SNAPSHOT button', @@ -94,7 +100,7 @@ for (const [name, program] of [['blink', 'blink.wast'], ['button', 'button.wast' program: EXAMPLES + program, steps: [{ title: 'Send SNAPSHOT command', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: snapshotWithLocals}, expected: expectSnapshotLocals }] }); @@ -107,7 +113,7 @@ const running: Step[] = [SNAPSHOT, { instruction: {kind: Kind.Request, value: Message.run}, }, { title: 'CHECK: execution continues', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: [{ 'programCounter': {kind: 'description', value: Description.defined} as Expected }, { @@ -140,13 +146,13 @@ const pauseTest: TestScenario = { instruction: {kind: Kind.Request, value: Message.pause}, }, { title: 'Send SNAPSHOT command', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: [{ 'programCounter': {kind: 'description', value: Description.defined} as Expected }] }, { title: 'CHECK: execution is stopped', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: [{ 'programCounter': {kind: 'description', value: Description.defined} as Expected }, { @@ -162,14 +168,14 @@ integration.test(pauseTest); function stepping(start: number, end: number): Step[] { return [{ title: 'Send SNAPSHOT command', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: [{'programCounter': {kind: 'primitive', value: start} as Expected}] }, { title: 'Send STEP command', instruction: {kind: Kind.Request, value: Message.step}, }, { title: 'CHECK: execution took one step', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: [{'programCounter': {kind: 'primitive', value: end} as Expected}] }]; } @@ -200,10 +206,10 @@ integration.test({ // Test *step over* command -const stepOverCall: Request = { +const stepOverCall: Request = { type: DebugProtocol.Command.COMMAND_STEP_OVER, notification: DebugProtocol.NotificationType.NOTIFICATION_HIT_BREAKPOINT, - parser: DebugProtocol.HitBreakpoint.decode + parser: DebugProtocol.CodeLocation.decode }; const stepOverTest: TestScenario = { @@ -211,28 +217,28 @@ const stepOverTest: TestScenario = { program: `${EXAMPLES}call.wast`, steps: [{ title: 'Send SNAPSHOT command', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: [{'programCounter': {kind: 'primitive', value: 167} as Expected}] }, { title: 'Send STEP OVER command', - instruction: {kind: Kind.Request, value: stepOverCall}, + instruction: {kind: Kind.Request, value: stepOverCall}, expected: [{programCounter: {kind: "comparison", value: (_: Object, value: number) => value > 0} as Expected}] }, { title: 'CHECK: execution stepped over direct call', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: [{'programCounter': {kind: 'primitive', value: 169} as Expected}] }, { title: 'Send STEP OVER command', instruction: {kind: Kind.Request, value: Message.stepOver} }, { title: 'CHECK: execution took one step', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: [{'programCounter': {kind: 'primitive', value: 171} as Expected}] }, { title: 'Send STEP OVER command', - instruction: {kind: Kind.Request, value: stepOverCall} + instruction: {kind: Kind.Request, value: stepOverCall}, expected: [{programCounter: {kind: "comparison", value: (_: Object, value: number) => value > 0} as Expected}] }, { title: 'CHECK: execution stepped over indirect call', - instruction: {kind: Kind.Request, value: Message.snapshot}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: [{'programCounter': {kind: 'primitive', value: 174} as Expected}] }] } @@ -246,7 +252,7 @@ integration.test({ program: `${EXAMPLES}blink.wast`, steps: [{ title: 'Inspect complete state', - instruction: {kind: Kind.Request, value: Message.inspect([])}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: expectSnapshot }] }); @@ -256,32 +262,42 @@ const inspectExpectations: Record = { [WARDuino.Inspect.breakpoints]: {breakpoints: {kind: 'description', value: Description.defined} as Expected>}, [WARDuino.Inspect.callstack]: {callstack: {kind: 'description', value: Description.defined} as Expected>}, [WARDuino.Inspect.globals]: {globals: {kind: 'description', value: Description.defined} as Expected>}, - [WARDuino.Inspect.table]: {table: {kind: 'description', value: Description.notDefined} as Expected}, - [WARDuino.Inspect.memory]: {memory: {kind: 'description', value: Description.notDefined} as Expected}, + [WARDuino.Inspect.table]: {table: {kind: 'description', value: Description.defined} as Expected}, + [WARDuino.Inspect.memory]: {memory: {kind: 'description', value: Description.defined} as Expected}, [WARDuino.Inspect.branching]: {branchTable: {kind: 'description', value: Description.defined} as Expected>}, [WARDuino.Inspect.stack]: {stack: {kind: 'description', value: Description.defined} as Expected>}, - [WARDuino.Inspect.callbacks]: {callbacks: {kind: 'description', value: Description.notDefined} as Expected}, - [WARDuino.Inspect.events]: {queue: {kind: 'description', value: Description.notDefined} as Expected}, + [WARDuino.Inspect.callbacks]: {callbacks: {kind: 'description', value: Description.defined} as Expected}, + [WARDuino.Inspect.events]: {queue: {kind: 'description', value: Description.defined} as Expected}, [WARDuino.Inspect.io]: {io: {kind: 'description', value: Description.defined} as Expected>} }; +const inspectSelectors = Object.values(WARDuino.Inspect) + .filter((value): value is WARDuino.Inspect => typeof value === 'number'); + integration.test({ - title: 'Test INSPECT selectors', + title: 'Test snapshot selectors', program: `${EXAMPLES}blink.wast`, steps: [ - ...(Object.keys(inspectExpectations) as WARDuino.Inspect[]).map((selector): Step => ({ - title: `Inspect ${selector}`, - instruction: {kind: Kind.Request, value: Message.inspect([selector])}, + ...inspectSelectors.map((selector): Step => ({ + title: `Inspect ${selector.toString(2).padStart(11, '0')}`, + instruction: {kind: Kind.Request, value: Message.snapshot([selector])}, expected: [inspectExpectations[selector]] })), { title: 'Inspect all selectors', - instruction: {kind: Kind.Request, value: Message.inspect(Object.keys(inspectExpectations) as WARDuino.Inspect[])}, - expected: (Object.keys(inspectExpectations) as WARDuino.Inspect[]).map((selector) => inspectExpectations[selector]) + instruction: {kind: Kind.Request, value: Message.snapshot(inspectSelectors)}, + expected: inspectSelectors.map((selector) => inspectExpectations[selector]) } as Step ] }); +const breakpointRequest = (type: DebugProtocol.Command): Request => ({ + type, + notification: DebugProtocol.NotificationType.NOTIFICATION_OPERATION_RESULT, + payload: () => DebugProtocol.CodeLocation.encode({moduleIndex: 0, programCounter: 169}).finish(), + parser: DebugProtocol.OperationResult.decode +}); + integration.test({ title: 'Test breakpoints', program: `${EXAMPLES}blink.wast`, @@ -296,7 +312,7 @@ integration.test({ program: `${EXAMPLES}blink.wast`, steps: [ {title: 'Reset runtime', instruction: {kind: Kind.Request, value: Message.reset}}, - {title: 'Check reset state', instruction: {kind: Kind.Request, value: Message.snapshot}, expected: expectSnapshot} + {title: 'Check reset state', instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: expectSnapshot} ] }); @@ -320,6 +336,11 @@ const proxyCallWithoutProxy: Request = { parser: DebugProtocol.OperationResult.decode }; +const pushEvent: Request = { + ...Message.pushEvent("test", "payload"), + parser: DebugProtocol.Event.decode +}; + const popEventWithoutCallback: Request = { ...Message.popEvent, parser: DebugProtocol.OperationResult.decode @@ -344,9 +365,9 @@ integration.test({ {title: "Update stack value", instruction: {kind: Kind.Request, value: Message.updateStack(0, {i32Bits: 8, index: 0})}, expected: operationSucceeded}, {title: "Update global value", instruction: {kind: Kind.Request, value: Message.updateGlobal(0, {i32Bits: 9, index: 0})}, expected: operationSucceeded}, {title: "Update callback mapping", instruction: {kind: Kind.Request, value: Message.updateCallbacks({entries: []})}, expected: operationSucceeded}, - {title: "Push an event", instruction: {kind: Kind.Request, value: Message.pushEvent("test", "payload")}}, + {title: "Push an event", instruction: {kind: Kind.Request, value: pushEvent}, expected: [{topic: {kind: "primitive", value: "test"} as Expected}]}, {title: "Pop event", instruction: {kind: Kind.Request, value: popEventWithoutCallback}, expected: [{success: {kind: "primitive", value: false} as Expected}]}, - {title: "Load snapshot state", instruction: {kind: Kind.Request, value: Message.loadSnapshot({programCounter: 0, state: DebugProtocol.State.STATE_WARDUINO_PAUSE, breakpoints: [], functions: [], callstack: [], globals: [], stack: [], branchTable: [], io: [], overrides: [], heapUsed: 0})}, expected: operationSucceeded}, + {title: "Load snapshot state", instruction: {kind: Kind.Request, value: Message.loadSnapshot({programCounter: 0, state: DebugProtocol.State.STATE_WARDUINO_PAUSE, breakpoints: [], functions: [], callstack: [], globals: [], stack: [], branchTable: [], io: [], overrides: []})}, expected: operationSucceeded}, {title: "Set snapshot policy", instruction: {kind: Kind.Request, value: Message.setSnapshotPolicy({policy: DebugProtocol.SnapshotPolicy.SNAPSHOT_POLICY_NONE, interval: 1, minimumReturnCount: 0, selectedState: Buffer.alloc(0)})}, expected: operationSucceeded}, {title: "Set primitive override", instruction: {kind: Kind.Request, value: Message.setOverride({primitiveName: "chip_delay", argumentWords: [1000], result: 0})}, expected: operationSucceeded}, {title: "Remove primitive override", instruction: {kind: Kind.Request, value: Message.removeOverride({primitiveName: "chip_delay", argumentWords: [1000], result: 0})}, expected: operationSucceeded}, @@ -362,7 +383,9 @@ integration.test({ program: `${EXAMPLES}blink.wast`, steps: [ {title: "Enable checkpointing", instruction: {kind: Kind.Request, value: Message.setSnapshotPolicy({policy: DebugProtocol.SnapshotPolicy.SNAPSHOT_POLICY_CHECKPOINTING, interval: 1, minimumReturnCount: 0, selectedState: Buffer.alloc(0)})}, expected: operationSucceeded}, - {title: "Continue for one instruction", instruction: {kind: Kind.Request, value: Message.continueFor({count: 1})}, expected: [{instructionCount: {kind: "description", value: Description.defined} as Expected}]} + {title: "Continue for one instruction", instruction: {kind: Kind.Request, value: Message.continueFor({count: 1})}, expected: [ + {instructionCount: {kind: "description", value: Description.defined} as Expected} + ]} ] }); @@ -372,5 +395,5 @@ integration.test({ steps: [{title: "Halt debugger connection", instruction: {kind: Kind.Request, value: Message.halt}}] }); -framework.reporter.verbosity(Verbosity.more); +framework.reporter.verbosity(Verbosity.all); framework.run([integration]); diff --git a/tests/latch/src/primitives.test.ts b/tests/latch/src/primitives.test.ts index 731a95dd..9515dc15 100644 --- a/tests/latch/src/primitives.test.ts +++ b/tests/latch/src/primitives.test.ts @@ -8,7 +8,8 @@ import { Message, TestScenario, WASM, - awaitBreakpoint, PureAction, Suite, Assertable, assertable + awaitBreakpoint, PureAction, Suite, Assertable, assertable, + WARDuino } from 'latch'; import * as mqtt from 'mqtt'; import Type = WASM.Type; @@ -81,7 +82,7 @@ const interrupts: TestScenario = { }] }, { title: 'CHECK: callback function registered for pin 36', - instruction: {kind: Kind.Request, value: Message.dumpCallbackmapping}, + instruction: {kind: Kind.Request, value: Message.snapshot([WARDuino.Inspect.callbacks])}, expected: [{ 'callbacks': { kind: 'comparison', @@ -118,7 +119,7 @@ const scenario: TestScenario = { // MQTT test scenario instruction: {kind: Kind.Request, value: Message.run}, }, { title: 'CHECK: callback function registered', - instruction: {kind: Kind.Request, value: Message.dumpCallbackmapping}, + instruction: {kind: Kind.Request, value: Message.snapshot([WARDuino.Inspect.callbacks])}, expected: [{ 'callbacks': { kind: 'comparison', @@ -138,7 +139,7 @@ const scenario: TestScenario = { // MQTT test scenario instruction: {kind: Kind.Action, value: awaitBreakpoint()} }, { title: 'CHECK: entered callback function', - instruction: {kind: Kind.Request, value: Message.dump}, + instruction: {kind: Kind.Request, value: Message.snapshot([])}, expected: [{ 'state': {kind: 'primitive', value: 'paused'}, 'line': {kind: 'primitive', value: 11},