Add a Debugger breakpoint card (PR #1441)

. For AppleWin-Test regression tests for debugger breakpoints
This commit is contained in:
TomCh
2025-10-18 14:54:39 +01:00
committed by GitHub
parent ae28de062a
commit dce193b760
10 changed files with 389 additions and 3 deletions
+1
View File
@@ -27,6 +27,7 @@ enum SS_CARDTYPE
CT_Uthernet2,
CT_MegaAudio, // Soundcard
CT_SDMusic, // Soundcard
CT_BreakpointCard,
};
enum SLOTS { SLOT0=0, SLOT1, SLOT2, SLOT3, SLOT4, SLOT5, SLOT6, SLOT7, NUM_SLOTS, SLOT_AUX, GAME_IO_CONNECTOR };
+4 -1
View File
@@ -32,6 +32,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "CardManager.h"
#include "Registry.h"
#include "BreakpointCard.h"
#include "Disk.h"
#include "FourPlay.h"
#include "Harddisk.h"
@@ -132,7 +133,9 @@ void CardManager::InsertInternal(UINT slot, SS_CARDTYPE type)
m_slot[slot] = new Saturn128K(slot, Saturn128K::kMaxSaturnBanks);
}
break;
case CT_BreakpointCard:
m_slot[slot] = new BreakpointCard(slot);
break;
default:
_ASSERT(0);
break;
+4
View File
@@ -223,6 +223,10 @@ bool ProcessCmdLine(LPSTR lpCmdLine)
g_cmdLine.slotInsert[slot] = CT_SDMusic;
g_cmdLine.supportExtraMBCardTypes = true;
}
else if (strcmp(lpCmdLine, "breakpointcard") == 0)
{
g_cmdLine.slotInsert[slot] = CT_BreakpointCard;
}
else if (strcmp(lpCmdLine, "6522a-bad") == 0)
{
g_cmdLine.slotInfo[slot].useBad6522A = true;
+1 -1
View File
@@ -151,7 +151,7 @@ enum AppMode_e
#define PATH_SEPARATOR '/'
#endif
enum eIRQSRC {IS_6522=0, IS_SPEECH, IS_SSC, IS_MOUSE};
enum eIRQSRC {IS_6522=0, IS_SPEECH, IS_SSC, IS_MOUSE, IS_BREAKPOINTCARD};
//
#define APPLE2P_MASK 0x01
+237
View File
@@ -0,0 +1,237 @@
/*
AppleWin : An Apple //e emulator for Windows
Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2025, Tom Charlesworth, Michael Pohoreski
AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
Breakpoint card
---------------
For debugger breakpoint regression tests.
C0n0 (R) : Status
b7: BP match
b6: BP mismatch
b1: FIFO full
b0: FIFO empty
C0n1 (R) : Cmd: Reset
Flush FIFO
NB. don't change intercept mode
C0n2 (R) : Cmd: Intercept BP by card
C0n3 (R) : Cmd: Intercept BP by debugger
C0nX (R) : ID byte $0X (except when X=0)
C0nX (W) : FIFO
FIFO is 32 x 6 bytes deep
where: 6-byte BP-set = {type.b, addrStart.w, addrEnd.w, access.b}
Any r/w clears the IRQ
RESET clears the IRQ, flushes FIFO and sets: Intercept BP by card
Operation:
. Cmd:Reset flushes FIFO & sets Status.empty=1 (all other bits are zero)
. Cmd:Intercept BP by card
. Write FIFO with multiple BP-sets for test code
- these are the expected BP hit results
. Status.full=1 after writing 32 BP-sets
. Execute test code
. AppleWin debugger:
- Cmd:Intercept BP by card: when active, then debugger doesn't break on BP match
- Instead debugger hands off BP to Breakpoint card (only 'bp' & 'bpm[r|w]')
. Remove BP-set from front of FIFO
- Check if it matches/mismatches and set status accordingly
. Delay 1 cycle & assert IRQ
. CPU vectors to IRQ handler
. Reading Status clears IRQ
*/
#include "StdAfx.h"
#include "CardManager.h"
#include "Core.h"
#include "CPU.h"
#include "Debug.h"
#include "BreakpointCard.h"
#include "Memory.h"
BreakpointCard::BreakpointCard(UINT slot) :
Card(CT_BreakpointCard, slot),
m_syncEvent(slot, 0, SyncEventCallback) // use slot# as "unique" id for Disk2InterfaceCards
{
Reset(true);
}
BreakpointCard::~BreakpointCard()
{
if (m_syncEvent.m_active)
g_SynchronousEventMgr.Remove(m_syncEvent.m_id);
}
void BreakpointCard::Reset(const bool powerCycle)
{
m_interceptBPByCard = false;
InterceptBreakpoints(m_slot, nullptr);
ResetState();
if (!powerCycle) // if called by ctor: CriticalSection not created yet
CpuIrqDeassert(IS_BREAKPOINTCARD);
}
void BreakpointCard::InitializeIO(LPBYTE pCxRomPeripheral)
{
RegisterIoHandler(m_slot, &BreakpointCard::IORead, &BreakpointCard::IOWrite, IO_Null, IO_Null, this, NULL);
}
BYTE __stdcall BreakpointCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles)
{
const UINT slot = ((addr & 0xff) >> 4) - 8;
BreakpointCard* pCard = (BreakpointCard*)MemGetSlotParameters(slot);
CpuIrqDeassert(IS_BREAKPOINTCARD);
const BYTE reg = addr & 0xf;
if (reg == 0)
{
uint8_t otherStatus = pCard->m_BP_FIFO.empty() ? kEmpty : 0;
return pCard->m_status | otherStatus;
}
else if (reg == 1) // Cmd: Reset
{
pCard->ResetState();
}
else if (reg == 2) // Cmd: Intercept BP by card
{
pCard->m_interceptBPByCard = true;
InterceptBreakpoints(slot, BreakpointCard::CbFunction);
}
else if (reg == 3) // Cmd: Intercept BP by debugger
{
pCard->m_interceptBPByCard = false;
InterceptBreakpoints(slot, nullptr);
}
return addr & 0x0f; // ID bytes [$01-$0F]
}
BYTE __stdcall BreakpointCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles)
{
const UINT slot = ((addr & 0xff) >> 4) - 8;
BreakpointCard* pCard = (BreakpointCard*)MemGetSlotParameters(slot);
CpuIrqDeassert(IS_BREAKPOINTCARD);
pCard->m_BPSet[pCard->m_BPSetIdx++] = value;
if (pCard->m_BPSetIdx == kNumParams)
{
pCard->m_BPSetIdx = 0;
if (!(pCard->m_status & kFull))
{
INTERCEPTBREAKPOINT bpSet;
bpSet.type = pCard->m_BPSet[0];
bpSet.addrStart = (pCard->m_BPSet[2] << 8) | pCard->m_BPSet[1];
bpSet.addrEnd = (pCard->m_BPSet[4] << 8) | pCard->m_BPSet[3];
bpSet.access = pCard->m_BPSet[5];
pCard->m_BP_FIFO.push(bpSet);
}
if (pCard->m_BP_FIFO.size() == kFIFO_SIZE)
pCard->m_status |= kFull;
}
return 0;
}
void BreakpointCard::CbFunction(uint8_t slot, INTERCEPTBREAKPOINT interceptBreakpoint)
{
BreakpointCard* pCard = (BreakpointCard*)MemGetSlotParameters(slot);
pCard->m_deferred = interceptBreakpoint;
// Defer processing the breakpoint by 1 opcode (ie. 1 cycle), otherwise this happens:
// . BP occurs at <addr>, but opcode hasn't executed yet
// . Breakpoint card aserts IRQ, and IRQ is taken by 6502
// . 6502 executes ISR, and returns to <addr>
// . BP occurs at <addr>...
pCard->m_syncEvent.m_cyclesRemaining = 1; // Next opcode
g_SynchronousEventMgr.Insert(&pCard->m_syncEvent);
}
int BreakpointCard::SyncEventCallback(int id, int cycles, ULONG uExecutedCycles)
{
BreakpointCard& card = dynamic_cast<BreakpointCard&>(GetCardMgr().GetRef(id));
card.Deferred(card.m_deferred.type, card.m_deferred.addrStart, card.m_deferred.addrEnd, card.m_deferred.access);
return 0; // Don't repeat event
}
void BreakpointCard::Deferred(uint8_t type, uint16_t addrStart, uint16_t addrEnd, uint8_t access)
{
m_status &= ~(kMatch | kMismatch);
CpuIrqAssert(IS_BREAKPOINTCARD);
if (m_BP_FIFO.empty())
{
m_status |= kMismatch;
return;
}
const INTERCEPTBREAKPOINT bpSet = m_BP_FIFO.front();
m_BP_FIFO.pop();
m_status &= ~kFull;
if (bpSet.type != type)
{
m_status |= kMismatch;
return;
}
switch (type)
{
case BPTYPE_PC:
case BPTYPE_MEM:
if ((bpSet.addrStart != addrStart) || (bpSet.access != access))
{
m_status |= kMismatch;
break;
}
m_status |= kMatch;
break;
case BPTYPE_DMA:
{
bool validAddr = ((addrStart >= bpSet.addrStart && addrStart < bpSet.addrEnd) ||
(bpSet.addrStart >= addrStart && bpSet.addrStart < addrEnd));
if (!validAddr || (bpSet.access != access))
{
m_status |= kMismatch;
break;
}
m_status |= kMatch;
}
break;
case BPTYPE_UNKNOWN:
default:
m_status |= kMismatch;
break;
}
}
+90
View File
@@ -0,0 +1,90 @@
#pragma once
#include "Card.h"
#include "Interface.h"
#include "SynchronousEventManager.h"
enum BreakType_t { BPTYPE_UNKNOWN, BPTYPE_PC, BPTYPE_MEM, BPTYPE_DMA };
enum BreakAccess_t { BPACCESS_R, BPACCESS_W, BPACCESS_RW };
struct INTERCEPTBREAKPOINT
{
INTERCEPTBREAKPOINT()
{
type = BPTYPE_UNKNOWN;
addrStart = 0x0000;
addrEnd = 0x0000;
access = BPACCESS_R;
}
void Set(uint8_t type_, uint16_t addrStart_, uint8_t access_)
{
type = type_;
addrStart = addrStart_;
access = access_;
}
void SetDMA(uint16_t addrStart_, uint16_t addrEnd_, uint8_t access_)
{
type = BPTYPE_DMA;
addrStart = addrStart_;
addrEnd = addrEnd_;
access = access_;
}
uint8_t type;
uint16_t addrStart;
uint16_t addrEnd;
uint8_t access;
};
class BreakpointCard : public Card
{
public:
BreakpointCard(UINT slot);
virtual ~BreakpointCard(void);
void ResetState()
{
m_BPSetIdx = 0;
m_status = 0;
while (!m_BP_FIFO.empty())
m_BP_FIFO.pop();
}
virtual void Destroy(void) {}
virtual void Reset(const bool powerCycle);
virtual void Update(const ULONG nExecutedCycles) {}
virtual void InitializeIO(LPBYTE pCxRomPeripheral);
static BYTE __stdcall IORead(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles);
static BYTE __stdcall IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles);
static void CbFunction(uint8_t slot, INTERCEPTBREAKPOINT interceptBreakpoint);
virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper) {}
virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { return false; }
private:
static int SyncEventCallback(int id, int cycles, ULONG uExecutedCycles);
void Deferred(uint8_t type, uint16_t addrStart, uint16_t addrEnd, uint8_t access);
static const uint8_t kMatch = 1 << 7;
static const uint8_t kMismatch = 1 << 6;
static const uint8_t kFull = 1 << 1;
static const uint8_t kEmpty = 1 << 0;
uint8_t m_status;
bool m_interceptBPByCard;
static const uint8_t kNumParams = 6; // sizeof(packed(INTERCEPTBREAKPOINT))
BYTE m_BPSet[kNumParams];
BYTE m_BPSetIdx;
static const uint8_t kFIFO_SIZE = 32; // needs to be big enough for each test's set of BPs
std::queue<INTERCEPTBREAKPOINT> m_BP_FIFO;
INTERCEPTBREAKPOINT m_deferred;
SyncEvent m_syncEvent;
};
+40 -1
View File
@@ -87,6 +87,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
static int g_bDebugBreakpointHit = 0; // See: BreakpointHit_t
static Breakpoint_t *g_pDebugBreakpointHit = nullptr;
static WORD g_nBreakMemoryAddr = 0;
static std::string g_sBreakMemoryFullPrefixAddr;
static int g_breakpointHitID = -1;
@@ -363,6 +364,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
static std::string g_sAutoRunScriptFilename("DebuggerAutoRun.txt");
static uint8_t g_interceptBreakpointsSlot = 0;
static CBFUNCTION g_InterceptBreakpointsCB = nullptr;
// Private ________________________________________________________________________________________
@@ -1429,6 +1433,7 @@ int CheckBreakpointsIO ()
{
if (_CheckBreakpointValue( pBP, nAddress ))
{
g_nBreakMemoryAddr = (WORD)nAddress; // last BP hit
g_sBreakMemoryFullPrefixAddr = GetFullPrefixAddrForBreakpoint(pBP->addrPrefix, (WORD)nAddress, DEVICE_e::DEV_MEMORY, false); // string is last BP hit
BYTE opcode = ReadByteFromMemory(regs.pc);
@@ -9103,6 +9108,7 @@ void DebugContinueStepping (const bool bCallerWillUpdateDisplay/*=false*/)
{
std::string stopReason = "Unknown!";
bool skipStopReason = false;
INTERCEPTBREAKPOINT interceptBreakpoint;
if (regs.pc == g_nDebugStepUntil)
stopReason = StrFormat( CHC_DEFAULT "Register " CHC_REGS "PC" CHC_DEFAULT " matches '" CHC_INFO "Go until" CHC_DEFAULT "' address $" CHC_ADDRESS "%04X", g_nDebugStepUntil);
@@ -9120,14 +9126,25 @@ void DebugContinueStepping (const bool bCallerWillUpdateDisplay/*=false*/)
g_aBreakpointSource[ g_pDebugBreakpointHit->eSource ],
CHC_DEFAULT
);
if (g_pDebugBreakpointHit->eSource == BP_SRC_REG_PC)
interceptBreakpoint.Set(BPTYPE_PC, regs.pc, BPACCESS_R);
}
}
else if (g_bDebugBreakpointHit & BP_HIT_MEM)
{
stopReason = StrFormat("Memory access at %s", g_sBreakMemoryFullPrefixAddr.c_str());
interceptBreakpoint.Set(BPTYPE_MEM, g_nBreakMemoryAddr, BPACCESS_RW);
}
else if (g_bDebugBreakpointHit & BP_HIT_MEMW)
{
stopReason = StrFormat("Write access at %s", g_sBreakMemoryFullPrefixAddr.c_str());
interceptBreakpoint.Set(BPTYPE_MEM, g_nBreakMemoryAddr, BPACCESS_W);
}
else if (g_bDebugBreakpointHit & BP_HIT_MEMR)
{
stopReason = StrFormat("Read access at %s", g_sBreakMemoryFullPrefixAddr.c_str());
interceptBreakpoint.Set(BPTYPE_MEM, g_nBreakMemoryAddr, BPACCESS_R);
}
else if (g_bDebugBreakpointHit & BP_HIT_PC_READ_FLOATING_BUS_OR_IO_MEM)
stopReason = "PC reads from floating bus or I/O memory";
else if (g_bDebugBreakpointHit & BP_HIT_INTERRUPT)
@@ -9144,6 +9161,9 @@ void DebugContinueStepping (const bool bCallerWillUpdateDisplay/*=false*/)
if (!skipStopReason)
{
if (g_InterceptBreakpointsCB != nullptr)
g_InterceptBreakpointsCB(g_interceptBreakpointsSlot, interceptBreakpoint);
std::string hitId = GetBreakpointHitIdString(g_breakpointHitID);
ConsolePrintFormat(CHC_INFO "Stop reason: %s " CHC_DEFAULT "%s", hitId.c_str(), stopReason.c_str());
g_breakpointHitID = -1;
@@ -9160,12 +9180,22 @@ void DebugContinueStepping (const bool bCallerWillUpdateDisplay/*=false*/)
stopReason = StrFormat("HDD DMA from memory " CHC_ARG_SEP "$" CHC_ADDRESS "%04X" CHC_ARG_SEP "-" CHC_ADDRESS "%04X", g_DebugBreakOnDMA[i].memoryAddr, g_DebugBreakOnDMA[i].memoryAddrEnd);
std::string hitId = GetBreakpointHitIdString(g_DebugBreakOnDMA[i].BPid);
ConsolePrintFormat(CHC_INFO "Stop reason: %s " CHC_DEFAULT "%s", hitId.c_str(), stopReason.c_str());
if (g_InterceptBreakpointsCB != nullptr)
{
const uint8_t access = (nDebugBreakpointHit & BP_DMA_FROM_MEM) ? BPACCESS_R : BPACCESS_W;
interceptBreakpoint.SetDMA(g_DebugBreakOnDMA[i].memoryAddr, g_DebugBreakOnDMA[i].memoryAddrEnd, access);
g_InterceptBreakpointsCB(g_interceptBreakpointsSlot, interceptBreakpoint);
}
}
}
ConsoleUpdate();
g_nDebugSteps = 0;
//
if (g_InterceptBreakpointsCB == nullptr)
g_nDebugSteps = 0;
}
if (g_nDebugSteps > 0)
@@ -9286,6 +9316,7 @@ void DebugInitialize ()
WindowUpdateConsoleDisplayedSize();
// CLEAR THE BREAKPOINT AND WATCH TABLES
g_nBreakMemoryAddr = 0;
g_breakpointHitID = -1;
for (int i = 0; i < MAX_BREAKPOINTS; i++)
g_aBreakpoints[i].Clear();
@@ -10126,3 +10157,11 @@ void DebugSetAutoRunScript (std::string& sAutoRunScriptFilename)
{
g_sAutoRunScriptFilename = sAutoRunScriptFilename;
}
//===========================================================================
void InterceptBreakpoints(uint8_t slot, CBFUNCTION cbfunction)
{
g_interceptBreakpointsSlot = slot;
g_InterceptBreakpointsCB = cbfunction;
}
+4
View File
@@ -14,6 +14,7 @@
#include "Debugger_Display.h"
#include "Debugger_Symbols.h"
#include "Util_MemoryTextFile.h"
#include "BreakpointCard.h"
// Globals __________________________________________________________________
@@ -191,3 +192,6 @@
void ClearTempBreakpoints();
void DebugSetAutoRunScript(std::string& sAutoRunScriptFilename);
typedef void(*CBFUNCTION)(uint8_t slot, INTERCEPTBREAKPOINT interceptBreakpoint);
void InterceptBreakpoints(uint8_t slot, CBFUNCTION cbfunction);