Debugger: Initial memory access logger support

This commit is contained in:
Vicki Pfau 2023-09-06 00:02:21 -07:00
parent 9c673f527d
commit d55a13c9ba
12 changed files with 990 additions and 8 deletions

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -1,5 +1,6 @@
include(ExportDirectory)
set(SOURCE_FILES
access-logger.c
cli-debugger.c
debugger.c
parser.c

View File

@ -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, &region->blockEx[offset]);
ex = mDebuggerAccessLogFlagsExFillErrorIllegalOpcode(ex);
STORE_16LE(ex, 0, &region->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, &region->blockEx[offset + j]);
ex = mDebuggerAccessLogFlagsExFillExecuteARM(ex);
STORE_16LE(ex, 0, &region->blockEx[offset + j]);
}
} else {
region->block[offset + j] = mDebuggerAccessLogFlagsFillAccess16(region->block[offset]);
if (region->blockEx) {
uint16_t ex;
LOAD_16LE(ex, 0, &region->blockEx[offset + j]);
ex = mDebuggerAccessLogFlagsExFillExecuteThumb(ex);
STORE_16LE(ex, 0, &region->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, &region->blockEx[offset + j]);
ex = mDebuggerAccessLogFlagsExFillExecuteOpcode(ex);
STORE_16LE(ex, 0, &region->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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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>

View File

@ -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");

View File

@ -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;

View File

@ -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);