From 24fc92f5881f73a1fbf69e0ebfffba1322e4f8c4 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Thu, 11 Apr 2024 22:44:23 +0200 Subject: [PATCH 1/3] Add back override interrupts But now they are better as they work at the VM level and are not just a little bit of code inside a primitive. They also override primitive + argument combinations instead of just overriding a pin which only works for analog and digital read primitives. --- src/Debug/debugger.cpp | 62 ++++++++++++++++++++++++++++++++ src/Debug/debugger.h | 14 +++++++- src/Interpreter/instructions.cpp | 11 ++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 8828f2fe..135919ab 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -1,6 +1,7 @@ #include "debugger.h" #include +#include #include #include #ifndef ARDUINO @@ -332,6 +333,14 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { this->dumpCallbackmapping(); free(interruptData); break; + case interruptSetOverridePinValue: + this->addOverride(m, interruptData + 1); + free(interruptData); + break; + case interruptUnsetOverridePinValue: + this->removeOverride(m, interruptData + 1); + free(interruptData); + break; default: // handle later this->channel->write("COULD not parse interrupt data!\n"); @@ -1411,6 +1420,59 @@ bool Debugger::reset(Module *m) const { return true; } +std::optional resolve_imported_function(Module *m, std::string function_name) { + for (uint32_t fidx = 0; fidx < m->import_count; fidx++) { + if (!strcmp(m->functions[fidx].import_field, function_name.c_str())) { + return fidx; + } + } + return {}; +} + +std::string read_string(uint8_t **pos) { + std::string str = ""; + char c = *(*pos)++; + while (c != '\0') { + str += c; + c = *(*pos)++; + } + return str; +} + +void Debugger::addOverride(Module *m, uint8_t *interruptData) { + std::string primitive_name = read_string(&interruptData); + uint32_t arg = read_B32(&interruptData); + uint32_t result = read_B32(&interruptData); + + std::optional fidx = resolve_imported_function(m, primitive_name); + if (!fidx) { + channel->write("Cannot override the result for unknown function \"%s\".\n", primitive_name.c_str()); + return; + } + + channel->write("Override %s(%d) = %d.\n", primitive_name.c_str(), arg, result); + overrides[fidx.value()][arg] = result; +} + +void Debugger::removeOverride(Module *m, uint8_t *interruptData) { + std::string primitive_name = read_string(&interruptData); + uint32_t arg = read_B32(&interruptData); + + std::optional fidx = resolve_imported_function(m, primitive_name); + if (!fidx) { + channel->write("Cannot remove override for unknown function \"%s\".\n", primitive_name.c_str()); + return; + } + + if (overrides[fidx.value()].count(arg) == 0) { + channel->write("Override for %s(%d) not found.\n", primitive_name.c_str(), arg); + return; + } + + channel->write("Removing override %s(%d) = %d.\n", primitive_name.c_str(), arg, overrides[fidx.value()][arg]); + overrides[fidx.value()].erase(arg); +} + Debugger::~Debugger() { this->disconnect_proxy(); this->stop(); diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 0dbb1cca..8f0309bb 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -90,10 +90,13 @@ enum InterruptTypes { interruptDUMPCallbackmapping = 0x74, interruptRecvCallbackmapping = 0x75, + // Primitive overrides + interruptSetOverridePinValue = 0x80, + interruptUnsetOverridePinValue = 0x81, + // Operations interruptStore = 0xa0, interruptStored = 0xa1, - }; class Debugger { @@ -117,6 +120,8 @@ class Debugger { bool asyncSnapshots; + std::unordered_map> overrides; + // Private methods void printValue(const StackValue *v, uint32_t idx, bool end) const; @@ -270,4 +275,11 @@ class Debugger { void notifyPushedEvent() const; bool handlePushedEvent(char *bytes) const; + + // Concolic Multiverse Debugging + inline bool isMocked(uint32_t fidx, uint32_t argument) { return overrides.count(fidx) > 0 && overrides[fidx].count(argument) > 0; } + inline uint32_t getMockedValue(uint32_t fidx, uint32_t argument) { return overrides[fidx][argument]; } + + void addOverride(Module *m, uint8_t *interruptData); + void removeOverride(Module *m, uint8_t *interruptData); }; diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index 9046c0d0..abee8542 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -287,6 +287,17 @@ bool i_instr_call(Module *m) { } if (fidx < m->import_count) { + // Mocking only works on primitives, no need to check for it otherwise. + if (m->sp >= 0) { + printf("Call primitive with arguments on the stack %s.\n", m->functions[fidx].import_field); + uint32_t arg = m->stack[m->sp].value.uint32; + if (m->warduino->debugger->isMocked(fidx, arg)) { + printf("Put result on the stack for arg %d %d\n", arg, m->warduino->debugger->getMockedValue(fidx, arg)); + m->stack[m->sp].value.uint32 = m->warduino->debugger->getMockedValue(fidx, arg); + return true; + } + } + return ((Primitive)m->functions[fidx].func_ptr)(m); } else { if (m->csp >= CALLSTACK_SIZE) { From 6a5a6ee1683bde19f69653e75c508d51d073a592 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Fri, 12 Apr 2024 12:18:22 +0200 Subject: [PATCH 2/3] Store overrides in snapshots This makes them work well with time travelling so a decision in the future doesn't affect the past anymore when stepping back in time. --- src/Debug/debugger.cpp | 47 ++++++++++++++++++++++++-------- src/Debug/debugger.h | 1 + src/Interpreter/instructions.cpp | 2 -- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 135919ab..5fe36d38 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -734,18 +734,11 @@ bool Debugger::handlePushedEvent(char *bytes) const { } void Debugger::snapshot(Module *m) const { - uint16_t numberBytes = 11; - uint8_t state[] = {pcState, - breakpointsState, - callstackState, - globalsState, - tableState, - memoryState, - branchingTableState, - stackState, - callbacksState, - eventsState, - ioState}; + uint16_t numberBytes = 12; + uint8_t state[] = { + pcState, breakpointsState, callstackState, globalsState, + tableState, memoryState, branchingTableState, stackState, + callbacksState, eventsState, ioState, overridesState}; inspect(m, numberBytes, state); } @@ -894,6 +887,23 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, addComma = true; break; } + case overridesState: { + this->channel->write("%s", addComma ? "," : ""); + this->channel->write(R"("overrides": [)"); + bool comma = false; + for (auto key : overrides) { + for (auto argResult : key.second) { + this->channel->write("%s", comma ? ", ": ""); + this->channel->write( + R"({"fidx": %d, "arg": %d, "return_value": %d})", + key.first, argResult.first, argResult.second); + comma = true; + } + } + this->channel->write("]"); + addComma = true; + break; + } default: { debug("dumpExecutionState: Received unknown state request\n"); break; @@ -1249,6 +1259,19 @@ bool Debugger::saveState(Module *m, uint8_t *interruptData) { restore_external_state(m, external_state); break; } + case overridesState: { + debug("receiving overridesState\n"); + overrides.clear(); + uint8_t overrides_count = *program_state++; + for (uint32_t i = 0; i < overrides_count; i++) { + uint32_t fidx = read_B32(&program_state); + uint32_t arg = read_B32(&program_state); + uint32_t return_value = read_B32(&program_state); + overrides[fidx][arg] = return_value; + debug("Override %d %d %d\n", fidx, arg, return_value); + } + break; + } default: { FATAL("saveState: Received unknown program state\n"); } diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 8f0309bb..3cff2581 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -49,6 +49,7 @@ enum ExecutionState { callbacksState = 0x09, eventsState = 0x0A, ioState = 0x0B, + overridesState = 0x0C, }; enum InterruptTypes { diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index abee8542..93190b6a 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -289,10 +289,8 @@ bool i_instr_call(Module *m) { if (fidx < m->import_count) { // Mocking only works on primitives, no need to check for it otherwise. if (m->sp >= 0) { - printf("Call primitive with arguments on the stack %s.\n", m->functions[fidx].import_field); uint32_t arg = m->stack[m->sp].value.uint32; if (m->warduino->debugger->isMocked(fidx, arg)) { - printf("Put result on the stack for arg %d %d\n", arg, m->warduino->debugger->getMockedValue(fidx, arg)); m->stack[m->sp].value.uint32 = m->warduino->debugger->getMockedValue(fidx, arg); return true; } From cbef5fa3319f610ea10650549f092231a10e1818 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Wed, 30 Oct 2024 15:17:49 +0100 Subject: [PATCH 3/3] Clang-format --- src/Debug/debugger.cpp | 39 ++++++++++++++++++++++---------- src/Debug/debugger.h | 11 ++++++--- src/Interpreter/instructions.cpp | 3 ++- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 5fe36d38..c2b2ce63 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -1,9 +1,9 @@ #include "debugger.h" #include -#include #include #include +#include #ifndef ARDUINO #include #else @@ -735,10 +735,18 @@ bool Debugger::handlePushedEvent(char *bytes) const { void Debugger::snapshot(Module *m) const { uint16_t numberBytes = 12; - uint8_t state[] = { - pcState, breakpointsState, callstackState, globalsState, - tableState, memoryState, branchingTableState, stackState, - callbacksState, eventsState, ioState, overridesState}; + uint8_t state[] = {pcState, + breakpointsState, + callstackState, + globalsState, + tableState, + memoryState, + branchingTableState, + stackState, + callbacksState, + eventsState, + ioState, + overridesState}; inspect(m, numberBytes, state); } @@ -893,7 +901,7 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, bool comma = false; for (auto key : overrides) { for (auto argResult : key.second) { - this->channel->write("%s", comma ? ", ": ""); + this->channel->write("%s", comma ? ", " : ""); this->channel->write( R"({"fidx": %d, "arg": %d, "return_value": %d})", key.first, argResult.first, argResult.second); @@ -1443,7 +1451,8 @@ bool Debugger::reset(Module *m) const { return true; } -std::optional resolve_imported_function(Module *m, std::string function_name) { +std::optional resolve_imported_function(Module *m, + std::string function_name) { for (uint32_t fidx = 0; fidx < m->import_count; fidx++) { if (!strcmp(m->functions[fidx].import_field, function_name.c_str())) { return fidx; @@ -1469,11 +1478,14 @@ void Debugger::addOverride(Module *m, uint8_t *interruptData) { std::optional fidx = resolve_imported_function(m, primitive_name); if (!fidx) { - channel->write("Cannot override the result for unknown function \"%s\".\n", primitive_name.c_str()); + channel->write( + "Cannot override the result for unknown function \"%s\".\n", + primitive_name.c_str()); return; } - channel->write("Override %s(%d) = %d.\n", primitive_name.c_str(), arg, result); + channel->write("Override %s(%d) = %d.\n", primitive_name.c_str(), arg, + result); overrides[fidx.value()][arg] = result; } @@ -1483,16 +1495,19 @@ void Debugger::removeOverride(Module *m, uint8_t *interruptData) { std::optional fidx = resolve_imported_function(m, primitive_name); if (!fidx) { - channel->write("Cannot remove override for unknown function \"%s\".\n", primitive_name.c_str()); + channel->write("Cannot remove override for unknown function \"%s\".\n", + primitive_name.c_str()); return; } if (overrides[fidx.value()].count(arg) == 0) { - channel->write("Override for %s(%d) not found.\n", primitive_name.c_str(), arg); + channel->write("Override for %s(%d) not found.\n", + primitive_name.c_str(), arg); return; } - channel->write("Removing override %s(%d) = %d.\n", primitive_name.c_str(), arg, overrides[fidx.value()][arg]); + channel->write("Removing override %s(%d) = %d.\n", primitive_name.c_str(), + arg, overrides[fidx.value()][arg]); overrides[fidx.value()].erase(arg); } diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 3cff2581..c43a00af 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -121,7 +121,8 @@ class Debugger { bool asyncSnapshots; - std::unordered_map> overrides; + std::unordered_map> + overrides; // Private methods @@ -278,8 +279,12 @@ class Debugger { bool handlePushedEvent(char *bytes) const; // Concolic Multiverse Debugging - inline bool isMocked(uint32_t fidx, uint32_t argument) { return overrides.count(fidx) > 0 && overrides[fidx].count(argument) > 0; } - inline uint32_t getMockedValue(uint32_t fidx, uint32_t argument) { return overrides[fidx][argument]; } + inline bool isMocked(uint32_t fidx, uint32_t argument) { + return overrides.count(fidx) > 0 && overrides[fidx].count(argument) > 0; + } + inline uint32_t getMockedValue(uint32_t fidx, uint32_t argument) { + return overrides[fidx][argument]; + } void addOverride(Module *m, uint8_t *interruptData); void removeOverride(Module *m, uint8_t *interruptData); diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index 93190b6a..0e4ceb3c 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -291,7 +291,8 @@ bool i_instr_call(Module *m) { if (m->sp >= 0) { uint32_t arg = m->stack[m->sp].value.uint32; if (m->warduino->debugger->isMocked(fidx, arg)) { - m->stack[m->sp].value.uint32 = m->warduino->debugger->getMockedValue(fidx, arg); + m->stack[m->sp].value.uint32 = + m->warduino->debugger->getMockedValue(fidx, arg); return true; } }