Scripting: Add debugger integration

This commit is contained in:
Vicki Pfau 2023-05-29 00:18:53 -07:00
parent c1421afccb
commit 6561223536
4 changed files with 722 additions and 9 deletions

View File

@ -2,6 +2,7 @@
Features:
- Scripting: New `input` API for getting raw keyboard/mouse/controller state
- Scripting: New `storage` API for saving data for a script, e.g. settings
- Scripting: Debugger integration to allow for breakpoints and watchpoints
- New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81
- Debugger: Add range watchpoints
Emulation fixes:

View File

@ -156,10 +156,39 @@ struct mScriptMemoryDomain {
struct mCoreMemoryBlock block;
};
#ifdef USE_DEBUGGERS
struct mScriptBreakpointName {
uint32_t address;
uint32_t maxAddress;
int segment : 9;
int type : 1;
int subtype : 3;
};
struct mScriptBreakpoint {
ssize_t id;
struct mScriptBreakpointName name;
struct Table callbacks;
};
struct mScriptCoreAdapter;
struct mScriptDebugger {
struct mDebuggerModule d;
struct mScriptCoreAdapter* p;
struct Table breakpoints;
struct Table cbidMap;
struct Table bpidMap;
int64_t nextBreakpoint;
};
#endif
struct mScriptCoreAdapter {
struct mCore* core;
struct mScriptContext* context;
struct mScriptValue memory;
#ifdef USE_DEBUGGERS
struct mScriptDebugger debugger;
#endif
};
struct mScriptConsole {
@ -659,9 +688,239 @@ static void _rebuildMemoryMap(struct mScriptContext* context, struct mScriptCore
}
}
#ifdef USE_DEBUGGERS
static void _freeBreakpoint(void* bp) {
struct mScriptBreakpoint* point = bp;
HashTableDeinit(&point->callbacks);
free(bp);
}
static struct mScriptBreakpoint* _ensureBreakpoint(struct mScriptDebugger* debugger, struct mBreakpoint* breakpoint) {
struct mDebuggerModule* module = &debugger->d;
struct mScriptBreakpointName name = {
.address = breakpoint->address,
.maxAddress = 0,
.segment = breakpoint->segment,
.type = 0,
.subtype = breakpoint->type
};
struct mScriptBreakpoint* point = HashTableLookupBinary(&debugger->breakpoints, &name, sizeof(name));
if (point) {
return point;
}
point = calloc(1, sizeof(*point));
point->id = module->p->platform->setBreakpoint(module->p->platform, module, breakpoint);
point->name = name;
HashTableInit(&point->callbacks, 0, (void (*)(void*)) mScriptValueDeref);
HashTableInsertBinary(&debugger->bpidMap, &point->id, sizeof(point->id), point);
HashTableInsertBinary(&debugger->breakpoints, &name, sizeof(name), point);
return point;
}
static struct mScriptBreakpoint* _ensureWatchpoint(struct mScriptDebugger* debugger, struct mWatchpoint* watchpoint) {
struct mDebuggerModule* module = &debugger->d;
struct mScriptBreakpointName name = {
.address = watchpoint->minAddress,
.maxAddress = watchpoint->maxAddress,
.segment = watchpoint->segment,
.type = 1,
.subtype = watchpoint->type
};
struct mScriptBreakpoint* point = HashTableLookupBinary(&debugger->breakpoints, &name, sizeof(name));
if (point) {
return point;
}
point = calloc(1, sizeof(*point));
point->id = module->p->platform->setWatchpoint(module->p->platform, module, watchpoint);
point->name = name;
HashTableInit(&point->callbacks, 0, (void (*)(void*)) mScriptValueDeref);
HashTableInsertBinary(&debugger->bpidMap, &point->id, sizeof(point->id), point);
HashTableInsertBinary(&debugger->breakpoints, &name, sizeof(name), point);
return point;
}
static int64_t _addCallbackToBreakpoint(struct mScriptDebugger* debugger, struct mScriptBreakpoint* point, struct mScriptValue* callback) {
int64_t cbid = debugger->nextBreakpoint;
++debugger->nextBreakpoint;
HashTableInsertBinary(&debugger->cbidMap, &cbid, sizeof(cbid), point);
mScriptValueRef(callback);
HashTableInsertBinary(&point->callbacks, &cbid, sizeof(cbid), callback);
return cbid;
}
static void _runCallbacks(struct mScriptBreakpoint* point) {
struct TableIterator iter;
if (!HashTableIteratorStart(&point->callbacks, &iter)) {
return;
}
do {
struct mScriptValue* fn = HashTableIteratorGetValue(&point->callbacks, &iter);
struct mScriptFrame frame;
mScriptFrameInit(&frame);
mScriptInvoke(fn, &frame);
mScriptFrameDeinit(&frame);
} while (HashTableIteratorNext(&point->callbacks, &iter));
}
static void _scriptDebuggerInit(struct mDebuggerModule* debugger) {
struct mScriptDebugger* scriptDebugger = (struct mScriptDebugger*) debugger;
debugger->isPaused = false;
debugger->needsCallback = false;
HashTableInit(&scriptDebugger->breakpoints, 0, _freeBreakpoint);
HashTableInit(&scriptDebugger->cbidMap, 0, NULL);
HashTableInit(&scriptDebugger->bpidMap, 0, NULL);
}
static void _scriptDebuggerDeinit(struct mDebuggerModule* debugger) {
struct mScriptDebugger* scriptDebugger = (struct mScriptDebugger*) debugger;
HashTableDeinit(&scriptDebugger->cbidMap);
HashTableDeinit(&scriptDebugger->bpidMap);
HashTableDeinit(&scriptDebugger->breakpoints);
}
static void _scriptDebuggerPaused(struct mDebuggerModule* debugger, int32_t timeoutMs) {
UNUSED(debugger);
UNUSED(timeoutMs);
}
static void _scriptDebuggerUpdate(struct mDebuggerModule* debugger) {
UNUSED(debugger);
}
static void _scriptDebuggerEntered(struct mDebuggerModule* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {
struct mScriptDebugger* scriptDebugger = (struct mScriptDebugger*) debugger;
struct mScriptBreakpoint* point;
switch (reason) {
case DEBUGGER_ENTER_BREAKPOINT:
case DEBUGGER_ENTER_WATCHPOINT:
point = HashTableLookupBinary(&scriptDebugger->bpidMap, &info->pointId, sizeof(info->pointId));
break;
default:
return;
}
_runCallbacks(point);
debugger->isPaused = false;
}
static void _scriptDebuggerCustom(struct mDebuggerModule* debugger) {
UNUSED(debugger);
}
static void _scriptDebuggerInterrupt(struct mDebuggerModule* debugger) {
UNUSED(debugger);
}
static bool _setupDebugger(struct mScriptCoreAdapter* adapter) {
if (!adapter->core->debugger) {
return false;
}
if (adapter->debugger.d.p) {
return true;
}
adapter->debugger.p = adapter;
adapter->debugger.d.type = DEBUGGER_CUSTOM;
adapter->debugger.d.init = _scriptDebuggerInit;
adapter->debugger.d.deinit = _scriptDebuggerDeinit;
adapter->debugger.d.paused = _scriptDebuggerPaused;
adapter->debugger.d.update = _scriptDebuggerUpdate;
adapter->debugger.d.entered = _scriptDebuggerEntered;
adapter->debugger.d.custom = _scriptDebuggerCustom;
adapter->debugger.d.interrupt = _scriptDebuggerInterrupt;
adapter->debugger.d.isPaused = false;
adapter->debugger.d.needsCallback = false;
adapter->debugger.nextBreakpoint = 1;
mDebuggerAttachModule(adapter->core->debugger, &adapter->debugger.d);
return true;
}
static int64_t _mScriptCoreAdapterSetBreakpoint(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback, uint32_t address, int32_t segment) {
if (!_setupDebugger(adapter)) {
return -1;
}
struct mBreakpoint breakpoint = {
.address = address,
.segment = segment,
.type = BREAKPOINT_HARDWARE
};
struct mDebuggerModule* module = &adapter->debugger.d;
if (!module->p->platform->setBreakpoint) {
return -1;
}
struct mScriptBreakpoint* point = _ensureBreakpoint(&adapter->debugger, &breakpoint);
return _addCallbackToBreakpoint(&adapter->debugger, point, callback);
}
static int64_t _mScriptCoreAdapterSetWatchpoint(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback, uint32_t address, int type, int32_t segment) {
if (!_setupDebugger(adapter)) {
return -1;
}
struct mWatchpoint watchpoint = {
.minAddress = address,
.maxAddress = address + 1,
.segment = segment,
.type = type,
};
struct mDebuggerModule* module = &adapter->debugger.d;
if (!module->p->platform->setWatchpoint) {
return -1;
}
struct mScriptBreakpoint* point = _ensureWatchpoint(&adapter->debugger, &watchpoint);
return _addCallbackToBreakpoint(&adapter->debugger, point, callback);
}
static int64_t _mScriptCoreAdapterSetRangeWatchpoint(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback, uint32_t minAddress, uint32_t maxAddress, int type, int32_t segment) {
if (!_setupDebugger(adapter)) {
return -1;
}
struct mWatchpoint watchpoint = {
.minAddress = minAddress,
.maxAddress = maxAddress,
.segment = segment,
.type = type,
};
struct mDebuggerModule* module = &adapter->debugger.d;
if (!module->p->platform->setWatchpoint) {
return -1;
}
struct mScriptBreakpoint* point = _ensureWatchpoint(&adapter->debugger, &watchpoint);
return _addCallbackToBreakpoint(&adapter->debugger, point, callback);
}
static bool _mScriptCoreAdapterClearBreakpoint(struct mScriptCoreAdapter* adapter, int64_t cbid) {
if (!_setupDebugger(adapter)) {
return false;
}
struct mScriptBreakpoint* point = HashTableLookupBinary(&adapter->debugger.cbidMap, &cbid, sizeof(cbid));
if (!point) {
return false;
}
HashTableRemoveBinary(&adapter->debugger.cbidMap, &cbid, sizeof(cbid));
HashTableRemoveBinary(&point->callbacks, &cbid, sizeof(cbid));
if (!HashTableSize(&point->callbacks)) {
struct mDebuggerModule* module = &adapter->debugger.d;
module->p->platform->clearBreakpoint(module->p->platform, point->id);
struct mScriptBreakpointName name = point->name;
HashTableRemoveBinary(&adapter->debugger.breakpoints, &name, sizeof(name));
}
return true;
}
#endif
static void _mScriptCoreAdapterDeinit(struct mScriptCoreAdapter* adapter) {
_clearMemoryMap(adapter->context, adapter, false);
adapter->memory.type->free(&adapter->memory);
#ifdef USE_DEBUGGERS
if (adapter->core->debugger) {
mDebuggerDetachModule(adapter->core->debugger, &adapter->debugger.d);
}
#endif
}
static struct mScriptValue* _mScriptCoreAdapterGet(struct mScriptCoreAdapter* adapter, const char* name) {
@ -686,6 +945,33 @@ mSCRIPT_DECLARE_STRUCT(mScriptCoreAdapter);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, W(mCore), _get, _mScriptCoreAdapterGet, 1, CHARP, name);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, _deinit, _mScriptCoreAdapterDeinit, 0);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, reset, _mScriptCoreAdapterReset, 0);
#ifdef USE_DEBUGGERS
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setBreakpoint, _mScriptCoreAdapterSetBreakpoint, 3, WRAPPER, callback, U32, address, S32, segment);
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setWatchpoint, _mScriptCoreAdapterSetWatchpoint, 4, WRAPPER, callback, U32, address, S32, type, S32, segment);
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setRangeWatchpoint, _mScriptCoreAdapterSetRangeWatchpoint, 5, WRAPPER, callback, U32, minAddress, U32, maxAddress, S32, type, S32, segment);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, BOOL, clearBreakpoint, _mScriptCoreAdapterClearBreakpoint, 1, S64, cbid);
#endif
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptCoreAdapter, setBreakpoint)
mSCRIPT_NO_DEFAULT,
mSCRIPT_NO_DEFAULT,
mSCRIPT_S32(-1)
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptCoreAdapter, setWatchpoint)
mSCRIPT_NO_DEFAULT,
mSCRIPT_NO_DEFAULT,
mSCRIPT_NO_DEFAULT,
mSCRIPT_S32(-1)
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptCoreAdapter, setRangeWatchpoint)
mSCRIPT_NO_DEFAULT,
mSCRIPT_NO_DEFAULT,
mSCRIPT_NO_DEFAULT,
mSCRIPT_NO_DEFAULT,
mSCRIPT_S32(-1)
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT(mScriptCoreAdapter)
mSCRIPT_DEFINE_CLASS_DOCSTRING(
@ -700,6 +986,19 @@ mSCRIPT_DEFINE_STRUCT(mScriptCoreAdapter)
mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptCoreAdapter)
mSCRIPT_DEFINE_DOCSTRING("Reset the emulation. As opposed to struct::mCore.reset, this version calls the **reset** callback")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, reset)
#ifdef USE_DEBUGGERS
mSCRIPT_DEFINE_DOCSTRING("Set a breakpoint at a given address")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setBreakpoint)
mSCRIPT_DEFINE_DOCSTRING("Clear a breakpoint or watchpoint for a given id returned by a previous call")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, clearBreakpoint)
mSCRIPT_DEFINE_DOCSTRING("Set a watchpoint at a given address of a given type")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setWatchpoint)
mSCRIPT_DEFINE_DOCSTRING(
"Set a watchpoint in a given range of a given type. Note that the range is exclusive on the end, "
"as though you've added the size, i.e. a 4-byte watch would specify the maximum as the minimum address + 4"
)
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setRangeWatchpoint)
#endif
mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(mScriptCoreAdapter, S(mCore), _core)
mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(mScriptCoreAdapter, CS(mCore), _core)
mSCRIPT_DEFINE_END;

View File

@ -9,8 +9,7 @@
#include <mgba/core/log.h>
#include <mgba/core/scripting.h>
#include <mgba/internal/script/lua.h>
#include <mgba/script/context.h>
#include <mgba/script/types.h>
#include <mgba/script.h>
#include "script/test.h"
@ -143,8 +142,8 @@ M_TEST_DEFINE(globals) {
LOAD_PROGRAM("assert(emu)");
assert_true(lua->run(lua));
TEARDOWN_CORE;
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(infoFuncs) {
@ -161,8 +160,8 @@ M_TEST_DEFINE(infoFuncs) {
TEST_VALUE(S32, "frequency", core->frequency(core));
TEST_VALUE(S32, "frameCycles", core->frameCycles(core));
TEARDOWN_CORE;
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(detach) {
@ -195,8 +194,8 @@ M_TEST_DEFINE(detach) {
);
assert_false(lua->run(lua));
TEARDOWN_CORE;
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(runFrame) {
@ -215,8 +214,8 @@ M_TEST_DEFINE(runFrame) {
TEST_VALUE(S32, "frame", i);
}
TEARDOWN_CORE;
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(memoryRead) {
@ -250,8 +249,8 @@ M_TEST_DEFINE(memoryRead) {
TEST_VALUE(S32, "b16", 0x0807);
TEST_VALUE(S32, "a32", 0x0C0B0A09);
TEARDOWN_CORE;
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(memoryWrite) {
@ -278,8 +277,8 @@ M_TEST_DEFINE(memoryWrite) {
assert_int_equal(core->busRead8(core, RAM_BASE + i), i + 1);
}
TEARDOWN_CORE;
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(logging) {
@ -323,11 +322,402 @@ M_TEST_DEFINE(screenshot) {
TEST_PROGRAM("assert(im.width >= 160)");
TEST_PROGRAM("assert(im.height >= 144)");
TEARDOWN_CORE;
free(buffer);
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
#ifdef USE_DEBUGGERS
void _setupBp(struct mCore* core) {
switch (core->platform(core)) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
core->busWrite32(core, 0x020000C0, 0xE0000000); // nop
core->busWrite32(core, 0x020000C4, 0xE0000000); // nop
core->busWrite32(core, 0x020000C8, 0xEAFFFFFD); // b 0x020000C4
break;
#endif
#ifdef M_CORE_GB
case mPLATFORM_GB:
core->rawWrite8(core, 0x101, 0, 0xEE); // Jump to 0xF0
core->rawWrite8(core, 0xF0, 0, 0x00); // nop
core->rawWrite8(core, 0xF1, 0, 0x18); // Loop forecer
core->rawWrite8(core, 0xF2, 0, 0xFD); // jr $-3
break;
#endif
}
}
#ifdef M_CORE_GBA
M_TEST_DEFINE(basicBreakpointGBA) {
SETUP_LUA;
struct mCore* core = mCoreCreate(mPLATFORM_GBA);
struct mDebugger debugger;
assert_non_null(core);
assert_true(core->init(core));
mCoreInitConfig(core, NULL);
core->reset(core);
_setupBp(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt()\n"
" hit = hit + 1\n"
"end"
);
TEST_PROGRAM("cbid = emu:setBreakpoint(bkpt, 0x020000C4)");
TEST_PROGRAM("assert(cbid == 1)");
int i;
for (i = 0; i < 20; ++i) {
mDebuggerRun(&debugger);
}
assert_int_equal(debugger.state, DEBUGGER_RUNNING);
TEST_PROGRAM("assert(hit >= 1)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
#endif
#ifdef M_CORE_GB
M_TEST_DEFINE(basicBreakpointGB) {
SETUP_LUA;
struct mCore* core = mCoreCreate(mPLATFORM_GB);
struct mDebugger debugger;
assert_non_null(core);
assert_true(core->init(core));
mCoreInitConfig(core, NULL);
assert_true(core->loadROM(core, VFileFromConstMemory(_fakeGBROM, sizeof(_fakeGBROM))));
core->reset(core);
_setupBp(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt()\n"
" hit = hit + 1\n"
"end"
);
TEST_PROGRAM("cbid = emu:setBreakpoint(bkpt, 0xF0)");
TEST_PROGRAM("assert(cbid == 1)");
int i;
for (i = 0; i < 20; ++i) {
mDebuggerRun(&debugger);
}
assert_int_equal(debugger.state, DEBUGGER_RUNNING);
TEST_PROGRAM("assert(hit >= 1)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
#endif
M_TEST_DEFINE(multipleBreakpoint) {
SETUP_LUA;
struct mCore* core = mCoreCreate(TEST_PLATFORM);
struct mDebugger debugger;
assert_non_null(core);
assert_true(core->init(core));
mCoreInitConfig(core, NULL);
core->reset(core);
_setupBp(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt1()\n"
" hit = hit + 1\n"
"end\n"
"function bkpt2()\n"
" hit = hit + 100\n"
"end"
);
#ifdef M_CORE_GBA
TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0x020000C4)");
TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0x020000C8)");
#else
TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0xF0)");
TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0xF1)");
#endif
TEST_PROGRAM("assert(cbid1 == 1)");
TEST_PROGRAM("assert(cbid2 == 2)");
int i;
for (i = 0; i < 20; ++i) {
mDebuggerRun(&debugger);
}
assert_int_equal(debugger.state, DEBUGGER_RUNNING);
TEST_PROGRAM("assert(hit >= 101)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(basicWatchpoint) {
SETUP_LUA;
mScriptContextAttachStdlib(&context);
CREATE_CORE;
struct mDebugger debugger;
core->reset(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt()\n"
" hit = hit + 1\n"
"end"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 1, C.WATCHPOINT_TYPE.WRITE))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 2, C.WATCHPOINT_TYPE.RW))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 3, C.WATCHPOINT_TYPE.WRITE_CHANGE))");
TEST_PROGRAM("assert(hit == 0)");
uint8_t value;
// Read
TEST_PROGRAM("hit = 0");
value = core->rawRead8(core, RAM_BASE, -1);
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 1)");
core->busWrite8(core, RAM_BASE, value);
TEST_PROGRAM("assert(hit == 1)");
core->busWrite8(core, RAM_BASE, ~value);
TEST_PROGRAM("assert(hit == 1)");
// Write
TEST_PROGRAM("hit = 0");
value = core->rawRead8(core, RAM_BASE + 1, -1);
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE + 1);
TEST_PROGRAM("assert(hit == 0)");
core->busWrite8(core, RAM_BASE + 1, value);
TEST_PROGRAM("assert(hit == 1)");
core->busWrite8(core, RAM_BASE + 1, ~value);
TEST_PROGRAM("assert(hit == 2)");
// RW
TEST_PROGRAM("hit = 0");
value = core->rawRead8(core, RAM_BASE + 2, -1);
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE + 2);
TEST_PROGRAM("assert(hit == 1)");
core->busWrite8(core, RAM_BASE + 2, value);
TEST_PROGRAM("assert(hit == 2)");
core->busWrite8(core, RAM_BASE + 2, ~value);
TEST_PROGRAM("assert(hit == 3)");
// Change
TEST_PROGRAM("hit = 0");
value = core->rawRead8(core, RAM_BASE + 3, -1);
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE + 3);
TEST_PROGRAM("assert(hit == 0)");
core->busWrite8(core, RAM_BASE + 3, value);
TEST_PROGRAM("assert(hit == 0)");
core->busWrite8(core, RAM_BASE + 3, ~value);
TEST_PROGRAM("assert(hit == 1)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(removeBreakpoint) {
SETUP_LUA;
mScriptContextAttachStdlib(&context);
CREATE_CORE;
struct mDebugger debugger;
core->reset(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt()\n"
" hit = hit + 1\n"
"end"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
TEST_PROGRAM("cbid = emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ)");
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 1)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 2)");
TEST_PROGRAM("assert(emu:clearBreakpoint(cbid))");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 2)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(overlappingBreakpoint) {
SETUP_LUA;
struct mCore* core = mCoreCreate(TEST_PLATFORM);
struct mDebugger debugger;
assert_non_null(core);
assert_true(core->init(core));
mCoreInitConfig(core, NULL);
core->reset(core);
_setupBp(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt1()\n"
" hit = hit + 1\n"
"end\n"
"function bkpt2()\n"
" hit = hit + 100\n"
"end"
);
#ifdef M_CORE_GBA
TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0x020000C4)");
TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0x020000C4)");
#else
TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0xF0)");
TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0xF0)");
#endif
TEST_PROGRAM("assert(cbid1 == 1)");
TEST_PROGRAM("assert(cbid2 == 2)");
int i;
for (i = 0; i < 20; ++i) {
mDebuggerRun(&debugger);
}
assert_int_equal(debugger.state, DEBUGGER_RUNNING);
TEST_PROGRAM("assert(hit >= 101)");
TEST_PROGRAM("oldHit = hit");
TEST_PROGRAM("assert(emu:clearBreakpoint(cbid2))");
for (i = 0; i < 10; ++i) {
mDebuggerRun(&debugger);
}
TEST_PROGRAM("assert(hit - oldHit > 0)");
TEST_PROGRAM("assert(hit - oldHit < 100)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(overlappingWatchpoint) {
SETUP_LUA;
mScriptContextAttachStdlib(&context);
CREATE_CORE;
struct mDebugger debugger;
core->reset(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt()\n"
" hit = hit + 1\n"
"end"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.WRITE))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.RW))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.WRITE_CHANGE))");
TEST_PROGRAM("assert(hit == 0)");
uint8_t value;
// Read
TEST_PROGRAM("hit = 0");
value = core->rawRead8(core, RAM_BASE, -1);
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 2)"); // Read, RW
core->busWrite8(core, RAM_BASE, value);
TEST_PROGRAM("assert(hit == 4)"); // Write, RW
core->busWrite8(core, RAM_BASE, ~value);
TEST_PROGRAM("assert(hit == 7)"); // Write, RW, change
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(rangeWatchpoint) {
SETUP_LUA;
mScriptContextAttachStdlib(&context);
CREATE_CORE;
struct mDebugger debugger;
core->reset(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt()\n"
" hit = hit + 1\n"
"end"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
TEST_PROGRAM("assert(0 < emu:setRangeWatchpoint(bkpt, base, base + 2, C.WATCHPOINT_TYPE.READ))");
TEST_PROGRAM("assert(0 < emu:setRangeWatchpoint(bkpt, base + 1, base + 3, C.WATCHPOINT_TYPE.READ))");
// Read
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 1)");
core->busRead8(core, RAM_BASE + 1);
TEST_PROGRAM("assert(hit == 3)");
core->busRead8(core, RAM_BASE + 2);
TEST_PROGRAM("assert(hit == 4)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
#endif
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore,
cmocka_unit_test(globals),
cmocka_unit_test(infoFuncs),
@ -337,4 +727,18 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore,
cmocka_unit_test(memoryWrite),
cmocka_unit_test(logging),
cmocka_unit_test(screenshot),
#ifdef USE_DEBUGGERS
#ifdef M_CORE_GBA
cmocka_unit_test(basicBreakpointGBA),
#endif
#ifdef M_CORE_GB
cmocka_unit_test(basicBreakpointGB),
#endif
cmocka_unit_test(multipleBreakpoint),
cmocka_unit_test(basicWatchpoint),
cmocka_unit_test(removeBreakpoint),
cmocka_unit_test(overlappingBreakpoint),
cmocka_unit_test(overlappingWatchpoint),
cmocka_unit_test(rangeWatchpoint),
#endif
)

View File

@ -158,6 +158,15 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) {
mSCRIPT_CONSTANT_PAIR(GB_KEY, DOWN),
mSCRIPT_KV_SENTINEL
});
#endif
#ifdef USE_DEBUGGERS
mScriptContextExportConstants(context, "WATCHPOINT_TYPE", (struct mScriptKVPair[]) {
mSCRIPT_CONSTANT_PAIR(WATCHPOINT, WRITE),
mSCRIPT_CONSTANT_PAIR(WATCHPOINT, READ),
mSCRIPT_CONSTANT_PAIR(WATCHPOINT, RW),
mSCRIPT_CONSTANT_PAIR(WATCHPOINT, WRITE_CHANGE),
mSCRIPT_KV_SENTINEL
});
#endif
mScriptContextSetGlobal(context, "C", context->constants);
mScriptContextSetDocstring(context, "C", "A table containing the [exported constants](#constants)");