Debugger: NES - Added new "break on..." options

Invalid VRAM access, Invalid OAM write, DMA controller read, Unstable opcodes
This commit is contained in:
Sour 2025-03-27 18:03:25 +09:00
parent 9e5d013c84
commit ad548465c6
14 changed files with 109 additions and 49 deletions

View file

@ -325,12 +325,15 @@ enum class BreakSource
GbOamCorruption,
NesBreakOnDecayedOamRead,
NesBreakOnPpu2000ScrollGlitch,
NesBreakOnPpu2006ScrollGlitch,
NesBreakOnPpuScrollGlitch,
BreakOnUnofficialOpCode,
BreakOnUnstableOpCode,
NesBusConflict,
NesBreakOnCpuCrash,
NesBreakOnExtOutputMode,
NesInvalidVramAccess,
NesInvalidOamWrite,
NesDmaInputRead,
PceBreakOnInvalidVramAddress,

View file

@ -789,19 +789,22 @@ bool Debugger::IsDebugWindowOpened(CpuType cpuType)
bool Debugger::IsBreakOptionEnabled(BreakSource src)
{
DebugConfig& cfg = _settings->GetDebugConfig();
switch(src) {
case BreakSource::GbDisableLcdOutsideVblank: return _settings->GetDebugConfig().GbBreakOnDisableLcdOutsideVblank;
case BreakSource::GbInvalidVramAccess: return _settings->GetDebugConfig().GbBreakOnInvalidVramAccess;
case BreakSource::GbInvalidOamAccess: return _settings->GetDebugConfig().GbBreakOnInvalidOamAccess;
case BreakSource::NesBreakOnDecayedOamRead: return _settings->GetDebugConfig().NesBreakOnDecayedOamRead;
case BreakSource::NesBreakOnPpu2000ScrollGlitch: return _settings->GetDebugConfig().NesBreakOnPpu2000ScrollGlitch;
case BreakSource::NesBreakOnPpu2006ScrollGlitch: return _settings->GetDebugConfig().NesBreakOnPpu2006ScrollGlitch;
case BreakSource::NesBusConflict: return _settings->GetDebugConfig().NesBreakOnBusConflict;
case BreakSource::NesBreakOnCpuCrash: return _settings->GetDebugConfig().NesBreakOnCpuCrash;
case BreakSource::NesBreakOnExtOutputMode: return _settings->GetDebugConfig().NesBreakOnExtOutputMode;
case BreakSource::PceBreakOnInvalidVramAddress: return _settings->GetDebugConfig().PceBreakOnInvalidVramAddress;
case BreakSource::GbaInvalidOpCode: return _settings->GetDebugConfig().GbaBreakOnInvalidOpCode;
case BreakSource::GbaUnalignedMemoryAccess: return _settings->GetDebugConfig().GbaBreakOnUnalignedMemAccess;
case BreakSource::GbDisableLcdOutsideVblank: return cfg.GbBreakOnDisableLcdOutsideVblank;
case BreakSource::GbInvalidVramAccess: return cfg.GbBreakOnInvalidVramAccess;
case BreakSource::GbInvalidOamAccess: return cfg.GbBreakOnInvalidOamAccess;
case BreakSource::NesBreakOnDecayedOamRead: return cfg.NesBreakOnDecayedOamRead;
case BreakSource::NesBreakOnPpuScrollGlitch: return cfg.NesBreakOnPpuScrollGlitch;
case BreakSource::NesBusConflict: return cfg.NesBreakOnBusConflict;
case BreakSource::NesBreakOnCpuCrash: return cfg.NesBreakOnCpuCrash;
case BreakSource::NesBreakOnExtOutputMode: return cfg.NesBreakOnExtOutputMode;
case BreakSource::NesInvalidVramAccess: return cfg.NesBreakOnInvalidVramAccess;
case BreakSource::NesInvalidOamWrite: return cfg.NesBreakOnInvalidOamWrite;
case BreakSource::NesDmaInputRead: return cfg.NesBreakOnDmaInputRead;
case BreakSource::PceBreakOnInvalidVramAddress: return cfg.PceBreakOnInvalidVramAddress;
case BreakSource::GbaInvalidOpCode: return cfg.GbaBreakOnInvalidOpCode;
case BreakSource::GbaUnalignedMemoryAccess: return cfg.GbaBreakOnUnalignedMemAccess;
}
return true;
}

View file

@ -10,7 +10,6 @@
#include "Debugger/MemoryDumper.h"
#include "Debugger/MemoryAccessCounter.h"
#include "Debugger/ExpressionEvaluator.h"
#include "Debugger/CodeDataLogger.h"
#include "NES/NesHeader.h"
#include "NES/NesConsole.h"
#include "NES/NesCpu.h"
@ -135,6 +134,8 @@ void NesDebugger::ProcessInstruction()
_step->Break(BreakSource::BreakOnBrk);
} else if(_settings->GetDebugConfig().NesBreakOnUnofficialOpCode && NesDisUtils::IsOpUnofficial(opCode)) {
_step->Break(BreakSource::BreakOnUnofficialOpCode);
} else if(_settings->GetDebugConfig().NesBreakOnUnstableOpCode && NesDisUtils::IsOpUnstable(opCode)) {
_step->Break(BreakSource::BreakOnUnstableOpCode);
}
}
@ -189,7 +190,9 @@ void NesDebugger::ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType
if(operation.Type == MemoryOperationType::DmaRead) {
bool isDmcDma = _cpu->IsDmcDma();
_eventManager->AddEvent(isDmcDma ? DebugEventType::DmcDmaRead : DebugEventType::DmaRead, operation);
if(isDmcDma && addressInfo.Type == MemoryType::NesPrgRom && addressInfo.Address >= 0) {
if((addr == 0x4016 || addr == 0x4017) && _settings->CheckDebuggerFlag(DebuggerFlags::NesDebuggerEnabled) && _settings->GetDebugConfig().NesBreakOnDmaInputRead) {
_debugger->BreakImmediately(CpuType::Nes, BreakSource::NesDmaInputRead);
} else if(isDmcDma && addressInfo.Type == MemoryType::NesPrgRom && addressInfo.Address >= 0) {
_codeDataLogger->SetData<NesCdlFlags::PcmData>(addressInfo.Address);
}
} else if(operation.Type != MemoryOperationType::DummyRead && addressInfo.Type == MemoryType::NesPrgRom && addressInfo.Address >= 0) {

View file

@ -239,6 +239,11 @@ bool NesDisUtils::IsOpUnofficial(uint8_t opCode)
return _isUnofficial[opCode];
}
bool NesDisUtils::IsOpUnstable(uint8_t opCode)
{
return opCode == 0xAB || opCode == 0x8B;
}
bool NesDisUtils::IsUnconditionalJump(uint8_t opCode)
{
switch(opCode) {

View file

@ -22,6 +22,7 @@ public:
static char const* const GetOpName(uint8_t opCode);
static NesAddrMode GetOpMode(uint8_t opCode);
static bool IsOpUnofficial(uint8_t opCode);
static bool IsOpUnstable(uint8_t opCode);
static bool IsUnconditionalJump(uint8_t opCode);
static bool IsConditionalJump(uint8_t opCode);
static CdlFlags::CdlFlags GetOpFlags(uint8_t opCode, uint16_t pc, uint16_t prevPc);

View file

@ -433,7 +433,7 @@ void NesCpu::ProcessPendingDma(uint16_t readAddress)
assert(_needHalt || _needDummyRead);
processCycle();
if(!skipDummyReads) {
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
_memoryManager->Read(readAddress, MemoryOperationType::DmaRead);
}
EndCpuCycle(true);
}
@ -451,7 +451,7 @@ void NesCpu::ProcessPendingDma(uint16_t readAddress)
//Align to read cycle before starting sprite DMA (or align to perform DMC read)
processCycle();
if(!skipDummyReads) {
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
_memoryManager->Read(readAddress, MemoryOperationType::DmaRead);
}
EndCpuCycle(true);
}

View file

@ -389,6 +389,10 @@ template<class T> uint8_t NesPpu<T>::ReadRam(uint16_t addr)
openBusMask = 0x00;
}
if(_scanline < 240 && IsRenderingEnabled()) {
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesInvalidVramAccess);
}
_ignoreVramRead = 6;
_needStateUpdate = true;
_needVideoRamIncrement = true;
@ -441,6 +445,7 @@ template<class T> void NesPpu<T>::WriteRam(uint16_t addr, uint8_t value)
//"Writes to OAMDATA during rendering (on the pre-render line and the visible lines 0-239, provided either sprite or background rendering is enabled) do not modify values in OAM,
//but do perform a glitchy increment of OAMADDR, bumping only the high 6 bits"
_spriteRamAddr = (_spriteRamAddr + 4) & 0xFF;
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesInvalidOamWrite);
}
break;
@ -489,6 +494,7 @@ template<class T> void NesPpu<T>::WriteRam(uint16_t addr, uint8_t value)
} else {
//During rendering, the value written is ignored, and instead the address' LSB is used (not confirmed, based on Visual NES)
_mapper->WriteVram(_ppuBusAddress & 0x3FFF, _ppuBusAddress & 0xFF);
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesInvalidVramAccess);
}
}
_needStateUpdate = true;
@ -510,7 +516,7 @@ template<class T> void NesPpu<T>::ProcessTmpAddrScrollGlitch(uint16_t normalAddr
if(_cycle == 257 && _console->GetNesConfig().EnablePpu2000ScrollGlitch && _scanline < 240 && IsRenderingEnabled()) {
//Use open bus to set some parts of V (glitch that occurs when writing to $2000/$2005/$2006 on cycle 257)
_videoRamAddr = (_videoRamAddr & ~mask) | (value & mask);
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesBreakOnPpu2000ScrollGlitch);
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesBreakOnPpuScrollGlitch);
}
}
@ -1461,10 +1467,10 @@ template<class T> void NesPpu<T>::UpdateState()
//When a $2006 address update lands on the Y or X increment, the written value is bugged and is ANDed with the incremented value
if(_cycle == 257) {
_videoRamAddr &= _updateVramAddr;
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesBreakOnPpu2006ScrollGlitch);
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesBreakOnPpuScrollGlitch);
} else if(_cycle > 0 && (_cycle & 0x07) == 0 && (_cycle <= 256 || _cycle > 320)) {
_videoRamAddr = (_updateVramAddr & ~0x41F) | (_videoRamAddr & _updateVramAddr & 0x41F);
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesBreakOnPpu2006ScrollGlitch);
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesBreakOnPpuScrollGlitch);
} else {
_videoRamAddr = _updateVramAddr;
}

View file

@ -812,12 +812,15 @@ struct DebugConfig
bool NesBreakOnBrk = false;
bool NesBreakOnUnofficialOpCode = false;
bool NesBreakOnUnstableOpCode = false;
bool NesBreakOnCpuCrash = false;
bool NesBreakOnBusConflict = false;
bool NesBreakOnDecayedOamRead = false;
bool NesBreakOnPpu2000ScrollGlitch = false;
bool NesBreakOnPpu2006ScrollGlitch = false;
bool NesBreakOnPpuScrollGlitch = false;
bool NesBreakOnExtOutputMode = false;
bool NesBreakOnInvalidVramAccess = false;
bool NesBreakOnInvalidOamWrite = false;
bool NesBreakOnDmaInputRead = false;
bool PceBreakOnBrk = false;
bool PceBreakOnUnofficialOpCode = false;

View file

@ -73,12 +73,15 @@ namespace Mesen.Config
NesBreakOnBrk = Debugger.Nes.BreakOnBrk,
NesBreakOnUnofficialOpCode = Debugger.Nes.BreakOnUnofficialOpCode,
NesBreakOnUnstableOpCode = Debugger.Nes.BreakOnUnstableOpCode,
NesBreakOnCpuCrash = Debugger.Nes.BreakOnCpuCrash,
NesBreakOnBusConflict = Debugger.Nes.BreakOnBusConflict,
NesBreakOnDecayedOamRead = Debugger.Nes.BreakOnDecayedOamRead,
NesBreakOnPpu2000ScrollGlitch = Debugger.Nes.BreakOnPpu2000ScrollGlitch,
NesBreakOnPpu2006ScrollGlitch = Debugger.Nes.BreakOnPpu2006ScrollGlitch,
NesBreakOnExtOutputMode = Debugger.Nes.NesBreakOnExtOutputMode,
NesBreakOnPpuScrollGlitch = Debugger.Nes.BreakOnPpuScrollGlitch,
NesBreakOnExtOutputMode = Debugger.Nes.BreakOnExtOutputMode,
NesBreakOnInvalidVramAccess = Debugger.Nes.BreakOnInvalidVramAccess,
NesBreakOnInvalidOamWrite = Debugger.Nes.BreakOnInvalidOamWrite,
NesBreakOnDmaInputRead = Debugger.Nes.BreakOnDmaInputRead,
PceBreakOnBrk = Debugger.Pce.BreakOnBrk,
PceBreakOnUnofficialOpCode = Debugger.Pce.BreakOnUnofficialOpCode,
@ -136,12 +139,15 @@ namespace Mesen.Config
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnBrk;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnUnofficialOpCode;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnUnstableOpCode;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnCpuCrash;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnBusConflict;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnDecayedOamRead;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnPpu2000ScrollGlitch;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnPpu2006ScrollGlitch;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnPpuScrollGlitch;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnExtOutputMode;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnInvalidVramAccess;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnInvalidOamWrite;
[MarshalAs(UnmanagedType.I1)] public bool NesBreakOnDmaInputRead;
[MarshalAs(UnmanagedType.I1)] public bool PceBreakOnBrk;
[MarshalAs(UnmanagedType.I1)] public bool PceBreakOnUnofficialOpCode;

View file

@ -13,12 +13,15 @@ namespace Mesen.Config
{
[Reactive] public bool BreakOnBrk { get; set; } = false;
[Reactive] public bool BreakOnUnofficialOpCode { get; set; } = false;
[Reactive] public bool BreakOnUnstableOpCode { get; set; } = false;
[Reactive] public bool BreakOnCpuCrash { get; set; } = false;
[Reactive] public bool BreakOnBusConflict { get; set; } = false;
[Reactive] public bool BreakOnDecayedOamRead { get; set; } = false;
[Reactive] public bool BreakOnPpu2000ScrollGlitch { get; set; } = false;
[Reactive] public bool BreakOnPpu2006ScrollGlitch { get; set; } = false;
[Reactive] public bool NesBreakOnExtOutputMode { get; set; } = true;
[Reactive] public bool BreakOnPpuScrollGlitch { get; set; } = false;
[Reactive] public bool BreakOnExtOutputMode { get; set; } = true;
[Reactive] public bool BreakOnInvalidVramAccess { get; set; } = false;
[Reactive] public bool BreakOnInvalidOamWrite { get; set; } = false;
[Reactive] public bool BreakOnDmaInputRead { get; set; } = false;
}
}

View file

@ -22,9 +22,12 @@
<Style Selector="WrapPanel > CheckBox">
<Setter Property="Margin" Value="0 0 7 0" />
</Style>
<Style Selector="CheckBox">
<Setter Property="ToolTip.ShowDelay" Value="100" />
</Style>
</UserControl.Styles>
<StackPanel>
<StackPanel Margin="3 0">
<c:OptionSection Header="{l:Translate lblDisassemblyOptions}" Margin="0">
<Grid ColumnDefinitions="Auto,Auto,*" RowDefinitions="Auto,Auto,Auto">
<TextBlock VerticalAlignment="Center" Text="{l:Translate lblVerifiedData}" />
@ -127,6 +130,11 @@
IsChecked="{Binding Config.Nes.BreakOnUnofficialOpCode}"
Content="{l:Translate chkBreakOnUnofficialOpCode}"
/>
<CheckBox
IsChecked="{Binding Config.Nes.BreakOnUnstableOpCode}"
Content="{l:Translate chkBreakOnUnstableOpCode}"
ToolTip.Tip="{l:Translate chkBreakOnUnstableOpCodeHint}"
/>
<CheckBox
IsChecked="{Binding Config.Nes.BreakOnCpuCrash}"
Content="{l:Translate chkBreakOnCpuCrash}"
@ -141,15 +149,23 @@
Content="{l:Translate chkBreakOnDecayedOamRead}"
/>
<CheckBox
IsChecked="{Binding Config.Nes.BreakOnPpu2000ScrollGlitch}"
Content="{l:Translate chkBreakOnPpu2000ScrollGlitch}"
IsChecked="{Binding Config.Nes.BreakOnDmaInputRead}"
Content="{l:Translate chkNesDmaInputRead}"
/>
<CheckBox
IsChecked="{Binding Config.Nes.BreakOnPpu2006ScrollGlitch}"
Content="{l:Translate chkBreakOnPpu2006ScrollGlitch}"
IsChecked="{Binding Config.Nes.BreakOnInvalidOamWrite}"
Content="{l:Translate chkNesInvalidOamWrite}"
/>
<CheckBox
IsChecked="{Binding Config.Nes.NesBreakOnExtOutputMode}"
IsChecked="{Binding Config.Nes.BreakOnInvalidVramAccess}"
Content="{l:Translate chkNesInvalidVramAccess}"
/>
<CheckBox
IsChecked="{Binding Config.Nes.BreakOnPpuScrollGlitch}"
Content="{l:Translate chkBreakOnPpuScrollGlitch}"
/>
<CheckBox
IsChecked="{Binding Config.Nes.BreakOnExtOutputMode}"
Content="{l:Translate chkNesBreakOnExtOutputMode}"
/>
</StackPanel>

View file

@ -154,7 +154,7 @@
</StackPanel>
</DockPanel>
<DockPanel>
<Panel DockPanel.Dock="Right" IsVisible="{Binding Config.ShowSettingsPanel}" Margin="3 0">
<Panel DockPanel.Dock="Right" IsVisible="{Binding Config.ShowSettingsPanel}">
<ScrollViewer>
<v:DebuggerOptionsView DataContext="{Binding Options}" />
</ScrollViewer>

View file

@ -1557,12 +1557,15 @@ namespace Mesen.Interop
GbOamCorruption,
NesBreakOnDecayedOamRead,
NesBreakOnPpu2000ScrollGlitch,
NesBreakOnPpu2006ScrollGlitch,
NesBreakOnPpuScrollGlitch,
BreakOnUnofficialOpCode,
BreakOnUnstableOpCode,
NesBusConflict,
NesBreakOnCpuCrash,
NesBreakOnExtOutputMode,
NesInvalidVramAccess,
NesInvalidOamWrite,
NesDmaInputRead,
PceBreakOnInvalidVramAddress,

View file

@ -1354,21 +1354,25 @@
<Control ID="chkBreakOnPowerCycleReset">Power/reset</Control>
<Control ID="chkBreakOnUnofficialOpCode">Unofficial opcodes</Control>
<Control ID="chkBreakOnUnstableOpCode">Unstable opcodes</Control>
<Control ID="chkBreakOnUnstableOpCodeHint">Breaks on "ANE #imm" ($8B) and "LAX #imm" ($AB)&#10;Both opcodes are unstable on hardware and their results are usually unreliable.</Control>
<Control ID="chkBreakOnCpuCrash">CPU crash</Control>
<Control ID="chkBreakOnBusConflict">Bus conflict</Control>
<Control ID="chkBreakOnDecayedOamRead">Decayed OAM read</Control>
<Control ID="chkBreakOnPpu2000ScrollGlitch">PPU $2000/5/6 scroll glitch</Control>
<Control ID="chkBreakOnPpu2006ScrollGlitch">PPU $2006 scroll glitch</Control>
<Control ID="chkBreakOnPpuScrollGlitch">PPU scroll glitch</Control>
<Control ID="chkNesInvalidVramAccess">Invalid VRAM access</Control>
<Control ID="chkNesInvalidOamWrite">Invalid OAM write</Control>
<Control ID="chkNesDmaInputRead">DMA controller read</Control>
<Control ID="chkNesBreakOnExtOutputMode">PPU EXT output mode</Control>
<Control ID="chkGbBreakOnInvalidOamAccess">Break on invalid OAM access</Control>
<Control ID="chkGbBreakOnInvalidVramAccess">Break on invalid VRAM access</Control>
<Control ID="chkGbBreakOnDisableLcdOutsideVblank">Break on LCD disable outside vblank</Control>
<Control ID="chkGbBreakOnInvalidOpCode">Break on invalid instructions</Control>
<Control ID="chkGbBreakOnNopLoad">Break on LD B, B (nop)</Control>
<Control ID="chkGbBreakOnOamCorruption">Break on OAM corruption</Control>
<Control ID="chkGbBreakOnInvalidOamAccess">Invalid OAM access</Control>
<Control ID="chkGbBreakOnInvalidVramAccess">Invalid VRAM access</Control>
<Control ID="chkGbBreakOnDisableLcdOutsideVblank">LCD disable outside vblank</Control>
<Control ID="chkGbBreakOnInvalidOpCode">Invalid instructions</Control>
<Control ID="chkGbBreakOnNopLoad">LD B, B (nop)</Control>
<Control ID="chkGbBreakOnOamCorruption">OAM corruption</Control>
<Control ID="chkPceBreakOnInvalidVramAddress">Break on invalid VRAM address</Control>
<Control ID="chkPceBreakOnInvalidVramAddress">Invalid VRAM address</Control>
<Control ID="chkGbaBreakOnInvalidOpCode">Break on invalid instructions</Control>
<Control ID="chkGbaBreakOnNopLoad">Break on MOV R11, R11 (nop)</Control>
@ -2945,9 +2949,13 @@ E
<Value ID="NesBreakOnPpu2000ScrollGlitch">$2000/5/6 scroll glitch</Value>
<Value ID="NesBreakOnPpu2006ScrollGlitch">$2006 scroll glitch</Value>
<Value ID="BreakOnUnofficialOpCode">Unofficial OP</Value>
<Value ID="BreakOnUnstableOpCode">Unstable OP</Value>
<Value ID="NesBusConflict">Bus conflict</Value>
<Value ID="NesBreakOnCpuCrash">CPU crashed</Value>
<Value ID="NesBreakOnExtOutputMode">PPU EXT output mode</Value>
<Value ID="NesInvalidVramAccess">Invalid VRAM access</Value>
<Value ID="NesInvalidOamWrite">Invalid OAM access</Value>
<Value ID="NesDmaInputRead">DMA controller read</Value>
<Value ID="PceBreakOnInvalidVramAddress">Invalid VRAM address (>= $8000)</Value>