diff --git a/AppleWin-VS2022.vcxproj b/AppleWin-VS2022.vcxproj
index 4aa6fd1f..5fb83793 100644
--- a/AppleWin-VS2022.vcxproj
+++ b/AppleWin-VS2022.vcxproj
@@ -66,6 +66,7 @@
+
@@ -183,6 +184,7 @@
+
diff --git a/AppleWin-VS2022.vcxproj.filters b/AppleWin-VS2022.vcxproj.filters
index 809b2899..269313b3 100644
--- a/AppleWin-VS2022.vcxproj.filters
+++ b/AppleWin-VS2022.vcxproj.filters
@@ -280,6 +280,9 @@
Source Files\Windows
+
+ Source Files\Debugger
+
@@ -636,6 +639,9 @@
Source Files\Windows
+
+ Source Files\Debugger
+
diff --git a/source/Card.h b/source/Card.h
index 4fce8614..8b6fbcb7 100644
--- a/source/Card.h
+++ b/source/Card.h
@@ -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 };
diff --git a/source/CardManager.cpp b/source/CardManager.cpp
index 735309a0..63df606a 100644
--- a/source/CardManager.cpp
+++ b/source/CardManager.cpp
@@ -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;
diff --git a/source/CmdLine.cpp b/source/CmdLine.cpp
index b0fbb1fa..5ae0570f 100644
--- a/source/CmdLine.cpp
+++ b/source/CmdLine.cpp
@@ -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;
diff --git a/source/Common.h b/source/Common.h
index 76c6a2d4..199bc186 100644
--- a/source/Common.h
+++ b/source/Common.h
@@ -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
diff --git a/source/Debugger/BreakpointCard.cpp b/source/Debugger/BreakpointCard.cpp
new file mode 100644
index 00000000..748cb59a
--- /dev/null
+++ b/source/Debugger/BreakpointCard.cpp
@@ -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 , but opcode hasn't executed yet
+ // . Breakpoint card aserts IRQ, and IRQ is taken by 6502
+ // . 6502 executes ISR, and returns to
+ // . BP occurs at ...
+
+ 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(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;
+ }
+
+}
diff --git a/source/Debugger/BreakpointCard.h b/source/Debugger/BreakpointCard.h
new file mode 100644
index 00000000..7317bb11
--- /dev/null
+++ b/source/Debugger/BreakpointCard.h
@@ -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 m_BP_FIFO;
+
+ INTERCEPTBREAKPOINT m_deferred;
+ SyncEvent m_syncEvent;
+};
diff --git a/source/Debugger/Debug.cpp b/source/Debugger/Debug.cpp
index 32e09b5a..32d565f2 100644
--- a/source/Debugger/Debug.cpp
+++ b/source/Debugger/Debug.cpp
@@ -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;
+}
diff --git a/source/Debugger/Debug.h b/source/Debugger/Debug.h
index 3b86f674..0d3b112b 100644
--- a/source/Debugger/Debug.h
+++ b/source/Debugger/Debug.h
@@ -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);