mirror of https://github.com/mgba-emu/mgba.git
Scripting: Add debugger integration
This commit is contained in:
parent
c1421afccb
commit
6561223536
1
CHANGES
1
CHANGES
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)");
|
||||
|
|
Loading…
Reference in New Issue