mirror of https://github.com/mgba-emu/mgba.git
Debugger: Initial memory access logger support
This commit is contained in:
parent
9c673f527d
commit
d55a13c9ba
|
@ -24,6 +24,7 @@ enum mDebuggerType {
|
|||
DEBUGGER_CUSTOM,
|
||||
DEBUGGER_CLI,
|
||||
DEBUGGER_GDB,
|
||||
DEBUGGER_ACCESS_LOGGER,
|
||||
DEBUGGER_MAX
|
||||
};
|
||||
|
||||
|
@ -69,6 +70,7 @@ struct mDebuggerModule;
|
|||
struct mDebuggerEntryInfo {
|
||||
uint32_t address;
|
||||
int segment;
|
||||
int width;
|
||||
union {
|
||||
struct {
|
||||
uint32_t oldValue;
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef ACCESS_LOGGER_H
|
||||
#define ACCESS_LOGGER_H
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/debugger/debugger.h>
|
||||
#include <mgba-util/vector.h>
|
||||
|
||||
DECL_BITFIELD(mDebuggerAccessLogFlags, uint8_t);
|
||||
DECL_BIT(mDebuggerAccessLogFlags, Read, 0);
|
||||
DECL_BIT(mDebuggerAccessLogFlags, Write, 1);
|
||||
DECL_BIT(mDebuggerAccessLogFlags, Execute, 2);
|
||||
DECL_BIT(mDebuggerAccessLogFlags, Abort, 3);
|
||||
DECL_BIT(mDebuggerAccessLogFlags, Access8, 4);
|
||||
DECL_BIT(mDebuggerAccessLogFlags, Access16, 5);
|
||||
DECL_BIT(mDebuggerAccessLogFlags, Access32, 6);
|
||||
DECL_BIT(mDebuggerAccessLogFlags, Access64, 7);
|
||||
|
||||
DECL_BITFIELD(mDebuggerAccessLogFlagsEx, uint16_t);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, AccessProgram, 0);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, AccessDMA, 1);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, AccessSystem, 2);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, AccessDecompress, 3);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, AccessCopy, 4);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, ErrorIllegalOpcode, 8);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, ErrorAccessRead, 9);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, ErrorAccessWrite, 10);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, ErrorAccessExecute, 11);
|
||||
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, ExecuteARM, 14);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, ExecuteThumb, 15);
|
||||
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, ExecuteOpcode, 14);
|
||||
DECL_BIT(mDebuggerAccessLogFlagsEx, ExecuteOperand, 15);
|
||||
|
||||
DECL_BITFIELD(mDebuggerAccessLogRegionFlags, uint64_t);
|
||||
DECL_BIT(mDebuggerAccessLogRegionFlags, HasExBlock, 0);
|
||||
|
||||
struct mDebuggerAccessLogRegion {
|
||||
uint32_t start;
|
||||
uint32_t end;
|
||||
uint32_t size;
|
||||
uint32_t segmentStart;
|
||||
mDebuggerAccessLogFlags* block;
|
||||
mDebuggerAccessLogFlagsEx* blockEx;
|
||||
};
|
||||
|
||||
DECLARE_VECTOR(mDebuggerAccessLogRegionList, struct mDebuggerAccessLogRegion);
|
||||
|
||||
struct mDebuggerAccessLog;
|
||||
struct mDebuggerAccessLogger {
|
||||
struct mDebuggerModule d;
|
||||
struct VFile* backing;
|
||||
struct mDebuggerAccessLog* mapped;
|
||||
struct mDebuggerAccessLogRegionList regions;
|
||||
};
|
||||
|
||||
void mDebuggerAccessLoggerInit(struct mDebuggerAccessLogger*);
|
||||
void mDebuggerAccessLoggerDeinit(struct mDebuggerAccessLogger*);
|
||||
|
||||
bool mDebuggerAccessLoggerOpen(struct mDebuggerAccessLogger*, struct VFile*, int mode);
|
||||
bool mDebuggerAccessLoggerClose(struct mDebuggerAccessLogger*);
|
||||
|
||||
int mDebuggerAccessLoggerWatchMemoryBlockId(struct mDebuggerAccessLogger*, size_t id, mDebuggerAccessLogRegionFlags);
|
||||
int mDebuggerAccessLoggerWatchMemoryBlockName(struct mDebuggerAccessLogger*, const char* internalName, mDebuggerAccessLogRegionFlags);
|
||||
|
||||
bool mDebuggerAccessLoggerCreateShadowFile(struct mDebuggerAccessLogger*, int region, struct VFile*, uint8_t fill);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -119,9 +119,11 @@ static void _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, en
|
|||
struct mDebuggerEntryInfo info;
|
||||
info.type.wp.oldValue = oldValue;
|
||||
info.type.wp.newValue = newValue;
|
||||
info.address = address;
|
||||
info.type.wp.watchType = watchpoint->type;
|
||||
info.type.wp.accessType = type;
|
||||
info.address = address;
|
||||
info.segment = 0;
|
||||
info.width = width;
|
||||
info.pointId = watchpoint->id;
|
||||
info.target = TableLookup(&debugger->d.p->pointOwner, watchpoint->id);
|
||||
mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
include(ExportDirectory)
|
||||
set(SOURCE_FILES
|
||||
access-logger.c
|
||||
cli-debugger.c
|
||||
debugger.c
|
||||
parser.c
|
||||
|
|
|
@ -0,0 +1,579 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/internal/debugger/access-logger.h>
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/internal/arm/isa-inlines.h>
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/sm83/sm83.h>
|
||||
#endif
|
||||
|
||||
#define DEFAULT_MAX_REGIONS 20
|
||||
|
||||
const char mAL_MAGIC[] = "mAL\1";
|
||||
|
||||
DEFINE_VECTOR(mDebuggerAccessLogRegionList, struct mDebuggerAccessLogRegion);
|
||||
|
||||
DECL_BITFIELD(mDebuggerAccessLogHeaderFlags, uint64_t);
|
||||
|
||||
struct mDebuggerAccessLogRegionInfo {
|
||||
uint32_t start;
|
||||
uint32_t end;
|
||||
uint32_t size;
|
||||
uint32_t segmentStart;
|
||||
uint64_t fileOffset;
|
||||
uint64_t fileOffsetEx;
|
||||
mDebuggerAccessLogRegionFlags flags;
|
||||
uint64_t reserved;
|
||||
};
|
||||
static_assert(sizeof(struct mDebuggerAccessLogRegionInfo) == 0x30, "mDebuggerAccessLogRegionInfo struct sized wrong");
|
||||
|
||||
struct mDebuggerAccessLogHeader {
|
||||
char magic[4];
|
||||
uint32_t version;
|
||||
mDebuggerAccessLogHeaderFlags flags;
|
||||
uint8_t nRegions;
|
||||
uint8_t regionCapacity;
|
||||
uint16_t padding;
|
||||
uint32_t platform;
|
||||
uint8_t reserved[0x28];
|
||||
};
|
||||
static_assert(sizeof(struct mDebuggerAccessLogHeader) == 0x40, "mDebuggerAccessLogHeader struct sized wrong");
|
||||
|
||||
struct mDebuggerAccessLog {
|
||||
struct mDebuggerAccessLogHeader header;
|
||||
struct mDebuggerAccessLogRegionInfo regionInfo[];
|
||||
};
|
||||
|
||||
static void _mDebuggerAccessLoggerEntered(struct mDebuggerModule* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {
|
||||
struct mDebuggerAccessLogger* logger = (struct mDebuggerAccessLogger*) debugger;
|
||||
logger->d.isPaused = false;
|
||||
|
||||
switch (reason) {
|
||||
case DEBUGGER_ENTER_MANUAL:
|
||||
case DEBUGGER_ENTER_ATTACHED:
|
||||
return;
|
||||
case DEBUGGER_ENTER_BREAKPOINT:
|
||||
case DEBUGGER_ENTER_STACK:
|
||||
mLOG(DEBUGGER, WARN, "Hit unexpected access logger entry type %i", reason);
|
||||
return;
|
||||
case DEBUGGER_ENTER_WATCHPOINT:
|
||||
case DEBUGGER_ENTER_ILLEGAL_OP:
|
||||
break;
|
||||
}
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < mDebuggerAccessLogRegionListSize(&logger->regions); ++i) {
|
||||
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, i);
|
||||
if (info->address < region->start || info->address >= region->end) {
|
||||
continue;
|
||||
}
|
||||
size_t offset = info->address - region->start;
|
||||
if (info->segment > 0) {
|
||||
uint32_t segmentSize = region->end - region->segmentStart;
|
||||
offset %= segmentSize;
|
||||
offset += segmentSize * info->segment;
|
||||
}
|
||||
|
||||
if (offset >= region->size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
offset &= -info->width;
|
||||
|
||||
int j;
|
||||
switch (reason) {
|
||||
case DEBUGGER_ENTER_WATCHPOINT:
|
||||
for (j = 0; j < info->width; ++j) {
|
||||
if (info->type.wp.accessType & WATCHPOINT_WRITE) {
|
||||
region->block[offset + j] = mDebuggerAccessLogFlagsFillWrite(region->block[offset + j]);
|
||||
}
|
||||
if (info->type.wp.accessType & WATCHPOINT_READ) {
|
||||
region->block[offset + j] = mDebuggerAccessLogFlagsFillRead(region->block[offset + j]);
|
||||
}
|
||||
}
|
||||
switch (info->width) {
|
||||
case 1:
|
||||
region->block[offset] = mDebuggerAccessLogFlagsFillAccess8(region->block[offset]);
|
||||
break;
|
||||
case 2:
|
||||
region->block[offset] = mDebuggerAccessLogFlagsFillAccess16(region->block[offset]);
|
||||
region->block[offset + 1] = mDebuggerAccessLogFlagsFillAccess16(region->block[offset + 1]);
|
||||
break;
|
||||
case 4:
|
||||
region->block[offset] = mDebuggerAccessLogFlagsFillAccess32(region->block[offset]);
|
||||
region->block[offset + 1] = mDebuggerAccessLogFlagsFillAccess32(region->block[offset + 1]);
|
||||
region->block[offset + 2] = mDebuggerAccessLogFlagsFillAccess32(region->block[offset + 2]);
|
||||
region->block[offset + 3] = mDebuggerAccessLogFlagsFillAccess32(region->block[offset + 3]);
|
||||
break;
|
||||
case 8:
|
||||
region->block[offset] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset]);
|
||||
region->block[offset + 1] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 1]);
|
||||
region->block[offset + 2] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 2]);
|
||||
region->block[offset + 3] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 3]);
|
||||
region->block[offset + 4] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 4]);
|
||||
region->block[offset + 5] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 5]);
|
||||
region->block[offset + 6] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 6]);
|
||||
region->block[offset + 7] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 7]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DEBUGGER_ENTER_ILLEGAL_OP:
|
||||
region->block[offset] = mDebuggerAccessLogFlagsFillExecute(region->block[offset]);
|
||||
if (region->blockEx) {
|
||||
uint16_t ex;
|
||||
LOAD_16LE(ex, 0, ®ion->blockEx[offset]);
|
||||
ex = mDebuggerAccessLogFlagsExFillErrorIllegalOpcode(ex);
|
||||
STORE_16LE(ex, 0, ®ion->blockEx[offset]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _mDebuggerAccessLoggerCallback(struct mDebuggerModule* debugger) {
|
||||
struct mDebuggerAccessLogger* logger = (struct mDebuggerAccessLogger*) debugger;
|
||||
|
||||
struct mCore* core = logger->d.p->core;
|
||||
enum mPlatform platform = core->platform(core);
|
||||
uint32_t address;
|
||||
int segment = -1;
|
||||
unsigned width = 1;
|
||||
|
||||
switch (platform) {
|
||||
// TODO: Move to debugger->platform
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
address = ((struct ARMCore*) core->cpu)->gprs[ARM_PC];
|
||||
width = ((struct ARMCore*) core->cpu)->executionMode == MODE_THUMB ? 2 : 4;
|
||||
address -= width;
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
address = ((struct SM83Core*) core->cpu)->regs.pc;
|
||||
segment = ((struct SM83Core*) core->cpu)->memory.currentSegment(core->cpu, address);
|
||||
break;
|
||||
#endif
|
||||
case mPLATFORM_NONE:
|
||||
return;
|
||||
}
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < mDebuggerAccessLogRegionListSize(&logger->regions); ++i) {
|
||||
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, i);
|
||||
if (address < region->start || address >= region->end) {
|
||||
continue;
|
||||
}
|
||||
size_t offset = address - region->start;
|
||||
if (segment > 0) {
|
||||
uint32_t segmentSize = region->end - region->segmentStart;
|
||||
offset %= segmentSize;
|
||||
offset += segmentSize * segment;
|
||||
}
|
||||
|
||||
if (offset >= region->size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t j;
|
||||
for (j = 0; j < width; ++j) {
|
||||
region->block[offset + j] = mDebuggerAccessLogFlagsFillExecute(region->block[offset + j]);
|
||||
|
||||
switch (platform) {
|
||||
// TODO: Move to debugger->platform
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
if (((struct ARMCore*) core->cpu)->executionMode == MODE_ARM) {
|
||||
region->block[offset + j] = mDebuggerAccessLogFlagsFillAccess32(region->block[offset + j]);
|
||||
if (region->blockEx) {
|
||||
uint16_t ex;
|
||||
LOAD_16LE(ex, 0, ®ion->blockEx[offset + j]);
|
||||
ex = mDebuggerAccessLogFlagsExFillExecuteARM(ex);
|
||||
STORE_16LE(ex, 0, ®ion->blockEx[offset + j]);
|
||||
}
|
||||
} else {
|
||||
region->block[offset + j] = mDebuggerAccessLogFlagsFillAccess16(region->block[offset]);
|
||||
if (region->blockEx) {
|
||||
uint16_t ex;
|
||||
LOAD_16LE(ex, 0, ®ion->blockEx[offset + j]);
|
||||
ex = mDebuggerAccessLogFlagsExFillExecuteThumb(ex);
|
||||
STORE_16LE(ex, 0, ®ion->blockEx[offset + j]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
region->block[offset + j] = mDebuggerAccessLogFlagsFillAccess8(region->block[offset + j]);
|
||||
if (region->blockEx) {
|
||||
uint16_t ex;
|
||||
LOAD_16LE(ex, 0, ®ion->blockEx[offset + j]);
|
||||
ex = mDebuggerAccessLogFlagsExFillExecuteOpcode(ex);
|
||||
STORE_16LE(ex, 0, ®ion->blockEx[offset + j]);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case mPLATFORM_NONE:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mDebuggerAccessLoggerInit(struct mDebuggerAccessLogger* logger) {
|
||||
memset(logger, 0, sizeof(*logger));
|
||||
mDebuggerAccessLogRegionListInit(&logger->regions, 1);
|
||||
|
||||
logger->d.type = DEBUGGER_ACCESS_LOGGER;
|
||||
logger->d.entered = _mDebuggerAccessLoggerEntered;
|
||||
logger->d.custom = _mDebuggerAccessLoggerCallback;
|
||||
}
|
||||
|
||||
void mDebuggerAccessLoggerDeinit(struct mDebuggerAccessLogger* logger) {
|
||||
mDebuggerAccessLoggerClose(logger);
|
||||
mDebuggerAccessLogRegionListDeinit(&logger->regions);
|
||||
}
|
||||
|
||||
static bool _mapRegion(struct mDebuggerAccessLogger* logger, struct mDebuggerAccessLogRegion* region, const struct mDebuggerAccessLogRegionInfo* info) {
|
||||
uint64_t offset;
|
||||
mDebuggerAccessLogRegionFlags flags;
|
||||
|
||||
LOAD_64LE(offset, 0, &info->fileOffset);
|
||||
LOAD_64LE(flags, 0, &info->flags);
|
||||
|
||||
#if __SIZEOF_SIZE_T__ <= 4
|
||||
if (offset >= 0x100000000ULL) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (offset < sizeof(struct mDebuggerAccessLogHeader)) {
|
||||
return false;
|
||||
}
|
||||
ssize_t fileEnd = logger->backing->size(logger->backing);
|
||||
if (fileEnd < 0) {
|
||||
return false;
|
||||
}
|
||||
if ((size_t) fileEnd <= offset) {
|
||||
return false;
|
||||
}
|
||||
if ((size_t) fileEnd < offset + region->size * sizeof(mDebuggerAccessLogFlags)) {
|
||||
return false;
|
||||
}
|
||||
region->block = (mDebuggerAccessLogFlags*) ((uintptr_t) logger->mapped + offset);
|
||||
|
||||
if (mDebuggerAccessLogRegionFlagsIsHasExBlock(flags)) {
|
||||
LOAD_64LE(offset, 0, &info->fileOffsetEx);
|
||||
#if __SIZEOF_SIZE_T__ <= 4
|
||||
if (offset >= 0x100000000ULL) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (offset) {
|
||||
if (offset < sizeof(struct mDebuggerAccessLogHeader)) {
|
||||
return false;
|
||||
}
|
||||
if ((size_t) fileEnd <= offset) {
|
||||
return false;
|
||||
}
|
||||
if ((size_t) fileEnd < offset + region->size * sizeof(mDebuggerAccessLogFlagsEx)) {
|
||||
return false;
|
||||
}
|
||||
region->blockEx = (mDebuggerAccessLogFlagsEx*) ((uintptr_t) logger->mapped + offset);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _setupRegion(struct mDebuggerAccessLogger* logger, struct mDebuggerAccessLogRegion* region, const struct mDebuggerAccessLogRegionInfo* info) {
|
||||
if (!_mapRegion(logger, region, info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct mWatchpoint wp = {
|
||||
.segment = -1,
|
||||
.minAddress = region->start,
|
||||
.maxAddress = region->end,
|
||||
.type = WATCHPOINT_RW,
|
||||
};
|
||||
logger->d.p->platform->setWatchpoint(logger->d.p->platform, &logger->d, &wp);
|
||||
mDebuggerModuleSetNeedsCallback(&logger->d);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _remapAll(struct mDebuggerAccessLogger* logger) {
|
||||
size_t i;
|
||||
for (i = 0; i < mDebuggerAccessLogRegionListSize(&logger->regions); ++i) {
|
||||
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, i);
|
||||
if (!_mapRegion(logger, region, &logger->mapped->regionInfo[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mDebuggerAccessLoggerLoad(struct mDebuggerAccessLogger* logger) {
|
||||
if (memcmp(logger->mapped->header.magic, mAL_MAGIC, sizeof(logger->mapped->header.magic)) != 0) {
|
||||
return false;
|
||||
}
|
||||
uint32_t version;
|
||||
LOAD_32LE(version, 0, &logger->mapped->header.version);
|
||||
if (version != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
enum mPlatform platform;
|
||||
LOAD_32LE(platform, 0, &logger->mapped->header.platform);
|
||||
if (platform != logger->d.p->core->platform(logger->d.p->core)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mDebuggerAccessLogRegionListClear(&logger->regions);
|
||||
mDebuggerAccessLogRegionListResize(&logger->regions, logger->mapped->header.nRegions);
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < logger->mapped->header.nRegions; ++i) {
|
||||
struct mDebuggerAccessLogRegionInfo* info = &logger->mapped->regionInfo[i];
|
||||
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, i);
|
||||
memset(region, 0, sizeof(*region));
|
||||
LOAD_32LE(region->start, 0, &info->start);
|
||||
LOAD_32LE(region->end, 0, &info->end);
|
||||
LOAD_32LE(region->size, 0, &info->size);
|
||||
LOAD_32LE(region->segmentStart, 0, &info->segmentStart);
|
||||
if (!_setupRegion(logger, region, info)) {
|
||||
mDebuggerAccessLogRegionListClear(&logger->regions);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mDebuggerAccessLoggerOpen(struct mDebuggerAccessLogger* logger, struct VFile* vf, int mode) {
|
||||
if (logger->backing && !mDebuggerAccessLoggerClose(logger)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t size = vf->size(vf);
|
||||
if (size < 0) {
|
||||
return false;
|
||||
}
|
||||
if ((size_t) size < sizeof(struct mDebuggerAccessLogHeader)) {
|
||||
if (!(mode & O_CREAT)) {
|
||||
return false;
|
||||
}
|
||||
vf->truncate(vf, sizeof(struct mDebuggerAccessLogHeader) + DEFAULT_MAX_REGIONS * sizeof(struct mDebuggerAccessLogRegionInfo));
|
||||
size = sizeof(struct mDebuggerAccessLogHeader);
|
||||
}
|
||||
|
||||
logger->mapped = vf->map(vf, size, MAP_WRITE);
|
||||
if (!logger->mapped) {
|
||||
return false;
|
||||
}
|
||||
logger->backing = vf;
|
||||
bool loaded = false;
|
||||
if (!(mode & O_TRUNC)) {
|
||||
loaded = mDebuggerAccessLoggerLoad(logger);
|
||||
}
|
||||
if ((mode & O_CREAT) && ((mode & O_TRUNC) || !loaded)) {
|
||||
memset(logger->mapped, 0, sizeof(*logger->mapped));
|
||||
memcpy(logger->mapped->header.magic, mAL_MAGIC, sizeof(logger->mapped->header.magic));
|
||||
STORE_32LE(1, 0, &logger->mapped->header.version);
|
||||
STORE_32LE(DEFAULT_MAX_REGIONS, 0, &logger->mapped->header.regionCapacity);
|
||||
vf->sync(vf, NULL, 0);
|
||||
loaded = true;
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
static int _mDebuggerAccessLoggerWatchMemoryBlock(struct mDebuggerAccessLogger* logger, const struct mCoreMemoryBlock* block, mDebuggerAccessLogRegionFlags flags) {
|
||||
if (mDebuggerAccessLogRegionListSize(&logger->regions) >= logger->mapped->header.regionCapacity) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(block->flags & mCORE_MEMORY_MAPPED)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t fileEnd = logger->backing->size(logger->backing);
|
||||
if (fileEnd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < mDebuggerAccessLogRegionListSize(&logger->regions); ++i) {
|
||||
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, i);
|
||||
if (region->start != block->start) {
|
||||
continue;
|
||||
}
|
||||
if (region->end != block->end) {
|
||||
continue;
|
||||
}
|
||||
if (region->size != block->size) {
|
||||
continue;
|
||||
}
|
||||
if (!region->blockEx && mDebuggerAccessLogRegionFlagsIsHasExBlock(flags)) {
|
||||
size_t size = block->size * sizeof(mDebuggerAccessLogFlagsEx);
|
||||
|
||||
logger->backing->unmap(logger->backing, logger->mapped, fileEnd);
|
||||
logger->backing->truncate(logger->backing, fileEnd + size);
|
||||
logger->mapped = logger->backing->map(logger->backing, fileEnd + size, MAP_WRITE);
|
||||
|
||||
struct mDebuggerAccessLogRegionInfo* info = &logger->mapped->regionInfo[i];
|
||||
mDebuggerAccessLogRegionFlags oldFlags;
|
||||
LOAD_64LE(oldFlags, 0, &info->flags);
|
||||
oldFlags = mDebuggerAccessLogRegionFlagsFillHasExBlock(oldFlags);
|
||||
|
||||
STORE_64LE(fileEnd, 0, &info->fileOffsetEx);
|
||||
STORE_64LE(oldFlags, 0, &info->flags);
|
||||
|
||||
logger->backing->sync(logger->backing, logger->mapped, sizeof(struct mDebuggerAccessLogHeader) + logger->mapped->header.regionCapacity * sizeof(struct mDebuggerAccessLogRegionInfo));
|
||||
|
||||
_remapAll(logger);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
#if __SIZEOF_SIZE_T__ <= 4
|
||||
if (block->size >= 0x80000000) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
size_t size = block->size * sizeof(mDebuggerAccessLogFlags);
|
||||
if (mDebuggerAccessLogRegionFlagsIsHasExBlock(flags)) {
|
||||
size += block->size * sizeof(mDebuggerAccessLogFlagsEx);
|
||||
}
|
||||
logger->backing->unmap(logger->backing, logger->mapped, fileEnd);
|
||||
logger->backing->truncate(logger->backing, fileEnd + size);
|
||||
logger->mapped = logger->backing->map(logger->backing, fileEnd + size, MAP_WRITE);
|
||||
|
||||
_remapAll(logger);
|
||||
|
||||
int id = mDebuggerAccessLogRegionListSize(&logger->regions);
|
||||
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListAppend(&logger->regions);
|
||||
memset(region, 0, sizeof(*region));
|
||||
region->start = block->start;
|
||||
region->end = block->end;
|
||||
region->size = block->size;
|
||||
region->segmentStart = block->segmentStart;
|
||||
region->block = (mDebuggerAccessLogFlags*) ((uintptr_t) logger->backing + fileEnd);
|
||||
|
||||
struct mDebuggerAccessLogRegionInfo* info = &logger->mapped->regionInfo[id];
|
||||
STORE_32LE(region->start, 0, &info->start);
|
||||
STORE_32LE(region->end, 0, &info->end);
|
||||
STORE_32LE(region->segmentStart, 0, &info->segmentStart);
|
||||
STORE_64LE(fileEnd, 0, &info->fileOffset);
|
||||
if (mDebuggerAccessLogRegionFlagsIsHasExBlock(flags)) {
|
||||
STORE_64LE(fileEnd + block->size * sizeof(mDebuggerAccessLogFlags), 0, &info->fileOffsetEx);
|
||||
} else {
|
||||
STORE_64LE(0, 0, &info->fileOffsetEx);
|
||||
}
|
||||
STORE_64LE(flags, 0, &info->flags);
|
||||
STORE_64LE(0, 0, &info->reserved);
|
||||
STORE_32LE(region->size, 0, &info->size);
|
||||
|
||||
logger->mapped->header.nRegions = id + 1;
|
||||
|
||||
logger->backing->sync(logger->backing, logger->mapped, sizeof(struct mDebuggerAccessLogHeader) + logger->mapped->header.regionCapacity * sizeof(struct mDebuggerAccessLogRegionInfo));
|
||||
if (!_setupRegion(logger, region, info)) {
|
||||
return -1;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
bool mDebuggerAccessLoggerClose(struct mDebuggerAccessLogger* logger) {
|
||||
if (!logger->backing) {
|
||||
return true;
|
||||
}
|
||||
mDebuggerAccessLogRegionListClear(&logger->regions);
|
||||
logger->backing->unmap(logger->backing, logger->mapped, logger->backing->size(logger->backing));
|
||||
logger->mapped = NULL;
|
||||
logger->backing->close(logger->backing);
|
||||
logger->backing = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
int mDebuggerAccessLoggerWatchMemoryBlockId(struct mDebuggerAccessLogger* logger, size_t id, mDebuggerAccessLogRegionFlags flags) {
|
||||
struct mCore* core = logger->d.p->core;
|
||||
const struct mCoreMemoryBlock* blocks;
|
||||
size_t nBlocks = core->listMemoryBlocks(core, &blocks);
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < nBlocks; ++i) {
|
||||
if (blocks[i].id == id) {
|
||||
return _mDebuggerAccessLoggerWatchMemoryBlock(logger, &blocks[i], flags);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int mDebuggerAccessLoggerWatchMemoryBlockName(struct mDebuggerAccessLogger* logger, const char* internalName, mDebuggerAccessLogRegionFlags flags) {
|
||||
struct mCore* core = logger->d.p->core;
|
||||
const struct mCoreMemoryBlock* blocks;
|
||||
size_t nBlocks = core->listMemoryBlocks(core, &blocks);
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < nBlocks; ++i) {
|
||||
if (strcmp(blocks[i].internalName, internalName) == 0) {
|
||||
return _mDebuggerAccessLoggerWatchMemoryBlock(logger, &blocks[i], flags);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool mDebuggerAccessLoggerCreateShadowFile(struct mDebuggerAccessLogger* logger, int regionId, struct VFile* vf, uint8_t fill) {
|
||||
if (regionId < 0) {
|
||||
return false;
|
||||
}
|
||||
if ((unsigned) regionId >= mDebuggerAccessLogRegionListSize(&logger->regions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vf->seek(vf, 0, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
struct mCore* core = logger->d.p->core;
|
||||
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, regionId);
|
||||
vf->truncate(vf, region->size);
|
||||
|
||||
uint8_t buffer[0x800];
|
||||
int segment = 0;
|
||||
uint32_t segmentAddress = region->start;
|
||||
uint32_t segmentEnd = region->end;
|
||||
|
||||
uint64_t i;
|
||||
for (i = 0; i < region->size; ++i) {
|
||||
if (segmentAddress == region->segmentStart && segmentAddress != region->start) {
|
||||
++segment;
|
||||
}
|
||||
if (segmentAddress == segmentEnd) {
|
||||
segmentAddress = region->segmentStart;
|
||||
++segment;
|
||||
}
|
||||
if (region->block[i]) {
|
||||
buffer[i & 0x7FF] = core->rawRead8(core, segmentAddress, segment);
|
||||
} else {
|
||||
buffer[i & 0x7FF] = fill;
|
||||
}
|
||||
if (i && (i & 0x7FF) == 0x7FF) {
|
||||
if (vf->write(vf, buffer, 0x800) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
++segmentAddress;
|
||||
}
|
||||
if (i & 0x7FF) {
|
||||
if (vf->write(vf, buffer, i & 0x7FF) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -125,6 +125,7 @@ set(SOURCE_FILES
|
|||
LogView.cpp
|
||||
LogWidget.cpp
|
||||
MapView.cpp
|
||||
MemoryAccessLogView.cpp
|
||||
MemoryDump.cpp
|
||||
MemoryModel.cpp
|
||||
MemorySearch.cpp
|
||||
|
@ -173,6 +174,7 @@ set(UI_FILES
|
|||
LoadSaveState.ui
|
||||
LogView.ui
|
||||
MapView.ui
|
||||
MemoryAccessLogView.ui
|
||||
MemoryDump.ui
|
||||
MemorySearch.ui
|
||||
MemoryView.ui
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "MemoryAccessLogView.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "GBAApp.h"
|
||||
#include "VFileDevice.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
MemoryAccessLogView::MemoryAccessLogView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_controller(controller)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
connect(m_ui.browse, &QAbstractButton::clicked, this, &MemoryAccessLogView::selectFile);
|
||||
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.start, &QWidget::setDisabled);
|
||||
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.stop, &QWidget::setEnabled);
|
||||
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.filename, &QWidget::setDisabled);
|
||||
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.browse, &QWidget::setDisabled);
|
||||
|
||||
mCore* core = m_controller->thread()->core;
|
||||
const mCoreMemoryBlock* info;
|
||||
size_t nBlocks = core->listMemoryBlocks(core, &info);
|
||||
|
||||
QVBoxLayout* regionBox = static_cast<QVBoxLayout*>(m_ui.regionBox->layout());
|
||||
for (size_t i = 0; i < nBlocks; ++i) {
|
||||
if (!(info[i].flags & mCORE_MEMORY_MAPPED)) {
|
||||
continue;
|
||||
}
|
||||
QCheckBox* region = new QCheckBox(QString::fromUtf8(info[i].longName));
|
||||
regionBox->addWidget(region);
|
||||
|
||||
QString name(QString::fromUtf8(info[i].internalName));
|
||||
m_regionBoxes[name] = region;
|
||||
connect(region, &QAbstractButton::toggled, this, [this, name](bool checked) {
|
||||
updateRegion(name, checked);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MemoryAccessLogView::~MemoryAccessLogView() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void MemoryAccessLogView::updateRegion(const QString& internalName, bool checked) {
|
||||
if (checked) {
|
||||
m_watchedRegions += internalName;
|
||||
} else {
|
||||
m_watchedRegions -= internalName;
|
||||
}
|
||||
if (!m_active) {
|
||||
return;
|
||||
}
|
||||
m_regionBoxes[internalName]->setEnabled(false);
|
||||
mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, internalName.toUtf8().constData(), activeFlags());
|
||||
}
|
||||
|
||||
void MemoryAccessLogView::start() {
|
||||
int flags = O_CREAT | O_RDWR;
|
||||
if (!m_ui.loadExisting->isChecked()) {
|
||||
flags |= O_TRUNC;
|
||||
}
|
||||
VFile* vf = VFileDevice::open(m_ui.filename->text(), flags);
|
||||
if (!vf) {
|
||||
// log error
|
||||
return;
|
||||
}
|
||||
mDebuggerAccessLoggerInit(&m_logger);
|
||||
m_controller->attachDebuggerModule(&m_logger.d);
|
||||
if (!mDebuggerAccessLoggerOpen(&m_logger, vf, flags)) {
|
||||
mDebuggerAccessLoggerDeinit(&m_logger);
|
||||
// log error
|
||||
return;
|
||||
}
|
||||
|
||||
m_active = true;
|
||||
emit loggingChanged(true);
|
||||
for (const auto& region : m_watchedRegions) {
|
||||
m_regionBoxes[region]->setEnabled(false);
|
||||
mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, region.toUtf8().constData(), activeFlags());
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryAccessLogView::stop() {
|
||||
if (!m_active) {
|
||||
return;
|
||||
}
|
||||
m_controller->detachDebuggerModule(&m_logger.d);
|
||||
mDebuggerAccessLoggerDeinit(&m_logger);
|
||||
emit loggingChanged(false);
|
||||
for (const auto& region : m_watchedRegions) {
|
||||
m_regionBoxes[region]->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryAccessLogView::selectFile() {
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select access log file"), tr("Memory access logs (*.mal)"));
|
||||
if (!filename.isEmpty()) {
|
||||
m_ui.filename->setText(filename);
|
||||
}
|
||||
}
|
||||
|
||||
mDebuggerAccessLogRegionFlags MemoryAccessLogView::activeFlags() const {
|
||||
mDebuggerAccessLogRegionFlags loggerFlags = 0;
|
||||
if (m_ui.logExtra->isChecked()) {
|
||||
loggerFlags = mDebuggerAccessLogRegionFlagsFillHasExBlock(loggerFlags);
|
||||
}
|
||||
return loggerFlags;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QSet>
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "CoreController.h"
|
||||
|
||||
#include <mgba/internal/debugger/access-logger.h>
|
||||
|
||||
#include "ui_MemoryAccessLogView.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class MemoryAccessLogView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MemoryAccessLogView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||
~MemoryAccessLogView();
|
||||
|
||||
private slots:
|
||||
void updateRegion(const QString& internalName, bool enable);
|
||||
void selectFile();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void loggingChanged(bool active);
|
||||
|
||||
private:
|
||||
Ui::MemoryAccessLogView m_ui;
|
||||
|
||||
std::shared_ptr<CoreController> m_controller;
|
||||
QSet<QString> m_watchedRegions;
|
||||
QHash<QString, QCheckBox*> m_regionBoxes;
|
||||
struct mDebuggerAccessLogger m_logger;
|
||||
bool m_active = false;
|
||||
|
||||
mDebuggerAccessLogRegionFlags activeFlags() const;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QGBA::MemoryAccessLogView</class>
|
||||
<widget class="QWidget" name="QGBA::MemoryAccessLogView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>385</width>
|
||||
<height>380</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Memory access logging</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="start">
|
||||
<property name="text">
|
||||
<string>Start</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="stop">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Stop</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Log file</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="browse">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLineEdit" name="filename"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="logExtra">
|
||||
<property name="text">
|
||||
<string>Log additional information (uses 3× space)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="loadExisting">
|
||||
<property name="text">
|
||||
<string>Load existing file if present</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="regionBox">
|
||||
<property name="title">
|
||||
<string>Regions</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout"/>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>start</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>QGBA::MemoryAccessLogView</receiver>
|
||||
<slot>start()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>97</x>
|
||||
<y>357</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>192</x>
|
||||
<y>189</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>stop</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>QGBA::MemoryAccessLogView</receiver>
|
||||
<slot>stop()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>287</x>
|
||||
<y>357</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>192</x>
|
||||
<y>189</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>start()</slot>
|
||||
<slot>stop()</slot>
|
||||
</slots>
|
||||
</ui>
|
|
@ -43,6 +43,7 @@
|
|||
#include "LoadSaveState.h"
|
||||
#include "LogView.h"
|
||||
#include "MapView.h"
|
||||
#include "MemoryAccessLogView.h"
|
||||
#include "MemorySearch.h"
|
||||
#include "MemoryView.h"
|
||||
#include "MultiplayerController.h"
|
||||
|
@ -1711,6 +1712,8 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "stateViews");
|
||||
addGameAction(tr("View &I/O registers..."), "ioViewer", openControllerTView<IOViewer>(), "stateViews");
|
||||
|
||||
addGameAction(tr("Log memory &accesses..."), "memoryAccessView", openControllerTView<MemoryAccessLogView>(), "tools");
|
||||
|
||||
#if defined(USE_FFMPEG) && defined(M_CORE_GBA)
|
||||
m_actions.addSeparator("tools");
|
||||
m_actions.addAction(tr("Convert e-Reader card image to raw..."), "parseCard", this, &Window::parseCard, "tools");
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
#include <mgba/core/config.h>
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/serialize.h>
|
||||
#include <mgba/gb/core.h>
|
||||
#include <mgba/gba/core.h>
|
||||
#include <mgba/debugger/debugger.h>
|
||||
#include <mgba/internal/debugger/access-logger.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
|
||||
#include <mgba/feature/commandline.h>
|
||||
|
@ -20,19 +20,21 @@
|
|||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
#define FUZZ_OPTIONS "F:NO:S:V:"
|
||||
#define FUZZ_OPTIONS "F:M:NO:S:V:"
|
||||
#define FUZZ_USAGE \
|
||||
"Additional options:\n" \
|
||||
" -F FRAMES Run for the specified number of FRAMES before exiting\n" \
|
||||
" -N Disable video rendering entirely\n" \
|
||||
" -O OFFSET Offset to apply savestate overlay\n" \
|
||||
" -V FILE Overlay a second savestate over the loaded savestate\n" \
|
||||
" -M FILE Attach a memory access log file\n" \
|
||||
|
||||
struct FuzzOpts {
|
||||
bool noVideo;
|
||||
int frames;
|
||||
size_t overlayOffset;
|
||||
char* ssOverlay;
|
||||
char* accessLog;
|
||||
};
|
||||
|
||||
static void _fuzzRunloop(struct mCore* core, int frames);
|
||||
|
@ -119,6 +121,22 @@ int main(int argc, char** argv) {
|
|||
|
||||
core->reset(core);
|
||||
|
||||
struct mDebugger debugger;
|
||||
struct mDebuggerAccessLogger accessLog;
|
||||
bool hasDebugger = false;
|
||||
|
||||
mDebuggerInit(&debugger);
|
||||
|
||||
if (fuzzOpts.accessLog) {
|
||||
mDebuggerAttach(&debugger, core);
|
||||
|
||||
struct VFile* vf = VFileOpen(fuzzOpts.accessLog, O_RDWR);
|
||||
mDebuggerAccessLoggerInit(&accessLog);
|
||||
mDebuggerAttachModule(&debugger, &accessLog.d);
|
||||
mDebuggerAccessLoggerOpen(&accessLog, vf, O_RDWR);
|
||||
hasDebugger = true;
|
||||
}
|
||||
|
||||
struct mCheatDevice* device;
|
||||
if (args.cheatsFile && (device = core->cheatDevice(core))) {
|
||||
struct VFile* vf = VFileOpen(args.cheatsFile, O_RDONLY);
|
||||
|
@ -146,11 +164,17 @@ int main(int argc, char** argv) {
|
|||
savestate = 0;
|
||||
}
|
||||
|
||||
blip_set_rates(core->getAudioChannel(core, 0), GBA_ARM7TDMI_FREQUENCY, 0x8000);
|
||||
blip_set_rates(core->getAudioChannel(core, 1), GBA_ARM7TDMI_FREQUENCY, 0x8000);
|
||||
blip_set_rates(core->getAudioChannel(core, 0), core->frequency(core), 0x8000);
|
||||
blip_set_rates(core->getAudioChannel(core, 1), core->frequency(core), 0x8000);
|
||||
|
||||
_fuzzRunloop(core, fuzzOpts.frames);
|
||||
|
||||
if (hasDebugger) {
|
||||
core->detachDebugger(core);
|
||||
mDebuggerAccessLoggerDeinit(&accessLog);
|
||||
mDebuggerDeinit(&debugger);
|
||||
}
|
||||
|
||||
core->unloadROM(core);
|
||||
|
||||
if (savestate) {
|
||||
|
@ -192,6 +216,9 @@ static bool _parseFuzzOpts(struct mSubParser* parser, int option, const char* ar
|
|||
case 'F':
|
||||
opts->frames = strtoul(arg, 0, 10);
|
||||
return !errno;
|
||||
case 'M':
|
||||
opts->accessLog = strdup(arg);
|
||||
return true;
|
||||
case 'N':
|
||||
opts->noVideo = true;
|
||||
return true;
|
||||
|
|
|
@ -59,10 +59,11 @@ static void _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, e
|
|||
struct mDebuggerEntryInfo info;
|
||||
info.type.wp.oldValue = oldValue;
|
||||
info.type.wp.newValue = newValue;
|
||||
info.address = address;
|
||||
info.segment = debugger->originalMemory.currentSegment(debugger->cpu, address);
|
||||
info.type.wp.watchType = watchpoint->type;
|
||||
info.type.wp.accessType = type;
|
||||
info.address = address;
|
||||
info.segment = debugger->originalMemory.currentSegment(debugger->cpu, address);
|
||||
info.width = 1;
|
||||
info.pointId = watchpoint->id;
|
||||
info.target = TableLookup(&debugger->d.p->pointOwner, watchpoint->id);
|
||||
mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info);
|
||||
|
|
Loading…
Reference in New Issue