Skip to content

Commit 35bdf7b

Browse files
authored
✨ Primitive mocking (#269)
* 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. * 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. * Clang-format
1 parent f3d5668 commit 35bdf7b

File tree

3 files changed

+131
-3
lines changed

3 files changed

+131
-3
lines changed

src/Debug/debugger.cpp

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <algorithm>
44
#include <cinttypes>
55
#include <cstring>
6+
#include <optional>
67
#ifndef ARDUINO
78
#include <nlohmann/json.hpp>
89
#else
@@ -332,6 +333,14 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) {
332333
this->dumpCallbackmapping();
333334
free(interruptData);
334335
break;
336+
case interruptSetOverridePinValue:
337+
this->addOverride(m, interruptData + 1);
338+
free(interruptData);
339+
break;
340+
case interruptUnsetOverridePinValue:
341+
this->removeOverride(m, interruptData + 1);
342+
free(interruptData);
343+
break;
335344
default:
336345
// handle later
337346
this->channel->write("COULD not parse interrupt data!\n");
@@ -725,7 +734,7 @@ bool Debugger::handlePushedEvent(char *bytes) const {
725734
}
726735

727736
void Debugger::snapshot(Module *m) const {
728-
uint16_t numberBytes = 11;
737+
uint16_t numberBytes = 12;
729738
uint8_t state[] = {pcState,
730739
breakpointsState,
731740
callstackState,
@@ -736,7 +745,8 @@ void Debugger::snapshot(Module *m) const {
736745
stackState,
737746
callbacksState,
738747
eventsState,
739-
ioState};
748+
ioState,
749+
overridesState};
740750
inspect(m, numberBytes, state);
741751
}
742752

@@ -885,6 +895,23 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray,
885895
addComma = true;
886896
break;
887897
}
898+
case overridesState: {
899+
this->channel->write("%s", addComma ? "," : "");
900+
this->channel->write(R"("overrides": [)");
901+
bool comma = false;
902+
for (auto key : overrides) {
903+
for (auto argResult : key.second) {
904+
this->channel->write("%s", comma ? ", " : "");
905+
this->channel->write(
906+
R"({"fidx": %d, "arg": %d, "return_value": %d})",
907+
key.first, argResult.first, argResult.second);
908+
comma = true;
909+
}
910+
}
911+
this->channel->write("]");
912+
addComma = true;
913+
break;
914+
}
888915
default: {
889916
debug("dumpExecutionState: Received unknown state request\n");
890917
break;
@@ -1240,6 +1267,19 @@ bool Debugger::saveState(Module *m, uint8_t *interruptData) {
12401267
restore_external_state(m, external_state);
12411268
break;
12421269
}
1270+
case overridesState: {
1271+
debug("receiving overridesState\n");
1272+
overrides.clear();
1273+
uint8_t overrides_count = *program_state++;
1274+
for (uint32_t i = 0; i < overrides_count; i++) {
1275+
uint32_t fidx = read_B32(&program_state);
1276+
uint32_t arg = read_B32(&program_state);
1277+
uint32_t return_value = read_B32(&program_state);
1278+
overrides[fidx][arg] = return_value;
1279+
debug("Override %d %d %d\n", fidx, arg, return_value);
1280+
}
1281+
break;
1282+
}
12431283
default: {
12441284
FATAL("saveState: Received unknown program state\n");
12451285
}
@@ -1411,6 +1451,66 @@ bool Debugger::reset(Module *m) const {
14111451
return true;
14121452
}
14131453

1454+
std::optional<uint32_t> resolve_imported_function(Module *m,
1455+
std::string function_name) {
1456+
for (uint32_t fidx = 0; fidx < m->import_count; fidx++) {
1457+
if (!strcmp(m->functions[fidx].import_field, function_name.c_str())) {
1458+
return fidx;
1459+
}
1460+
}
1461+
return {};
1462+
}
1463+
1464+
std::string read_string(uint8_t **pos) {
1465+
std::string str = "";
1466+
char c = *(*pos)++;
1467+
while (c != '\0') {
1468+
str += c;
1469+
c = *(*pos)++;
1470+
}
1471+
return str;
1472+
}
1473+
1474+
void Debugger::addOverride(Module *m, uint8_t *interruptData) {
1475+
std::string primitive_name = read_string(&interruptData);
1476+
uint32_t arg = read_B32(&interruptData);
1477+
uint32_t result = read_B32(&interruptData);
1478+
1479+
std::optional<uint32_t> fidx = resolve_imported_function(m, primitive_name);
1480+
if (!fidx) {
1481+
channel->write(
1482+
"Cannot override the result for unknown function \"%s\".\n",
1483+
primitive_name.c_str());
1484+
return;
1485+
}
1486+
1487+
channel->write("Override %s(%d) = %d.\n", primitive_name.c_str(), arg,
1488+
result);
1489+
overrides[fidx.value()][arg] = result;
1490+
}
1491+
1492+
void Debugger::removeOverride(Module *m, uint8_t *interruptData) {
1493+
std::string primitive_name = read_string(&interruptData);
1494+
uint32_t arg = read_B32(&interruptData);
1495+
1496+
std::optional<uint32_t> fidx = resolve_imported_function(m, primitive_name);
1497+
if (!fidx) {
1498+
channel->write("Cannot remove override for unknown function \"%s\".\n",
1499+
primitive_name.c_str());
1500+
return;
1501+
}
1502+
1503+
if (overrides[fidx.value()].count(arg) == 0) {
1504+
channel->write("Override for %s(%d) not found.\n",
1505+
primitive_name.c_str(), arg);
1506+
return;
1507+
}
1508+
1509+
channel->write("Removing override %s(%d) = %d.\n", primitive_name.c_str(),
1510+
arg, overrides[fidx.value()][arg]);
1511+
overrides[fidx.value()].erase(arg);
1512+
}
1513+
14141514
Debugger::~Debugger() {
14151515
this->disconnect_proxy();
14161516
this->stop();

src/Debug/debugger.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ enum ExecutionState {
4949
callbacksState = 0x09,
5050
eventsState = 0x0A,
5151
ioState = 0x0B,
52+
overridesState = 0x0C,
5253
};
5354

5455
enum InterruptTypes {
@@ -90,10 +91,13 @@ enum InterruptTypes {
9091
interruptDUMPCallbackmapping = 0x74,
9192
interruptRecvCallbackmapping = 0x75,
9293

94+
// Primitive overrides
95+
interruptSetOverridePinValue = 0x80,
96+
interruptUnsetOverridePinValue = 0x81,
97+
9398
// Operations
9499
interruptStore = 0xa0,
95100
interruptStored = 0xa1,
96-
97101
};
98102

99103
class Debugger {
@@ -117,6 +121,9 @@ class Debugger {
117121

118122
bool asyncSnapshots;
119123

124+
std::unordered_map<uint32_t, std::unordered_map<uint32_t, uint32_t>>
125+
overrides;
126+
120127
// Private methods
121128

122129
void printValue(const StackValue *v, uint32_t idx, bool end) const;
@@ -270,4 +277,15 @@ class Debugger {
270277
void notifyPushedEvent() const;
271278

272279
bool handlePushedEvent(char *bytes) const;
280+
281+
// Concolic Multiverse Debugging
282+
inline bool isMocked(uint32_t fidx, uint32_t argument) {
283+
return overrides.count(fidx) > 0 && overrides[fidx].count(argument) > 0;
284+
}
285+
inline uint32_t getMockedValue(uint32_t fidx, uint32_t argument) {
286+
return overrides[fidx][argument];
287+
}
288+
289+
void addOverride(Module *m, uint8_t *interruptData);
290+
void removeOverride(Module *m, uint8_t *interruptData);
273291
};

src/Interpreter/instructions.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,16 @@ bool i_instr_call(Module *m) {
287287
}
288288

289289
if (fidx < m->import_count) {
290+
// Mocking only works on primitives, no need to check for it otherwise.
291+
if (m->sp >= 0) {
292+
uint32_t arg = m->stack[m->sp].value.uint32;
293+
if (m->warduino->debugger->isMocked(fidx, arg)) {
294+
m->stack[m->sp].value.uint32 =
295+
m->warduino->debugger->getMockedValue(fidx, arg);
296+
return true;
297+
}
298+
}
299+
290300
return ((Primitive)m->functions[fidx].func_ptr)(m);
291301
} else {
292302
if (m->csp >= CALLSTACK_SIZE) {

0 commit comments

Comments
 (0)