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